diff --git a/.htaccess b/.htaccess index 9a2d22dd4f..2ea2a09ca8 100644 --- a/.htaccess +++ b/.htaccess @@ -122,6 +122,16 @@ #RewriteBase /magento/ +############################################ +## uncomment next line to enable light API calls processing + +# RewriteRule ^api/([a-z][0-9a-z_]+)/?$ api.php?type=$1 [QSA,L] + +############################################ +## rewrite API2 calls to api.php (by now it is REST only) + + RewriteRule ^api/rest api.php?type=rest [QSA,L] + ############################################ ## workaround for HTTP authorization ## in CGI environment diff --git a/RELEASE_NOTES.txt b/RELEASE_NOTES.txt index c9971abea7..b54e748dbc 100644 --- a/RELEASE_NOTES.txt +++ b/RELEASE_NOTES.txt @@ -1,3 +1,569 @@ +==== 1.7.0.0-rc1 ==== + +=== Major Highlights === +New Layered Navigation price bucket algorithm +Added captcha functionality +Implemented different base prices for customer groups +Added auto generation of coupon codes +Backup and Rollback functionality +VAT ID Validation added +Implemented DHL for Europe +Added REST API + +=== Improvements === +XmlConnect package release v22.1 +Upgraded TinyMCE to v3.4.7 +Mobile theme was redesigned +Added ability to translate action array parameter nodes via layout.xml +Added support for using custom currency symbols +Added functionality to cleaning old cache files by cron task +Refactored rules-based modules +Improved customer address handling using PayPal Express checkout +Refactored escaping functionality used with translations +Added ability to customize logo in emails from the admin +Implemented front-end cookies restriction functionality +Added ability to turn off/on IP Tracking (e.g. 'Placed from IP') on the Sales pages in back-end (Order, Invoice, Shipment, Credit memo) +Provided logic to disable ACL resources through configuration files +Added additional placeholders for extensions developers + +=== Changes === +Added "Cache On Delivery" and "Bank Transfer" payment methods +Support for "memcached" PHP extension was added +Library js/scriptaculous/dragdrop.js is upgraded to version 1.9.0 +Image file of "jpg" type are allowed for favicon +Added ability to extend list of attributes to select for categories loaded via Mage_Catalog_Model_Resource_Category_Flat::_loadNodes() +Added changes to lib/Varien/Http/Adapter/Curl.php to provide interface for setting different cURL options +Displaying State or Province can be optional for any country +Added ability to get Magento type from Mage.php + +=== Fixes === +Fixed Multiple warnings in system.log after running compilation process +Fixed Session is lost while redirecting from secure to unsecure URL +Fixed Redirect to base URL should consider full request URI string +Fixed Product name with <> processed incorrect during creation order in backend +Fixed FedEx SmartPost method doesn't appears in rate request +Fixed 3 and 4 digits CVV should be accepted for JCB cards +Fixed Google Checkout tax isn't applied to Bundle product +Fixed FPT is not considered by Google Checkout +Fixed An error occurred during second Customer authorization fail +Fixed Property declaration typo in Mage_Bundle_Adminhtml_Catalog_Product_Edit_Tab_Bundle_Option +Fixed Product is not shown in the Catalog when it is Out of Stock and Display Out of Stock Products = Yes +Fixed Impossible download downloadable product (problem with secure link) +Fixed Wrong letter case in class names may cause malfunction when Compiler is enabled +Fixed Incorrect invoice amount in order with FPT +Fixed Attributes not connected to any product of selected category display in layered navigation +Fixed No feedback on creating attribute set in IE9 and IE8 +Fixed Unexpected breaking import process leads to creation phantom data into database +Fixed Two of the same FedEx option show instead of one +Fixed Notification for Google contains link that cover large area and blocked user work +Fixed Possible to create user role with empty name if it starts with "less than" sign +Fixed Impossible to use Clear Shopping Cart button in IE7 +Fixed Impossible create product with one FPT price for all State/Province +Fixed 'Interval Division Limit' affects to global displaying of Layered Navigation +Fixed Shipping method must be recalculated on Order Review page if Transfer Shipping Options is enabled +Fixed Issue with different secure/unsecure URL cause session data lost +Fixed Can't save option Country in Store Information Tab +Fixed Rule date information has been missed after using Product Rule or Shopping Cart Price Rule +Fixed Billing/Shipping address algorithm for PayPal Express checkout works incorrect +Fixed Error during quick search +Fixed Block "Description" on Catalog Price Rule page has incorrect size +Fixed Search doesn't work if Maximum Query Length field is empty +Fixed The Tags Product RSS doesn't update information after changing tag +Fixed Customer Attributes and Customer Address Attributes validation +Fixed Issue with final price calculation for Configurable product with sub products +Fixed Blank page after customer registration with enabled compilation +Fixed "Place Order" button must be enabled if all required fields passed the validation on PayPal Express Checkout +Fixed Products quantity displays incorrect in price ranges after import (rounding problem) +Fixed Layout issue in shopping cart on Frontend (IE8) +Fixed Impossible to assign user to the role if he is assigned to another role +Fixed 'Total records found' on Reports -> Tags -> Popular page shows wrong quantity of records +Fixed Unable to refresh lifetime statistics +Fixed After changing Price Navigation Step calculation from Automatic in Continuos the category is loaded very slowly +Fixed No ability to create Shipping Label (in case with USPS First-Class Mail International Parcel method) +Fixed It is impossible to create Shipping Label for FedEx +Fixed Redirect Customer to Account Dashboard after Logging is in enabled and doesn't work for Wishlist +Fixed No ability to edit values using mass actions for product in IE8, IE9 +Fixed Wrong message during checkout process in Inline Translate mode +Fixed Default country is selected in Shipping Address during Admin order creation +Fixed The Static block widget doesn't displayed on the Product View Extra hint for bundle product +Fixed Bunch of W3C validation errors on frontend while using inline translate +Fixed Product's association to root category is lost after export/import +Fixed WYSIWYG Editor: Page is scrolled to the top after inserting variables +Fixed Incorrect Backup/Rollback message +Fixed FedEx Free shipping doesn't work correctly +Fixed Configurable product displaying double price when choosing option +Fixed Impossible to configure Admin User Emails for store view scope +Fixed Long payment method data is printed improperly in PDF invoice +Fixed Impossibility of changing the Rating Value title for store view in existing Rating Value with filled the Rating Value title for store view +Fixed Trademark symbol not showing up +Fixed Add "Delivery Option" for FedEx Configurations +Fixed Saving product takes long time +Fixed Notice message disappear after clearing cache +Fixed Products qty displays incorrect in price ranges after import (rounding problem) +Fixed Images in CSS fail when merging CSS files +Fixed Mass action update of any attributes resets multiselect attributes to NULL +Fixed Move CSS from Prototype Windows plug-in to the backend skin +Fixed DB adapter should check transaction level in case of DDL query +Fixed When Payment method additional info contains double quote it is displayed as '"' in invoice PDF +Fixed Incorrect Unit Price(Excl. Tax) in the Shopping Cart Grid after changing currency +Fixed Incorrect total quantity of records and pagination doesn't work on Reports->Reviews->Products Reviews page +Fixed Incorrect title of All Reviews for product page +Fixed Invalid message in shopping cart when trying to add products amount more than allowed +Fixed Layered Navigation: Icon "Previous" should be appear only on sub-intervals level +Fixed Processing error occurred when big numeric value is entered to an browser URL +Fixed Issue with credit memo for multiple bundled products (order status is Processing) +Fixed Customer group has to be emulated even if customer is sticky assigned to the group +Fixed "Customs Value" isn't represented in store base currency +Fixed Layered Navigation: After clicking on interval $0.00, should be filtered and displayed products with price 0.00 +Fixed Absence of redirecting to the grid page after saving Role/User +Fixed Incorrect logic of assignment of unique ID in Varien_Data_Collection +Fixed Problems with sorting actions +Fixed When product is unsaleable checkout is still possible depending on its position in cart +Fixed Impossibility of creating new order with "Reorder" button when Out of stock or disabled products had been ordered +Fixed "Online Minute Interval" customer configuration option should have global scope +Fixed Unable to translate Active/Inactive in promotions grid +Fixed Response headers contains 500 error during front-end pages browsing +Fixed Add additional button to PayPal Redirect Page +Fixed Category Permissions: if "Display Product Price" is set to "No" the page toolbar is absent +Fixed Layered navigation options have wrong order in back-end +Fixed The "Remember Me" check box with the "What's this?" link should be located below the "Forgot Your Password?" link +Fixed Warning message is absent, when "Recovery Link Expiration Period" is specified within the correct range +Fixed PayPal HSS (Website Payment Pro Hosted Solutions) France doesn't work +Fixed Remove email from Billing address section of the PayPal Express Order Review page +Fixed It is available enter negative digits in the "Layered Navigation Price Step" (on Category page) +Fixed First/Last Name algorithm for PayPal Express checkout +Fixed Asterisk isn't imported in Zip/Post Code field +Fixed Some strings are not translated in widgets +Fixed Session isn't stored between secure and usecure URLs when they are located in different paths of the same domain (no SID in request) +Fixed "USPS First Class International Parcel" will not show up as an option for customers during checkout +Fixed UPS, FedEx and DHL methods should work if zip code from / to isn't required +Fixed Issue when trying to create an order from the back-end without selecting the state/province +Fixed HTTP error when uploading images from a MacOS with shockwave flash 11.1.102.55 +Fixed UPS Configuration "All Methods" should be selected by default +Fixed Cannot create a product review through back-end +Fixed Typos in Role Permission tree +Fixed WYSIWYG button layout issue on product page +Fixed Require Customer's Billing Address is missing option "For Virtual Quotes Only" +Fixed Errors appears on Design Configuration page if transactional email's logo image and/or favicon files were deleted +Fixed State/Province value is reset after page reload on some forms +Fixed Cannot access back-end after switching on and switching off "Use Custom Admin URL" +Fixed "Unsubscribe Selected" and "Delete Selected Problems" buttons don't work +Fixed After selecting shipping from dropdown system doesn't update order data automatically on PayPal Express Order Review page +Fixed Buttons on the back-end order page don't have titles +Fixed ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_DISPOSITION error in Chrome browser +Fixed Changes related with Apply and Discount Amount options for sub item, applied after clicking "Save Rule" button +Fixed Invalid Timezone error for Asia/Calcutta when changing default country to India +Fixed Sorting by position doesn't work for up-sells and related products +Fixed Absent validation for "Only X left Threshold" field +Fixed Shipping method calculation based on default shipping address instead of 'Same as billing' setting in back-end +Fixed There is no validation of the field "Handling Fee" that allows number less than zero in UPS shipping method +Fixed Product name with "<>" processed incorrect during creation order in back-end +Fixed Billing Address Line is always blank if "REQUIREBILLING = 1" for PayPal Express +Fixed SQL query for getting record count of archived orders takes too long time +Fixed Configuration->Inventory->Qty Increment isn't validated properly +Fixed Absent message about not enough quantity for bundle and configurable products +Fixed Wrong behaviour of split buttons in IE8 +Fixed Warning message appears after unselecting user in the Role Users grid +Fixed Incorrect total weight calculation in external shipping methods for products with decimal Qty Increments +Fixed Tax calculation is incorrect if configurable product mixed up with other composite products in the shopping cart for Store Tax != Customer Tax +Fixed Products qty displays wrong in layered navigation after changing currency +Fixed Billing address fields are editable if "Same as shipping" selected on the PayPal Express order review page +Fixed Incorrect location of "Clear All" link +Fixed Incorrect price for bundle fixed product with custom option % and catalog price rule applied +Fixed Import/Export: Append Complex Data works incorrect for customer's address and product's customs data +Fixed Discount changes subtotal when FPT is active +Fixed There is no ability to specify backup's name +Fixed Multi selections fields for website scope settings are greyed out +Fixed "Credit Memos" is written incorrectly in Role Permission tree and on the Sales -> Archive -> Credit Memos page +Fixed Persistent Shopping Cart: After deleting customers via Back-end, on front-end customer should be logout completely +Fixed Incorrect logic during dividing products into multiple boxes for shipping +Fixed Polls are not working properly in case with different domains for http and https +Fixed "Block Reference" drop-down contains wrong list of options for front-end Apps types with Products +Fixed "Wrong store specified" appears on order creation page +Fixed Customer can't continue Checkout process after selecting Billing Address +Fixed Refresh Statistics gone from Reports Role +Fixed USPS shipping label is printed with "SAMPLE - DO NOT MAIL" sign +Fixed Shipping methods are not refreshed after Update Order Data is pressed on PayPal Express Order Review page +Fixed Product still invisible in front-end after required reindex +Fixed JS error on configurable products +Fixed Configurable product missing name in error message when exceeding quantity during order +Fixed Added Display Product Count on the Layout Navigation +Fixed "FedEx Priority Overnight" shipping method isn't calculated correctly +Fixed Incorrect product price for Bundle products with fixed prices in the shopping cart +Fixed "Ship Bundle Items" for bundle product works incorrect +Fixed Impossible to expand settings accordions on the "Design Settings Editor" tab of Theme Customization page under IE7 +Fixed Impossibility to configure Bundle product with Disabled status and create new order with it in the back-end +Fixed Customer email isn't saved in Account Information field +Fixed Unable to sort products by price +Fixed Errors during creating/extracting "tar" archive with symbolic links +Fixed Wrong message text in "Manage Coupon" tab on Shopping Cart Price Rule page +Fixed 404 page not found error occurs when "Default Store" value is changed for Main Website +Fixed Unable to import products if Catalog price rule enabled +Fixed Partial Reindex isn't done for product saved in back-end +Fixed Catalog Price Rule: "Save and Apply" action leads to apply ALL rules, but it have to apply only specified rule +Fixed Tax isn't recalculated on PayPal Express +Fixed Backup Name field should allow to enter only a limited number of characters +Fixed In the "Subtotal" row of "Coupons Usage Report" displayed amounts for all Shopping Cart Price Rule in the system +Fixed Shipping address isn't passed to Magento from PayPal +Fixed Store view isn't changed when customer subscribes for newsletters +Fixed Shopping Cart Price Rules are not marked after creating Coupons Usage Report +Fixed Change buttons structure in prototype/windows.js to match general adminhtml buttons structure +Fixed Wrong error message on Create New Order page in back-end +Fixed Impossible to configure order of displaying rating values in the front-end +Fixed Incoming Message in Admin Part has the superfluous symbol '\' +Fixed Numerous issues with promotions on complex products +Fixed Recipient Postal-State Mismatch error seen on Shipping Label with FedEx +Fixed Text messages "This is a required fields." are displayed in the hidden FTP section +Fixed Incorrect popup on Manage Coupon Codes tab in Shopping Cart Price Rule +Fixed Layered navigation work incorrect if attribute values defined on Store View level +Fixed Checking import file returns blank page +Fixed Change the VAT Number format before sending to VAT ID Validation service +Fixed Simple products with configured customs options displayed in wishlist incorrectly +Fixed Incorrect text message for product for which there is no enough quantity in stock +Fixed Setting float Qty Increments is possible, but doesn't work +Fixed SQL error during checkout when customers register at checkout and orders a nominal item +Fixed Iframe for gateway isn't reloaded on the Payment information tab +Fixed Incorrect behavior after placing order from back-end in FF9 +Fixed Catalog price rule skips conditions specified and applies to all products in the catalog +Fixed Price rule is applying to individual items in bundle when price is set to Dynamic +Fixed Impossible to save payment method configurations on the Default Config scope +Fixed "There has been an error processing your request" message is display if not CSV file was selected to import +Fixed Wrong reindex product attributes after bundle product save +Fixed Add Products button absent during creating order from back-end +Fixed "All methods" should be selected by default in configuration section "Allowed Methods" for UPS +Fixed Redirect to base URL issue +Fixed Added Backup sorting by name possibility +Fixed Fix grammar mistakes +Fixed Notifications are not shown if URL Rewrite used +Fixed "Stop Further Rules Processing" option doesn't work +Fixed Admin can not unassign product from the tag if already approved tag was added to the product by customer +Fixed Inaccuracy calculation could be 10% for FedEx International Ground shipping +Fixed Incorrect price calculation of configurable product with custom options (resolved conflicts) +Fixed Wrong currency displayed in Recently Viewed Product App +Fixed Tabs are grayed on admin dashboard +Fixed JS validation for product weight attribute doesn't work +Fixed Group Price attribute is present as text field using the mass update action +Fixed Shopping Cart Price Rule isn't applied to Not Logged In Customers +Fixed "Sign up for our newsletter" text appearing twice +Fixed Newsletter problem report grid on back-end throws Exception +Fixed Special price doesn't work for Bundles with Dynamic price +Fixed My Orders block disappears in My Account pages when Reorder functionality is disabled +Fixed Search doesn't work in Backups grid +Fixed Reindex "Catalog URL Rewrites" works extremely slowly +Fixed Checkbox state is preserved for "Put store on the maintenance mode while rollback processing" +Fixed UPS Configuration All Methods Should Be Selected by Default +Fixed Rollback fails if database backup was performed after product import +Fixed Incorrect behavior with 10-digit Zip code, after creating new Tax Rate +Fixed Configurable attributes that used for create configurable product should not be applied to that product +Fixed Default group has to be used if customer selects address without VAT number +Fixed No ability to create Shopping Cart Price Rule +Fixed Layout issue appears in IE9 on the export grids +Fixed Issue when trying to view the order using a specific admin user +Fixed System messages are not displayed at CMS pages and appears only when another message will be invoked +Fixed On the front-end Search doesn't work properly if search value is 0 (null) +Fixed The size of the columns in Backup grid is changed if no records were found +Fixed Maintenance flag isn't deleted if rollback fails with not enough permissions error +Fixed Customer's group isn't changed if his billing address modified within back-end +Added Add a message and the link in the mini shopping cart, when the cart is empty +Fixed On Edit Shipping Address page button "Validate VAT Number" should be hidden +Fixed Fatal error when try to ship order with Flat Rate shipping method +- fixed potentially problematic chaining involved getShippingCarrier method +Fixed No ability to open PDF file with Label +Fixed Incorrect final price for configurable products if several custom options used +Fixed "Length", "Width" and "Height" fields on "Create Packages" pop up are active, when "Documents" type is selected in IE7 and IE9 +Fixed HTTP 500 error on front-end for bundle fixed with percent options enabled for sub-products +Fixed Shipment created on Magento side doesn't send to Google side for Merchant Calculated shipping +- added check for process only Google Checkout internal methods +Fixed PDF files for invoices and credit memos are not displaying Including Tax Price +Fixed Default value that was specified in system settings doesn't presented in Code Format drop-down on Manage Coupon Codes tab +Fixed Qty wipes out to 0 when no qty column is included on import +Fixed Broken controls makeup is observed after resizing window when customer's page is opened on the back-end +Fixed Values don't fit to "Date Fields Order" drop-downs in "Date & Time Custom Options" on the Catalog page +Fixed Processing error appears for products with "Qty Uses Decimals" = No and enabled DHL International +Fixed Apply Tax to FPT setting doesn't seem to work for products with Fixed Product Taxes +Fixed Apply Coupon Code textbox doesn't fit in the Apply Coupon Code channel on the back-end after reducing the browser window +Fixed Tax and Shipping amounts aren't showing on the merchant reports for Websites Payments Pro PayFlow Edition +Fixed Incorrect error popup on Manage Coupon Codes tab in Shopping Cart Price Rule +Fixed No ability to create Shipping Label with New DHL shipping methods +Fixed Indexing update on save takes too long for large catalog_product_entity_int tables +Fixed VAT ID group validation takes Default configuration on order creation from back-end +Fixed Pagination isn't shown on My Product Reviews page when items count exceeds the "Show per page" setting +Fixed Inline translation missing for customer account information labels +Fixed Corrupted text if drag attribute on Manage Attribute Sets page (IE8) +Fixed Wrong calculation price of Bundle product with Fixed price, when special price is configured +Fixed Error Message isn't displayed during unsuccessful Shipping Label creation +Fixed Unverified redirect is possible in Checkout controller +Fixed Customer group in not validated again on checkout if Tax Calculation Based on = Shipping Address +Fixed "Clear All" link doesn't work +Fixed Wrong message appears when products quantity is updated in the Shopping Cart with enabled Qty Increments setting +Fixed Created On field on Manage Coupon Codes grid shows incorrect date/time +Fixed Incorrect price calculation of configurable product with custom options +Fixed Manage Products > Custom options: Should be possible to enter negative price for custom option of 'Date' type +Fixed HTTP 500 Internal Server Error on Admin Forgot Password page +Fixed CSV/Excel XML export doesn't work on Sales->Invoices if filter by Selected=Yes is used +Fixed "Same As Billing Address" check-box doesn't work +Fixed Impossible to enter zero in the base price field for customer groups +Fixed Group Price attribute position on the Prices tab is incorrect +Fixed Add new column to the grid with number of used coupons +Fixed Custom options are not stored when downloadable product is duplicated +Fixed Broken logic for "Zero Subtotal Checkout" order statuses +Fixed Coupon codes generation fails when trying to generate large amount of codes +Fixed PayFlow Link: Using "Pay with PayPal" and selected shipping method that is greater than 0 doesn't process order +Fixed User have to stay on Add New Rule page if error appears on save shopping cart price rule +Fixed "Automatically Invoice All Items" should be inactive, when were selected "New Order Status: Pending" in "Zero Subtotal Checkout" settings +Fixed When enormous request in search fields on the front end 414 error appears +Fixed JS error on edit Shopping Cart Price Rule Page +Fixed Clear Shopping Cart button add selected item to Items Ordered if check box "Add to Order" is selected +Fixed "Clear Shopping Cart" button must be located to the left to "Update Shopping Cart" button +Fixed Export of Group Price data doesn't work +Fixed Incorrect translation messages definitions +Fixed Error message isn't displayed if currency exchange rate not found (in case with DHL Int) +Fixed User role with partial access can't edit attributes of configurable products +Fixed Letter "n" missed in the word "handling" +Fixed Buttons don't react for pressing on the "Widget Options" section in Insert Widget window +Fixed Regular price displays incorrect +Fixed {{base_url}} in (un)secure_url doesn't work since 1.6.1 +Fixed Product selection field gets cleared out with recently added products from latest page +Fixed "Change" button while checkout doesn't work +Fixed MySQL BIGINT field type is wrongly casted to integer +Fixed Magento Connect Manager proceed with installation of extension if error appears on database backup +Fixed "Set product as New from/to Date" works excluding selected dates +Fixed Function fireEvent from lib/varien/js.js does not work in IE9 +Fixed Searching with first and last name has no results +Fixed CMS Pages: Error in IE7 when select CMS page +Fixed White screen appears instead of 404 Error Page when going to review a product which doesn't exist +Fixed "Maximum Package Weight" option works incorrect in case with decimal Qty in shopping Cart +Fixed Unable to create tables for new EAV entity via SQL upgrade script +Fixed Customer group isn't revalidated on checkout if Enable Automatic Assignment to Customer Group = Yes +Fixed Mistake in PayPal Payments Advanced fieldset title +Fixed Zero Subtotal Checkout payment method is used, when it is disabled in settings +Fixed Some info lost from address when customer sets this address as default for shipping +Fixed Incorrect calculation logic during distribution products between several pieces (in case with DHL) +Fixed No ability to get shipping rates from US to another country (in case with DHL) +Fixed Incorrect calculation of pieces weight (in case with Bundle Product) +Fixed Product Flat Data index causes replication lag on MySQL master/slave model +Fixed Exception is shown, when admin user provides filtration of Newsletter problem reports by Subscriber +Fixed Typo in JavaScript error message +Fixed Unable to do mass action update for products +Fixed Error Message isn't displayed if currency exchange rate not found (in case with DHL) +Fixed Weight field is absent during Quick simple product creation +Fixed correct helper resolving +Fixed Shipping methods are shown twice in DHLs shipping quote +Fixed Unable to translate shipping and billing forms on the order creation page +Fixed Drop-down attribute with layered navigation filter doesn't work with value is set to 0 +Fixed Free Shipping options doesn't work (in case with DHL) +Fixed Handling Fee doesn't applied Per Package +Fixed Free Shipping options works incorrect +Fixed WYSIWYG Editor: It's impossible insert Widget to CMS page content +Fixed Customer's group is not changed if his billing address modified within backend +Fixed Wrong behaviour and exception while using invalid image +Fixed Uninformative error message during saving two nodes with the same parameter URL Key +Fixed Unable to change order addresses in the admin panel +Fixed PayPal Express always uses default billing address from customer account +Fixed Unable to place order if customer selects Register on checkout +Fixed Performance Issue: Most Viewed product reports on large amount of items +Fixed In "Customer Addresses" block before and after of State name is located symbols "," +Fixed Lightbox 2.5 with IE7 returns JS error on the page +Fixed Unable to change customer status (confirmed/not confirmed) when customer logged in +Fixed Incorrect notification for empty field during creation catalog price rules +Fixed Unable to save product with non-checked multiple select attribute +Fixed Package Size setting for DHL +Fixed No Input Validation for Catalog Fields +Fixed Row Total Calculation in Refund +Fixed "Maximum number of price intervals" should be written with capital letters +Fixed Divide Order Weight options for DHL +Fixed Impossible to create new customer in the backend +Fixed Catalog data-upgrade-1.6.0.0.4-1.6.0.0.5.php is exteremly slow +Fixed Free Shipping options doesn't work (in case with DHL International) +Fixed Mage_Catalog_Helper_Product::getProduct() doesn't load product by its SKU +Fixed Preview Template doesn't work correctly +Fixed Some options of Bundle Product disappeared from the Invoice PDF +Fixed "Allow Countries" affects on "Country of Manufacture" attribute +Fixed Some phrases are not translated +Fixed Incorrect Ordered Qty in Order (in case with decimal qty) +Fixed Trademark character isn't being displayed properly in the PDF invoice +Fixed Can't search transactions by order_id in manager.paypal.com +Fixed Inline Translation: Grid headers are displayed incorrect on the Tag Edit page +Fixed "Create Extension Package" page becomes broken after compilation +Fixed Price including tax isn't displayed for some kinds of bundle products +Fixed Layered navigation for prices displays incorrect price ranges in manual mode +Fixed Pager works wrong with float page number +Fixed Incorrect foreign key for EAV entity tables +Fixed Misprint in downloader/lib/Mage/Connect/Command/Install.php +Fixed URL Rewrites must be case-sensitive +Fixed Unable to install package via uploader if author name contains dash +Fixed Fixed invoice subtotals for cases with partial invoice and discount +Fixed Catalog URL Rewrites works incorrectly on creating categories +Fixed New Oder Status setting, specified for payment method works incorrectly when only virtual products are present in Order +Fixed Rounding issue in catalog and product view if price includes tax +Fixed Wrong status of catalog event is displayed by editing catalog event +Fixed Role Resources are not saved +Fixed "Qty for Item's Status to Become Out of Stock" option works incorrect +Fixed XML parser works incorrect +Fixed Mage_Reports_Model_Mysql4_Product_Index_Abstract must be declared abstract +Fixed "Date & Time" and "Time" custom options becomes required when editing product +Fixed Unable to cancel an order for an expired Authorize.net auth +Fixed Custom options are not stored when downloadable product is duplicated +Fixed "Cannot initialize the indexer process" error during Mass "Reindex Data" Action +Fixed Search by new attribute fails, attribute is not shown in layered navigation +Fixed Exception when "Price Navigation Step Calculation" set to "Manual" mode and FPT enabled +Fixed WPPHS: Cancel URL doesn't work as should be +Fixed Error about insufficient permissions is not appears on database backup creation +Fixed After rollback admin doesn't redirected to the Log in to Admin Panel page +Fixed Database Backup must not include indexer table data +Fixed Scheduled Backup creation/failure isn't logged +Fixed Deleting backup while it is used by another process +Fixed Opening *.tar files causes an error "There are no trailing zero-filled records" +Fixed Unable to search by "Time" and "No" in Backups table +Fixed Backups are deleted without confirmation +Fixed Reports must be excluded from database snapshot backup +Fixed There are no products in filtering results, if step calculation in automatic mode +Fixed No validation for "Default Price Navigation Step" +Fixed "Back" button doesn't work on the Create New Order page +Fixed Incorrect logic of Token expiration in Two Step Password Reset flow +Fixed Tag
is present in tool-tip for field "Number of results (For the last time placed)" on the Edit Search page +Fixed Unnecessary hard code in Magento Extension +Fixed Wrong logic in Mage_Core_Model_Resource_Db_Collection_Abstract::join() +Fixed Description and Short Description are displayed incorrectly for products added with WYSIWYG +Fixed Adding product to the cart from the product review page leads to 404 page +Fixed Special symbols in Sort Order field +Fixed Text is wrong displayed with enabled Inline translation for Admin on back-end +Fixed Inline Translation: Unable to translate some customer information +Fixed Useless colon on front-end login page +Fixed Unable to continue checkout for product with zero price and non-zero shipping price +Fixed Import news_from_date field is configured poorly. It is not accepting the data from file +Fixed When updating product data through import, attributes that have a value cannot be assigned a new value that is empty +Fixed Unable to replace non-complex data for products with empty values during import +Fixed "Wrong order ID" exception in PayPal Express module under heavy load +Fixed Tax is applying on the order when creating it in the admin panel for a Customer Group with no taxes +Fixed Issue with retrieving order statuses for array of states +Fixed Wrong calculation product price with custom option type = Field and Fixed price +Fixed Back ordered downloadable product is not available even when it is set to be be accessible when order status is Pending +Fixed Missing column "position" at table catalog_category_anc_products_index_tmp +Fixed Incorrect behavior of "Save in address book" option during admin Order creation for a new customer +- refactored Mage_Adminhtml_Model_Sales_Order_Create::_prepareCustomer() +Fixed Terms and Conditions is named differently on different pages +Fixed "Apply" and "Discount Amount" fields appear twice in the Catalog Price Rule +Fixed Poll shows incorrect percentage +Fixed Added validation ability for admin configuration forms +Fixed UPS XML misprint +Fixed Misprint in uploading files form +Fixed Unnecessary check boxes for gift options +Fixed Wrong resource section declaration in Mage_Tag module +Fixed "Customers Submitted this Tag" section doesn't update when customer has deleted tag from his account +Fixed Correct product in category position +Fixed Unable to create folder in Media Storage +Fixed Translation with single quotes breaks JavaScript +Fixed Out of memory error with hundreds of thousands of coupons attached to a single sales rule +Fixed Unable to translate buttons and tabs on the "Manage Category" page +Fixed Product Categories Tree doesn't expand in Manage Products page +Fixed Incorrect products qty returns to stock after refund for configurable product +Fixed Swf Uploader problems with cross domain Flash Player Policy +Fixed Unable to translate "Layout Updates" block on create/edit widget page +Fixed IE7: "Remember Me" checkbox visible on billing information step +Fixed CMS WYSIWYG Editor - widget is inserted as new while editing in IE8 +Fixed Currency code doesn't correspond to the amount in reports +Fixed Mage_Adminhtml_Block_Sales_Order_View_Tab_History::getFullHistory() doesn't use unique keys for each message +Fixed Scope labels are shown without translate wrapper +Fixed Wrong parameters handling in Core Helper formatDate() +Fixed Apostrophe in store name breaks Google Analytics tracking +Fixed Customer attribute prefix is not shown on frontend +Fixed Incorrect rounding for product with custom options (percent price) +Fixed Invoicing only part of products results in wrong totals calculation +Fixed Incorrect Row Total Calculation in Refund +- fixed rounding issues for partial Invoice and Refund +- refactored delta rounding +Fixed Filter by "Allow Countries" not working for Customer Address Form in the Backend +- checking added for set country to be in available list +Fixed There are sql-installs with empty string used as defaults for table columns, while column is not null able +Fixed Unable to translate "Note" in "Product Stock Options" +Fixed Various warning are displayed after creating shipment for 0 items +Fixed Invalid content in Content-header in the top of page during scrolling during order creation +Fixed "Online invoice" button present in backend when using Zero Subtotal Checkout +Fixed String cast type in in_array function +Fixed Newsletter template content should not disappear when "Show" / "Hide Editor" button was clicked +Fixed Import feature doesn't validate whether super_products_sku is existing or not +Fixed Cannot place order with downloadable product and discount code using Paypal Express payment method +Fixed Product still out of stock after Stock Status reindex +Fixed Save catalog price rule gives trace if full reindex has already started +Fixed Reindex requires notification is not shown for Stock Status when stock is updated for several products using Mass Action +Fixed Incorrect FedEx's shipping rates (in case with non-US country origin) +Fixed After partial reindex MSRP value is not applied (not viewed) in catalog during mass update action +Fixed Wrong stock reindex on catalog if partial reindex done after full reindex started +Fixed In error message string "already exists." written twice, when trying to save Product Tax Class with the same name +Fixed Slow checkout with non-flushed cache +Fixed Bundle product total is incorrect in Customization block +Fixed Special price isn't considered for bundle dynamic products in "Your Customization" block +Fixed Situation when any amount of duplicate reindex process can be running at one time +Fixed Error with Advanced Search (in case with Date Attribute) +Fixed Product Flat Data index isn't marked as "Reindex Required" after importing products when Flat Catalog is enabled +Fixed User can't change root category for the store +Fixed JS error causes the overwrite of Title field in PayPal Advanced configuration +Fixed Mass action doesn't partially reindex catalog for product name/description +Fixed Remove hint about $1 auth amount from informational message text (PayflowLink configuration) +Fixed Mass action doesn't partially reindex catalog for product prices +Fixed PayPal Payments Advanced works with $0 Auth instead of Full Auth +Fixed Impossible to place Order using "Pay with PayPal" button from PayPal Payments Advanced iframe +Fixed Mass action doesn't partially reindex catalog search for product if searchable attribute was updated +Fixed "Please wait" AJAX screen doesn't appear in the middle of the page +Fixed Checkout link on frontend is always referenced as http +Fixed GET request is incorrectly formed during category creation +- adjusted assertion to determine last viewed store +Fixed Display of Tier Pricing with Configurable Products +- added functionality to dynamically update tier prices accordingly to chosen product configuration +Fixed Google Analytics e-commerce tracking not working +Fixed URL key isn't used while product save +Fixed Added validation class to 'Qty increments' +Fixed Entered from admin customer date 1970 (or less) is saved as 2070 (or less) +Fixed cUrl resource must be closed after checking it for errors, not before +Fixed Exception while products mass update attributes in backend +Fixed No ability to reindex Catalog URL Rewrites, error is shown +Fixed Package with Core dependency +Fixed Stock Availability isn't updated if 1: Run Price Reindex 2: Update Stock Availability on product with mass action/single product +Fixed Blank page instead shopping cart page when compilation and PSC are enabled +Fixed Unable to save redirect URL with special characters in search terms +Fixed Attribute Set field should have client-side validation and appropriate information message +Fixed "Localhost" isn't a valid domain name for installation +Fixed Iframe for PayPal Payments Advanced is not loaded +Fixed Retain the selected tab on editing CMS page +Fixed Payflow Link Express Checkout (pay with PayPal button) payment +Fixed Wrong number of reindex options is displayed +Fixed Wrong phpDocs for Varien_Db_Select +Fixed JavaScript calendar date range +- fixed JS calendar behavior to use 4-digits year format +Fixed Performance issue connected with sales rules on adding product to cart +Fixed DHL same error message appears several times +Fixed Item Status says "Mixed" when an order has been completed, should say "Shipped" +Fixed Product option title is absent in backend order page +Fixed Incorrect items number during multi shipping checkout +Fixed User name displays differently in the unsuccessful message and in the text field label (log in form) +Fixed If one or more indexers have Update Required = Yes and all Status = READY for all indexers than there is no notification for user to do reindex action +Fixed No space between Address line 1 and line 2 in Shipping Label (in case with FedEx) +Fixed JS works depends on the position attributes of the product +Fixed Char set encoding is out-of-date in Settlement reports +Fixed Settlement reports can't be downloaded if in merchant's account 'Settlement file' is set to 6.0 version +Fixed Unable to login when secure and unsecure URLs are different +Fixed Customer session lost when using different domain/subdomain names for secure and unsecure URLs +Fixed "Most Viewed" reports ignore Store View switcher +Fixed Long FPT name (and product name) doesn't fit into "My cart" block +Fixed Paypal IPN post back failure +Fixed Customer was unable to receive newsletters when it was created via backend +Fixed Wrong Comments History in notification of order creation/cancellation +Fixed Non escaped string causes javascript error +Fixed Unable to view pictures during product editing +Fixed Ampersand is saved incorrect in attribute label +Fixed IE8: JS error appears after pressing 'Add new rule' in catalog price rules menu +Fixed Exception after sorting newsletter queue +Fixed Customer is not able to log in from URL without "www" in some cases +Fixed SQL error on category view with enabled and configured FPT +Fixed Automatic reindexing based on matched events doesn't change "Status" and "Last Run" columns at process list grid +Fixed Performance issue with Magento Compiler + APC results in too many I/O calls +Fixed Website Administrator is able to change default values +Fixed Some of wishlist blocks and templates still treat the collection of wishlist items as collection of products +Fixed Unnecessary comments in "Share Wishlist" email + + + ==== 1.7.0.0-beta1 ==== === Improvements === diff --git a/api.php b/api.php new file mode 100644 index 0000000000..6430d5cceb --- /dev/null +++ b/api.php @@ -0,0 +1,88 @@ +loadAreaPart(Mage_Core_Model_App_Area::AREA_GLOBAL, Mage_Core_Model_App_Area::PART_EVENTS); +Mage::app()->loadAreaPart(Mage_Core_Model_App_Area::AREA_ADMINHTML, Mage_Core_Model_App_Area::PART_EVENTS); + +// query parameter "type" is set by .htaccess rewrite rule +$apiAlias = Mage::app()->getRequest()->getParam('type'); + +// check request could be processed by API2 +if (in_array($apiAlias, Mage_Api2_Model_Server::getApiTypes())) { + /** @var $server Mage_Api2_Model_Server */ + $server = Mage::getSingleton('api2/server'); + + $server->run(); +} else { + /* @var $server Mage_Api_Model_Server */ + $server = Mage::getSingleton('api/server'); + $adapterCode = $server->getAdapterCodeByAlias($apiAlias); + + // if no adapters found in aliases - find it by default, by code + if (null === $adapterCode) { + $adapterCode = $apiAlias; + } + try { + $server->initialize($adapterCode); + $server->run(); + + Mage::app()->getResponse()->sendResponse(); + } catch (Exception $e) { + Mage::logException($e); + + echo $e->getMessage(); + exit; + } +} diff --git a/app/Mage.php b/app/Mage.php index 1b6fb4ffd5..8736004b52 100644 --- a/app/Mage.php +++ b/app/Mage.php @@ -171,7 +171,7 @@ public static function getVersionInfo() 'minor' => '7', 'revision' => '0', 'patch' => '0', - 'stability' => 'beta', + 'stability' => 'rc', 'number' => '1', ); } @@ -373,6 +373,7 @@ public static function getStoreConfigFlag($path, $store = null) * Get base URL path by type * * @param string $type + * @param null|bool $secure * @return string */ public static function getBaseUrl($type = Mage_Core_Model_Store::URL_TYPE_LINK, $secure = null) @@ -434,17 +435,16 @@ public static function addObserver($eventName, $callback, $data = array(), $obse * Dispatch event * * Calls all observer callbacks registered for this event - * and multiobservers matching event name pattern + * and multiple observers matching event name pattern * * @param string $name - * @param array $args + * @param array $data * @return Mage_Core_Model_App */ public static function dispatchEvent($name, array $data = array()) { Varien_Profiler::start('DISPATCH EVENT:'.$name); $result = self::app()->dispatchEvent($name, $data); - #$result = self::registry('events')->dispatch($name, $data); Varien_Profiler::stop('DISPATCH EVENT:'.$name); return $result; } @@ -455,7 +455,7 @@ public static function dispatchEvent($name, array $data = array()) * @link Mage_Core_Model_Config::getModelInstance * @param string $modelClass * @param array|object $arguments - * @return Mage_Core_Model_Abstract + * @return Mage_Core_Model_Abstract|false */ public static function getModel($modelClass = '', $arguments = array()) { @@ -584,6 +584,7 @@ public static function exception($module = 'Mage_Core', $message = '', $code = 0 * * @param string $message * @param string $messageStorage + * @throws Mage_Core_Exception */ public static function throwException($message, $messageStorage = null) { diff --git a/app/code/core/Mage/Admin/Model/User.php b/app/code/core/Mage/Admin/Model/User.php index 6be88b6db4..34d8b355c3 100644 --- a/app/code/core/Mage/Admin/Model/User.php +++ b/app/code/core/Mage/Admin/Model/User.php @@ -61,7 +61,7 @@ class Mage_Admin_Model_User extends Mage_Core_Model_Abstract { /** - * Configuration pathes for email templates and identities + * Configuration paths for email templates and identities */ const XML_PATH_FORGOT_EMAIL_TEMPLATE = 'admin/emails/forgot_email_template'; const XML_PATH_FORGOT_EMAIL_IDENTITY = 'admin/emails/forgot_email_identity'; diff --git a/app/code/core/Mage/AdminNotification/Model/Inbox.php b/app/code/core/Mage/AdminNotification/Model/Inbox.php index ba19c9865e..11fac2ac94 100644 --- a/app/code/core/Mage/AdminNotification/Model/Inbox.php +++ b/app/code/core/Mage/AdminNotification/Model/Inbox.php @@ -117,4 +117,94 @@ public function parse(array $data) { return $this->getResource()->parse($this, $data); } + + /** + * Add new message + * + * @param int $severity + * @param string $title + * @param string|array $description + * @param string $url + * @param bool $isInternal + * @return Mage_AdminNotification_Model_Inbox + */ + public function add($severity, $title, $description, $url = '', $isInternal = true) + { + if (!$this->getSeverities($severity)) { + Mage::throwException($this->__('Wrong message type')); + } + if (is_array($description)) { + $description = ''; + } + $date = date('Y-m-d H:i:s'); + $this->parse(array(array( + 'severity' => $severity, + 'date_added' => $date, + 'title' => $title, + 'description' => $description, + 'url' => $url, + 'internal' => $isInternal + ))); + return $this; + } + + /** + * Add critical severity message + * + * @param string $title + * @param string|array $description + * @param string $url + * @param bool $isInternal + * @return Mage_AdminNotification_Model_Inbox + */ + public function addCritical($title, $description, $url = '', $isInternal = true) + { + $this->add(self::SEVERITY_CRITICAL, $title, $description, $url, $isInternal); + return $this; + } + + /** + * Add major severity message + * + * @param string $title + * @param string|array $description + * @param string $url + * @param bool $isInternal + * @return Mage_AdminNotification_Model_Inbox + */ + public function addMajor($title, $description, $url = '', $isInternal = true) + { + $this->add(self::SEVERITY_MAJOR, $title, $description, $url, $isInternal); + return $this; + } + + /** + * Add minor severity message + * + * @param string $title + * @param string|array $description + * @param string $url + * @param bool $isInternal + * @return Mage_AdminNotification_Model_Inbox + */ + public function addMinor($title, $description, $url = '', $isInternal = true) + { + $this->add(self::SEVERITY_MINOR, $title, $description, $url, $isInternal); + return $this; + } + + /** + * Add notice + * + * @param string $title + * @param string|array $description + * @param string $url + * @param bool $isInternal + * @return Mage_AdminNotification_Model_Inbox + */ + public function addNotice($title, $description, $url = '', $isInternal = true) + { + $this->add(self::SEVERITY_NOTICE, $title, $description, $url, $isInternal); + return $this; + } } diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Attribute/Set/Toolbar/Add.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Attribute/Set/Toolbar/Add.php index bd4cc114ab..efbfbebd08 100644 --- a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Attribute/Set/Toolbar/Add.php +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Attribute/Set/Toolbar/Add.php @@ -44,7 +44,7 @@ protected function _prepareLayout() $this->getLayout()->createBlock('adminhtml/widget_button') ->setData(array( 'label' => Mage::helper('catalog')->__('Save Attribute Set'), - 'onclick' => 'addSet.submit();', + 'onclick' => 'if (addSet.submit()) disableElements(\'save\');', 'class' => 'save' ))); $this->setChild('back_button', diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Inventory.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Inventory.php index 100e6bb13e..c39212282d 100644 --- a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Inventory.php +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Inventory.php @@ -142,4 +142,14 @@ public function canUseQtyDecimals() { return $this->getProduct()->getTypeInstance()->canUseQtyDecimals(); } + + /** + * Check if product type is virtual + * + * @return boolean + */ + public function isVirtual() + { + return $this->getProduct()->getTypeInstance()->isVirtual(); + } } diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Search/Edit/Form.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Search/Edit/Form.php index 6f72f49cc9..b884dfc7bd 100644 --- a/app/code/core/Mage/Adminhtml/Block/Catalog/Search/Edit/Form.php +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Search/Edit/Form.php @@ -87,14 +87,15 @@ protected function _prepareForm() )); if (!Mage::app()->isSingleStoreMode()) { - $fieldset->addField('store_id', 'select', array( + $field = $fieldset->addField('store_id', 'select', array( 'name' => 'store_id', 'label' => Mage::helper('catalog')->__('Store'), 'title' => Mage::helper('catalog')->__('Store'), 'values' => Mage::getSingleton('adminhtml/system_store')->getStoreValuesForForm(true, false), 'required' => true, - 'after_element_html' => Mage::getBlockSingleton('adminhtml/store_switcher')->getHintHtml() )); + $renderer = $this->getLayout()->createBlock('adminhtml/store_switcher_form_renderer_fieldset_element'); + $field->setRenderer($renderer); } else { $fieldset->addField('store_id', 'hidden', array( diff --git a/app/code/core/Mage/Adminhtml/Block/Checkout/Agreement/Edit/Form.php b/app/code/core/Mage/Adminhtml/Block/Checkout/Agreement/Edit/Form.php index d03d701663..22fa71d1c3 100644 --- a/app/code/core/Mage/Adminhtml/Block/Checkout/Agreement/Edit/Form.php +++ b/app/code/core/Mage/Adminhtml/Block/Checkout/Agreement/Edit/Form.php @@ -99,14 +99,15 @@ protected function _prepareForm() )); if (!Mage::app()->isSingleStoreMode()) { - $fieldset->addField('store_id', 'multiselect', array( + $field = $fieldset->addField('store_id', 'multiselect', array( 'name' => 'stores[]', 'label' => Mage::helper('checkout')->__('Store View'), 'title' => Mage::helper('checkout')->__('Store View'), 'required' => true, 'values' => Mage::getSingleton('adminhtml/system_store')->getStoreValuesForForm(false, true), - 'after_element_html' => Mage::getBlockSingleton('adminhtml/store_switcher')->getHintHtml() )); + $renderer = $this->getLayout()->createBlock('adminhtml/store_switcher_form_renderer_fieldset_element'); + $field->setRenderer($renderer); } else { $fieldset->addField('store_id', 'hidden', array( diff --git a/app/code/core/Mage/Adminhtml/Block/Cms/Block/Edit/Form.php b/app/code/core/Mage/Adminhtml/Block/Cms/Block/Edit/Form.php index 307613472b..e799445dc6 100644 --- a/app/code/core/Mage/Adminhtml/Block/Cms/Block/Edit/Form.php +++ b/app/code/core/Mage/Adminhtml/Block/Cms/Block/Edit/Form.php @@ -93,14 +93,15 @@ protected function _prepareForm() * Check is single store mode */ if (!Mage::app()->isSingleStoreMode()) { - $fieldset->addField('store_id', 'multiselect', array( + $field =$fieldset->addField('store_id', 'multiselect', array( 'name' => 'stores[]', 'label' => Mage::helper('cms')->__('Store View'), 'title' => Mage::helper('cms')->__('Store View'), 'required' => true, 'values' => Mage::getSingleton('adminhtml/system_store')->getStoreValuesForForm(false, true), - 'after_element_html' => Mage::getBlockSingleton('adminhtml/store_switcher')->getHintHtml() )); + $renderer = $this->getLayout()->createBlock('adminhtml/store_switcher_form_renderer_fieldset_element'); + $field->setRenderer($renderer); } else { $fieldset->addField('store_id', 'hidden', array( diff --git a/app/code/core/Mage/Adminhtml/Block/Cms/Page/Edit/Tab/Main.php b/app/code/core/Mage/Adminhtml/Block/Cms/Page/Edit/Tab/Main.php index 3b7e0ec7d5..ff19fd959e 100644 --- a/app/code/core/Mage/Adminhtml/Block/Cms/Page/Edit/Tab/Main.php +++ b/app/code/core/Mage/Adminhtml/Block/Cms/Page/Edit/Tab/Main.php @@ -85,15 +85,16 @@ protected function _prepareForm() * Check is single store mode */ if (!Mage::app()->isSingleStoreMode()) { - $fieldset->addField('store_id', 'multiselect', array( + $field = $fieldset->addField('store_id', 'multiselect', array( 'name' => 'stores[]', 'label' => Mage::helper('cms')->__('Store View'), 'title' => Mage::helper('cms')->__('Store View'), 'required' => true, 'values' => Mage::getSingleton('adminhtml/system_store')->getStoreValuesForForm(false, true), 'disabled' => $isElementDisabled, - 'after_element_html' => Mage::getBlockSingleton('adminhtml/store_switcher')->getHintHtml() )); + $renderer = $this->getLayout()->createBlock('adminhtml/store_switcher_form_renderer_fieldset_element'); + $field->setRenderer($renderer); } else { $fieldset->addField('store_id', 'hidden', array( 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 ade577960c..3446d8f4c2 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 @@ -110,9 +110,8 @@ function(v, elem){ . '' ); } - $form->getElement('website_id')->setAfterElementHtml( - Mage::getBlockSingleton('adminhtml/store_switcher')->getHintHtml() - ); + $renderer = $this->getLayout()->createBlock('adminhtml/store_switcher_form_renderer_fieldset_element'); + $form->getElement('website_id')->setRenderer($renderer); // if (Mage::app()->isSingleStoreMode()) { // $fieldset->removeField('website_id'); diff --git a/app/code/core/Mage/Adminhtml/Block/Newsletter/Queue/Edit/Form.php b/app/code/core/Mage/Adminhtml/Block/Newsletter/Queue/Edit/Form.php index 79cf05d384..344a90c7f5 100644 --- a/app/code/core/Mage/Adminhtml/Block/Newsletter/Queue/Edit/Form.php +++ b/app/code/core/Mage/Adminhtml/Block/Newsletter/Queue/Edit/Form.php @@ -36,9 +36,9 @@ class Mage_Adminhtml_Block_Newsletter_Queue_Edit_Form extends Mage_Adminhtml_Blo { /** * Prepare form for newsletter queue editing. - * Form can be run from newsletter template grid by option "Queue newsletter" + * Form can be run from newsletter template grid by option "Queue newsletter" * or from newsletter queue grid by edit option. - * + * * @param void * @return Mage_Adminhtml_Block_Newsletter_Queue_Edit_Form */ @@ -50,7 +50,8 @@ protected function _prepareForm() $form = new Varien_Data_Form(); $fieldset = $form->addFieldset('base_fieldset', array( - 'legend' => Mage::helper('newsletter')->__('Queue Information') + 'legend' => Mage::helper('newsletter')->__('Queue Information'), + 'class' => 'fieldset-wide' )); $outputFormat = Mage::app()->getLocale()->getDateTimeFormat(Mage_Core_Model_Locale::FORMAT_TYPE_MEDIUM); @@ -118,7 +119,9 @@ protected function _prepareForm() 'name' =>'subject', 'label' => Mage::helper('newsletter')->__('Subject'), 'required' => true, - 'value' => ($queue->isNew() ? $queue->getTemplate()->getTemplateSubject() : $queue->getNewsletterSubject()) + 'value' => ( + $queue->isNew() ? $queue->getTemplate()->getTemplateSubject() : $queue->getNewsletterSubject() + ) )); $fieldset->addField('sender_name', 'text', array( @@ -126,7 +129,9 @@ protected function _prepareForm() 'label' => Mage::helper('newsletter')->__('Sender Name'), 'title' => Mage::helper('newsletter')->__('Sender Name'), 'required' => true, - 'value' => ($queue->isNew() ? $queue->getTemplate()->getTemplateSenderName() : $queue->getNewsletterSenderName()) + 'value' => ( + $queue->isNew() ? $queue->getTemplate()->getTemplateSenderName() : $queue->getNewsletterSenderName() + ) )); $fieldset->addField('sender_email', 'text', array( @@ -135,11 +140,14 @@ protected function _prepareForm() 'title' => Mage::helper('newsletter')->__('Sender Email'), 'class' => 'validate-email', 'required' => true, - 'value' => ($queue->isNew() ? $queue->getTemplate()->getTemplateSenderEmail() : $queue->getNewsletterSenderEmail()) + 'value' => ( + $queue->isNew() ? $queue->getTemplate()->getTemplateSenderEmail() : $queue->getNewsletterSenderEmail() + ) )); $widgetFilters = array('is_email_compatible' => 1); - $wysiwygConfig = Mage::getSingleton('cms/wysiwyg_config')->getConfig(array('widget_filters' => $widgetFilters)); + $wysiwygConfig = Mage::getSingleton('cms/wysiwyg_config') + ->getConfig(array('widget_filters' => $widgetFilters)); if ($queue->isNew()) { $fieldset->addField('text','editor', array( @@ -148,7 +156,7 @@ protected function _prepareForm() 'state' => 'html', 'required' => true, 'value' => $queue->getTemplate()->getTemplateText(), - 'style' => 'width:98%; height: 600px;', + 'style' => 'height: 600px;', 'config' => $wysiwygConfig )); @@ -184,7 +192,7 @@ protected function _prepareForm() 'state' => 'html', 'required' => true, 'value' => $queue->getNewsletterText(), - 'style' => 'width:98%; height: 600px;', + 'style' => 'height: 600px;', 'config' => $wysiwygConfig )); @@ -192,7 +200,7 @@ protected function _prepareForm() 'name' =>'styles', 'label' => Mage::helper('newsletter')->__('Newsletter Styles'), 'value' => $queue->getNewsletterStyles(), - 'style' => 'width:98%; height: 300px;', + 'style' => 'height: 300px;', )); } diff --git a/app/code/core/Mage/Adminhtml/Block/Page/Menu.php b/app/code/core/Mage/Adminhtml/Block/Page/Menu.php index c551e023ac..5634f0a448 100644 --- a/app/code/core/Mage/Adminhtml/Block/Page/Menu.php +++ b/app/code/core/Mage/Adminhtml/Block/Page/Menu.php @@ -275,4 +275,36 @@ protected function _callbackSecretKey($match) return Mage_Adminhtml_Model_Url::SECRET_KEY_PARAM_NAME . '/' . $this->_url->getSecretKey($match[1], $match[2]); } + + /** + * Get menu level HTML code + * + * @param array $menu + * @param int $level + * @return string + */ + public function getMenuLevel($menu, $level = 0) + { + $html = '' . PHP_EOL; + + return $html; + } } diff --git a/app/code/core/Mage/Adminhtml/Block/Promo/Catalog/Edit/Tab/Main.php b/app/code/core/Mage/Adminhtml/Block/Promo/Catalog/Edit/Tab/Main.php index dc6e9a0980..b0a6f55c0d 100644 --- a/app/code/core/Mage/Adminhtml/Block/Promo/Catalog/Edit/Tab/Main.php +++ b/app/code/core/Mage/Adminhtml/Block/Promo/Catalog/Edit/Tab/Main.php @@ -108,7 +108,7 @@ protected function _prepareForm() 'name' => 'description', 'label' => Mage::helper('catalogrule')->__('Description'), 'title' => Mage::helper('catalogrule')->__('Description'), - 'style' => 'width: 98%; height: 100px;', + 'style' => 'height: 100px;', )); $fieldset->addField('is_active', 'select', array( @@ -130,14 +130,15 @@ protected function _prepareForm() )); $model->setWebsiteIds($websiteId); } else { - $fieldset->addField('website_ids', 'multiselect', array( + $field = $fieldset->addField('website_ids', 'multiselect', array( 'name' => 'website_ids[]', 'label' => Mage::helper('catalogrule')->__('Websites'), 'title' => Mage::helper('catalogrule')->__('Websites'), 'required' => true, - 'values' => Mage::getSingleton('adminhtml/system_store')->getWebsiteValuesForForm(), - 'after_element_html' => Mage::getBlockSingleton('adminhtml/store_switcher')->getHintHtml() + 'values' => Mage::getSingleton('adminhtml/system_store')->getWebsiteValuesForForm() )); + $renderer = $this->getLayout()->createBlock('adminhtml/store_switcher_form_renderer_fieldset_element'); + $field->setRenderer($renderer); } $fieldset->addField('customer_group_ids', 'multiselect', array( diff --git a/app/code/core/Mage/Adminhtml/Block/Promo/Quote/Edit/Tab/Main.php b/app/code/core/Mage/Adminhtml/Block/Promo/Quote/Edit/Tab/Main.php index 9a7f6df0ed..241eb8a0b7 100644 --- a/app/code/core/Mage/Adminhtml/Block/Promo/Quote/Edit/Tab/Main.php +++ b/app/code/core/Mage/Adminhtml/Block/Promo/Quote/Edit/Tab/Main.php @@ -107,7 +107,7 @@ protected function _prepareForm() 'name' => 'description', 'label' => Mage::helper('salesrule')->__('Description'), 'title' => Mage::helper('salesrule')->__('Description'), - 'style' => 'width: 98%; height: 100px;', + 'style' => 'height: 100px;', )); $fieldset->addField('is_active', 'select', array( @@ -129,19 +129,19 @@ protected function _prepareForm() $websiteId = Mage::app()->getStore(true)->getWebsiteId(); $fieldset->addField('website_ids', 'hidden', array( 'name' => 'website_ids[]', - 'value' => $websiteId, - 'after_element_html' => Mage::getBlockSingleton('adminhtml/store_switcher')->getHintHtml() + 'value' => $websiteId )); $model->setWebsiteIds($websiteId); } else { - $fieldset->addField('website_ids', 'multiselect', array( + $field = $fieldset->addField('website_ids', 'multiselect', array( 'name' => 'website_ids[]', 'label' => Mage::helper('salesrule')->__('Websites'), 'title' => Mage::helper('salesrule')->__('Websites'), 'required' => true, - 'values' => Mage::getSingleton('adminhtml/system_store')->getWebsiteValuesForForm(), - 'after_element_html' => Mage::getBlockSingleton('adminhtml/store_switcher')->getHintHtml() + 'values' => Mage::getSingleton('adminhtml/system_store')->getWebsiteValuesForForm() )); + $renderer = $this->getLayout()->createBlock('adminhtml/store_switcher_form_renderer_fieldset_element'); + $field->setRenderer($renderer); } $customerGroups = Mage::getResourceModel('customer/group_collection')->load()->toOptionArray(); diff --git a/app/code/core/Mage/Adminhtml/Block/Rating/Edit/Tab/Form.php b/app/code/core/Mage/Adminhtml/Block/Rating/Edit/Tab/Form.php index 60ecf278ff..de2b8ffc32 100644 --- a/app/code/core/Mage/Adminhtml/Block/Rating/Edit/Tab/Form.php +++ b/app/code/core/Mage/Adminhtml/Block/Rating/Edit/Tab/Form.php @@ -106,12 +106,13 @@ protected function _prepareForm() 'legend' => Mage::helper('rating')->__('Rating Visibility') )); - $fieldset->addField('stores', 'multiselect', array( + $field = $fieldset->addField('stores', 'multiselect', array( 'label' => Mage::helper('rating')->__('Visible In'), 'name' => 'stores[]', - 'values' => Mage::getSingleton('adminhtml/system_store')->getStoreValuesForForm(), - 'after_element_html' => Mage::getBlockSingleton('adminhtml/store_switcher')->getHintHtml() + 'values' => Mage::getSingleton('adminhtml/system_store')->getStoreValuesForForm() )); + $renderer = $this->getLayout()->createBlock('adminhtml/store_switcher_form_renderer_fieldset_element'); + $field->setRenderer($renderer); $fieldset->addField('position', 'text', array( 'label' => Mage::helper('rating')->__('Sort Order'), diff --git a/app/code/core/Mage/Adminhtml/Block/Report/Config/Form/Field/YtdStart.php b/app/code/core/Mage/Adminhtml/Block/Report/Config/Form/Field/YtdStart.php index cf16cab47e..b29e6afc11 100644 --- a/app/code/core/Mage/Adminhtml/Block/Report/Config/Form/Field/YtdStart.php +++ b/app/code/core/Mage/Adminhtml/Block/Report/Config/Form/Field/YtdStart.php @@ -40,7 +40,7 @@ protected function _getElementHtml(Varien_Data_Form_Element_Abstract $element) for ($i = 1; $i <= 12; $i++) { $_months[$i] = Mage::app()->getLocale() ->date(mktime(null,null,null,$i)) - ->get(Zend_date::MONTH_NAME); + ->get(Zend_Date::MONTH_NAME); } $_days = array(); diff --git a/app/code/core/Mage/Adminhtml/Block/Review/Add/Form.php b/app/code/core/Mage/Adminhtml/Block/Review/Add/Form.php index 54d28bd2a2..45ed9edf95 100644 --- a/app/code/core/Mage/Adminhtml/Block/Review/Add/Form.php +++ b/app/code/core/Mage/Adminhtml/Block/Review/Add/Form.php @@ -68,13 +68,14 @@ protected function _prepareForm() * Check is single store mode */ if (!Mage::app()->isSingleStoreMode()) { - $fieldset->addField('select_stores', 'multiselect', array( + $field = $fieldset->addField('select_stores', 'multiselect', array( 'label' => Mage::helper('review')->__('Visible In'), 'required' => true, 'name' => 'select_stores[]', 'values' => Mage::getSingleton('adminhtml/system_store')->getStoreValuesForForm(), - 'after_element_html' => Mage::getBlockSingleton('adminhtml/store_switcher')->getHintHtml() )); + $renderer = $this->getLayout()->createBlock('adminhtml/store_switcher_form_renderer_fieldset_element'); + $field->setRenderer($renderer); } $fieldset->addField('nickname', 'text', array( @@ -97,7 +98,7 @@ protected function _prepareForm() 'name' => 'detail', 'title' => Mage::helper('review')->__('Review'), 'label' => Mage::helper('review')->__('Review'), - 'style' => 'width: 98%; height: 600px;', + 'style' => 'height: 600px;', 'required' => true, )); diff --git a/app/code/core/Mage/Adminhtml/Block/Review/Edit/Form.php b/app/code/core/Mage/Adminhtml/Block/Review/Edit/Form.php index 4646545bd2..d9efa34bde 100644 --- a/app/code/core/Mage/Adminhtml/Block/Review/Edit/Form.php +++ b/app/code/core/Mage/Adminhtml/Block/Review/Edit/Form.php @@ -96,13 +96,14 @@ protected function _prepareForm() * Check is single store mode */ if (!Mage::app()->isSingleStoreMode()) { - $fieldset->addField('select_stores', 'multiselect', array( + $field = $fieldset->addField('select_stores', 'multiselect', array( 'label' => Mage::helper('review')->__('Visible In'), 'required' => true, 'name' => 'stores[]', 'values' => Mage::getSingleton('adminhtml/system_store')->getStoreValuesForForm(), - 'after_element_html' => Mage::getBlockSingleton('adminhtml/store_switcher')->getHintHtml() )); + $renderer = $this->getLayout()->createBlock('adminhtml/store_switcher_form_renderer_fieldset_element'); + $field->setRenderer($renderer); $review->setSelectStores($review->getStores()); } else { diff --git a/app/code/core/Mage/Adminhtml/Block/Sales/Order/Create/Form/Address.php b/app/code/core/Mage/Adminhtml/Block/Sales/Order/Create/Form/Address.php index b9b5694486..b00f89d4a7 100644 --- a/app/code/core/Mage/Adminhtml/Block/Sales/Order/Create/Form/Address.php +++ b/app/code/core/Mage/Adminhtml/Block/Sales/Order/Create/Form/Address.php @@ -166,7 +166,7 @@ protected function _prepareForm() } } } - if (!$this->_form->getElement('country_id')->getValue()) { + if (is_null($this->_form->getElement('country_id')->getValue())) { $this->_form->getElement('country_id')->setValue( Mage::helper('core')->getDefaultCountry($this->getStore()) ); diff --git a/app/code/core/Mage/Adminhtml/Block/Sitemap/Edit/Form.php b/app/code/core/Mage/Adminhtml/Block/Sitemap/Edit/Form.php index ced91e6be8..725795ad2b 100644 --- a/app/code/core/Mage/Adminhtml/Block/Sitemap/Edit/Form.php +++ b/app/code/core/Mage/Adminhtml/Block/Sitemap/Edit/Form.php @@ -81,15 +81,16 @@ protected function _prepareForm() )); if (!Mage::app()->isSingleStoreMode()) { - $fieldset->addField('store_id', 'select', array( + $field = $fieldset->addField('store_id', 'select', array( 'label' => Mage::helper('sitemap')->__('Store View'), 'title' => Mage::helper('sitemap')->__('Store View'), 'name' => 'store_id', 'required' => true, 'value' => $model->getStoreId(), 'values' => Mage::getSingleton('adminhtml/system_store')->getStoreValuesForForm(), - 'after_element_html' => Mage::getBlockSingleton('adminhtml/store_switcher')->getHintHtml() )); + $renderer = $this->getLayout()->createBlock('adminhtml/store_switcher_form_renderer_fieldset_element'); + $field->setRenderer($renderer); } else { $fieldset->addField('store_id', 'hidden', array( diff --git a/app/code/core/Mage/Adminhtml/Block/Store/Switcher.php b/app/code/core/Mage/Adminhtml/Block/Store/Switcher.php index df939db2f1..a0b83f2697 100644 --- a/app/code/core/Mage/Adminhtml/Block/Store/Switcher.php +++ b/app/code/core/Mage/Adminhtml/Block/Store/Switcher.php @@ -251,7 +251,7 @@ public function getHintHtml() . ' href="'. $this->escapeUrl($url) . '"' . ' onclick="this.target=\'_blank\'"' . ' title="' . $this->__('What is this?') . '"' - . ' class="link-storeScope">' + . ' class="link-store-scope">' . $this->__('What is this?') . ''; } diff --git a/app/code/core/Mage/Adminhtml/Block/Store/Switcher/Form/Renderer/Fieldset/Element.php b/app/code/core/Mage/Adminhtml/Block/Store/Switcher/Form/Renderer/Fieldset/Element.php new file mode 100644 index 0000000000..64f8c4514a --- /dev/null +++ b/app/code/core/Mage/Adminhtml/Block/Store/Switcher/Form/Renderer/Fieldset/Element.php @@ -0,0 +1,84 @@ + + */ +class Mage_Adminhtml_Block_Store_Switcher_Form_Renderer_Fieldset_Element + extends Mage_Adminhtml_Block_Widget_Form_Renderer_Fieldset_Element + implements Varien_Data_Form_Element_Renderer_Interface +{ + /** + * Form element which re-rendering + * + * @var Varien_Data_Form_Element_Fieldset + */ + protected $_element; + + /** + * Constructor + */ + protected function _construct() + { + $this->setTemplate('store/switcher/form/renderer/fieldset/element.phtml'); + } + + /** + * Retrieve an element + * + * @return Varien_Data_Form_Element_Fieldset + */ + public function getElement() + { + return $this->_element; + } + + /** + * Render element + * + * @param Varien_Data_Form_Element_Abstract $element + * @return string + */ + public function render(Varien_Data_Form_Element_Abstract $element) + { + $this->_element = $element; + return $this->toHtml(); + } + + /** + * Return html for store switcher hint + * + * @return string + */ + public function getHintHtml() + { + return Mage::getBlockSingleton('adminhtml/store_switcher')->getHintHtml(); + } +} diff --git a/app/code/core/Mage/Adminhtml/Block/System/Design/Edit/Tab/General.php b/app/code/core/Mage/Adminhtml/Block/System/Design/Edit/Tab/General.php index 6035f890a2..9c38b9bc31 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Design/Edit/Tab/General.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Design/Edit/Tab/General.php @@ -33,14 +33,15 @@ protected function _prepareForm() $fieldset = $form->addFieldset('general', array('legend'=>Mage::helper('core')->__('General Settings'))); if (!Mage::app()->isSingleStoreMode()) { - $fieldset->addField('store_id', 'select', array( + $field = $fieldset->addField('store_id', 'select', array( 'label' => Mage::helper('core')->__('Store'), 'title' => Mage::helper('core')->__('Store'), 'values' => Mage::getSingleton('adminhtml/system_store')->getStoreValuesForForm(), 'name' => 'store_id', 'required' => true, - 'after_element_html' => Mage::getBlockSingleton('adminhtml/store_switcher')->getHintHtml() )); + $renderer = $this->getLayout()->createBlock('adminhtml/store_switcher_form_renderer_fieldset_element'); + $field->setRenderer($renderer); } else { $fieldset->addField('store_id', 'hidden', array( 'name' => 'store_id', diff --git a/app/code/core/Mage/Adminhtml/Block/Tax/Rate/Form.php b/app/code/core/Mage/Adminhtml/Block/Tax/Rate/Form.php index 86d77e3c93..31e8d6e4a8 100644 --- a/app/code/core/Mage/Adminhtml/Block/Tax/Rate/Form.php +++ b/app/code/core/Mage/Adminhtml/Block/Tax/Rate/Form.php @@ -45,15 +45,10 @@ public function __construct() protected function _prepareForm() { - $rateId = (int)$this->getRequest()->getParam('rate'); - $rateObject = new Varien_Object(); - $rateModel = Mage::getSingleton('tax/calculation_rate'); - $rateObject->setData($rateModel->getData()); - + $rateObject = new Varien_Object(Mage::getSingleton('tax/calculation_rate')->getData()); $form = new Varien_Data_Form(); - $countries = Mage::getModel('adminhtml/system_config_source_country') - ->toOptionArray(); + $countries = Mage::getModel('adminhtml/system_config_source_country')->toOptionArray(); unset($countries[0]); if (!$rateObject->hasTaxCountryId()) { @@ -69,127 +64,84 @@ protected function _prepareForm() ->addCountryFilter($rateObject->getTaxCountryId()); $regions = $regionCollection->toOptionArray(); - if ($regions) { $regions[0]['label'] = '*'; } else { - $regions = array(array('value'=>'', 'label'=>'*')); + $regions = array(array('value' => '', 'label' => '*')); } - $fieldset = $form->addFieldset('base_fieldset', array('legend'=>Mage::helper('tax')->__('Tax Rate Information'))); + $fieldset = $form->addFieldset('base_fieldset', array('legend' => Mage::helper('tax')->__('Tax Rate Information'))); - if( $rateObject->getTaxCalculationRateId() > 0 ) { - $fieldset->addField('tax_calculation_rate_id', 'hidden', - array( - 'name' => "tax_calculation_rate_id", - 'value' => $rateObject->getTaxCalculationRateId() - ) - ); + if ($rateObject->getTaxCalculationRateId() > 0) { + $fieldset->addField('tax_calculation_rate_id', 'hidden', array( + 'name' => 'tax_calculation_rate_id', + 'value' => $rateObject->getTaxCalculationRateId() + )); } - $fieldset->addField('code', 'text', - array( - 'name' => 'code', - 'label' => Mage::helper('tax')->__('Tax Identifier'), - 'title' => Mage::helper('tax')->__('Tax Identifier'), - 'class' => 'required-entry', - 'value' => $rateModel->getCode(), - 'required' => true, - ) - ); - - $fieldset->addField('tax_country_id', 'select', - array( - 'name' => 'tax_country_id', - 'label' => Mage::helper('tax')->__('Country'), - 'required' => true, - 'values' => $countries - ) - ); + $fieldset->addField('code', 'text', array( + 'name' => 'code', + 'label' => Mage::helper('tax')->__('Tax Identifier'), + 'title' => Mage::helper('tax')->__('Tax Identifier'), + 'class' => 'required-entry', + 'required' => true, + )); - $fieldset->addField('tax_region_id', 'select', - array( - 'name' => 'tax_region_id', - 'label' => Mage::helper('tax')->__('State'), - 'values' => $regions - ) - ); + $fieldset->addField('tax_country_id', 'select', array( + 'name' => 'tax_country_id', + 'label' => Mage::helper('tax')->__('Country'), + 'required' => true, + 'values' => $countries + )); - /* FIXME!!! {* - $fieldset->addField('tax_county_id', 'select', - array( - 'name' => 'tax_county_id', - 'label' => Mage::helper('tax')->__('County'), - 'values' => array( - array( - 'label' => '*', - 'value' => '' - ) - ), - 'value' => $rateObject->getTaxCountyId() - ) - ); - } */ + $fieldset->addField('tax_region_id', 'select', array( + 'name' => 'tax_region_id', + 'label' => Mage::helper('tax')->__('State'), + 'values' => $regions + )); $fieldset->addField('zip_is_range', 'select', array( - 'name' => 'zip_is_range', - 'label' => Mage::helper('tax')->__('Zip/Post is Range'), - 'options' => array( - '0' => Mage::helper('tax')->__('No'), - '1' => Mage::helper('tax')->__('Yes'), - ) + 'name' => 'zip_is_range', + 'label' => Mage::helper('tax')->__('Zip/Post is Range'), + 'options' => array( + '0' => Mage::helper('tax')->__('No'), + '1' => Mage::helper('tax')->__('Yes'), + ) )); if (!$rateObject->hasTaxPostcode()) { $rateObject->setTaxPostcode(Mage::getStoreConfig(Mage_Tax_Model_Config::CONFIG_XML_PATH_DEFAULT_POSTCODE)); } - $fieldset->addField('tax_postcode', 'text', - array( - 'name' => 'tax_postcode', - 'label' => Mage::helper('tax')->__('Zip/Post Code'), - 'note' => Mage::helper('tax')->__("'*' - matches any; 'xyz*' - matches any that begins on 'xyz' and not longer than %d.", Mage::helper('tax')->getPostCodeSubStringLength()), - ) - ); - - $fieldset->addField('zip_from', 'text', - array( - 'name' => 'zip_from', - 'label' => Mage::helper('tax')->__('Range From'), - 'value' => $rateObject->getZipFrom(), - 'required' => true, - 'maxlength' => 9, - 'class' => 'validate-digits' - ) - ); + $fieldset->addField('tax_postcode', 'text', array( + 'name' => 'tax_postcode', + 'label' => Mage::helper('tax')->__('Zip/Post Code'), + 'note' => Mage::helper('tax')->__("'*' - matches any; 'xyz*' - matches any that begins on 'xyz' and not longer than %d.", Mage::helper('tax')->getPostCodeSubStringLength()), + )); - $fieldset->addField('zip_to', 'text', - array( - 'name' => 'zip_to', - 'label' => Mage::helper('tax')->__('Range To'), - 'value' => $rateObject->getZipTo(), - 'required' => true, - 'maxlength' => 9, - 'class' => 'validate-digits' - ) - ); + $fieldset->addField('zip_from', 'text', array( + 'name' => 'zip_from', + 'label' => Mage::helper('tax')->__('Range From'), + 'required' => true, + 'maxlength' => 9, + 'class' => 'validate-digits' + )); - if ($rateObject->getRate()) { - $value = 1*$rateObject->getRate(); - } else { - $value = 0; - } + $fieldset->addField('zip_to', 'text', array( + 'name' => 'zip_to', + 'label' => Mage::helper('tax')->__('Range To'), + 'required' => true, + 'maxlength' => 9, + 'class' => 'validate-digits' + )); - $fieldset->addField('rate', 'text', - array( - 'name' => "rate", - 'label' => Mage::helper('tax')->__('Rate Percent'), - 'title' => Mage::helper('tax')->__('Rate Percent'), - 'value' => number_format($value, 4), - 'required' => true, - 'class' => 'validate-not-negative-number' - ) - ); + $fieldset->addField('rate', 'text', array( + 'name' => 'rate', + 'label' => Mage::helper('tax')->__('Rate Percent'), + 'title' => Mage::helper('tax')->__('Rate Percent'), + 'required' => true, + 'class' => 'validate-not-negative-number' + )); $form->setAction($this->getUrl('*/tax_rate/save')); $form->setUseContainer(true); @@ -200,18 +152,23 @@ protected function _prepareForm() $form->addElement(Mage::getBlockSingleton('adminhtml/tax_rate_title_fieldset')->setLegend(Mage::helper('tax')->__('Tax Titles'))); } - $form->setValues($rateObject->getData()); + $rateData = $rateObject->getData(); + if ($rateObject->getZipIsRange()) { + list($rateData['zip_from'], $rateData['zip_to']) = explode('-', $rateData['tax_postcode']); + } + $form->setValues($rateData); $this->setForm($form); - $this->setChild('form_after', + $this->setChild( + 'form_after', $this->getLayout()->createBlock('adminhtml/widget_form_element_dependence') - ->addFieldMap("zip_is_range", 'zip_is_range') - ->addFieldMap("tax_postcode", 'tax_postcode') - ->addFieldMap("zip_from", 'zip_from') - ->addFieldMap("zip_to", 'zip_to') - ->addFieldDependence('zip_from', 'zip_is_range', '1') - ->addFieldDependence('zip_to', 'zip_is_range', '1') - ->addFieldDependence('tax_postcode', 'zip_is_range', '0') + ->addFieldMap('zip_is_range', 'zip_is_range') + ->addFieldMap('tax_postcode', 'tax_postcode') + ->addFieldMap('zip_from', 'zip_from') + ->addFieldMap('zip_to', 'zip_to') + ->addFieldDependence('zip_from', 'zip_is_range', '1') + ->addFieldDependence('zip_to', 'zip_is_range', '1') + ->addFieldDependence('tax_postcode', 'zip_is_range', '0') ); return parent::_prepareForm(); diff --git a/app/code/core/Mage/Adminhtml/Block/Urlrewrite/Edit/Form.php b/app/code/core/Mage/Adminhtml/Block/Urlrewrite/Edit/Form.php index f47cc35fce..0d4ca91d97 100644 --- a/app/code/core/Mage/Adminhtml/Block/Urlrewrite/Edit/Form.php +++ b/app/code/core/Mage/Adminhtml/Block/Urlrewrite/Edit/Form.php @@ -157,8 +157,9 @@ protected function _prepareForm() 'values' => $stores, 'disabled' => true, 'value' => $formValues['store_id'], - 'after_element_html' => Mage::getBlockSingleton('adminhtml/store_switcher')->getHintHtml() )); + $renderer = $this->getLayout()->createBlock('adminhtml/store_switcher_form_renderer_fieldset_element'); + $element->setRenderer($renderer); if ($noStoreError) { $element->setAfterElementHtml($noStoreError); } diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid.php index 915e6dc9a4..c5c38be9b0 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid.php @@ -801,7 +801,7 @@ public function setDefaultFilter($filter) /** * Retrieve grid export types * - * @return array + * @return array|false */ public function getExportTypes() { diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Customer/GroupAutoAssign.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Cookie.php similarity index 56% rename from app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Customer/GroupAutoAssign.php rename to app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Cookie.php index 84c37fe375..17dbcf270b 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Customer/GroupAutoAssign.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Cookie.php @@ -25,33 +25,13 @@ */ /** - * Auto-assign customer group Model + * Config Cookie Restriction mode backend * * @category Mage * @package Mage_Adminhtml * @author Magento Core Team */ -class Mage_Adminhtml_Model_System_Config_Backend_Customer_GroupAutoAssign extends Mage_Core_Model_Config_Data +class Mage_Adminhtml_Model_System_Config_Backend_Cookie extends Mage_Core_Model_Config_Data { - /** - * If merchant country is not in EU, VAT Validation should be disabled - * - * @return Mage_Core_Model_Abstract - */ - protected function _beforeSave() - { - $storeId = $this->getScopeId(); - $merchantCountry = Mage::getStoreConfig('general/store_information/merchant_country', $storeId); - - if (!Mage::helper('core')->isCountryInEU($merchantCountry, $storeId)) { - Mage::getConfig()->saveConfig( - Mage_Customer_Helper_Address::XML_PATH_VAT_VALIDATION_ENABLED, - 0, - $this->getScope(), - $storeId - ); - } - - return parent::_beforeSave(); - } + protected $_eventPrefix = 'adminhtml_system_config_backend_cookie'; } diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Email/Logo.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Email/Logo.php index a02a2f024f..4cf9be100b 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Email/Logo.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Email/Logo.php @@ -78,14 +78,17 @@ protected function _addWhetherScopeInfo() * * Save changes and delete file if "delete" option passed * - * @return Mage_Adminhtml_Model_System_Config_Backend_Email_logo + * @return Mage_Adminhtml_Model_System_Config_Backend_Email_Logo */ protected function _beforeSave() { - $value = $this->getValue(); - if (is_array($value) && !empty($value['delete'])) { + $value = $this->getValue(); + $deleteFlag = (is_array($value) && !empty($value['delete'])); + $fileTmpName = $_FILES['groups']['tmp_name'][$this->getGroupId()]['fields'][$this->getField()]['value']; + + if ($this->getOldValue() && ($fileTmpName || $deleteFlag)) { $io = new Varien_Io_File(); - $io->rm($this->_getUploadRoot(self::UPLOAD_ROOT_TOKEN) . DS . self::UPLOAD_DIR . DS . $value['value']); + $io->rm($this->_getUploadRoot(self::UPLOAD_ROOT_TOKEN) . DS . self::UPLOAD_DIR . DS . $this->getOldValue()); } return parent::_beforeSave(); } diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/File.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/File.php index df8607c06d..11d421d4a8 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/File.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/File.php @@ -48,10 +48,6 @@ class Mage_Adminhtml_Model_System_Config_Backend_File extends Mage_Core_Model_Co protected function _beforeSave() { $value = $this->getValue(); - if (is_array($value) && !empty($value['delete'])) { - $this->setValue(''); - } - if ($_FILES['groups']['tmp_name'][$this->getGroupId()]['fields'][$this->getField()]['value']){ $uploadDir = $this->_getUploadDir(); @@ -80,6 +76,12 @@ protected function _beforeSave() } $this->setValue($filename); } + } else { + if (is_array($value) && !empty($value['delete'])) { + $this->setValue(''); + } else { + $this->unsValue(); + } } return $this; diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Price/Step.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Price/Step.php index 44e9cca44b..4c5de773e1 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Price/Step.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Price/Step.php @@ -31,11 +31,11 @@ public function toOptionArray() return array( array( 'value' => Mage_Catalog_Model_Layer_Filter_Price::RANGE_CALCULATION_AUTO, - 'label' => Mage::helper('adminhtml')->__('Automatic') + 'label' => Mage::helper('adminhtml')->__('Automatic (equalize price ranges)') ), array( 'value' => Mage_Catalog_Model_Layer_Filter_Price::RANGE_CALCULATION_IMPROVED, - 'label' => Mage::helper('adminhtml')->__('Continuous') + 'label' => Mage::helper('adminhtml')->__('Automatic (equalize product counts)') ), array( 'value' => Mage_Catalog_Model_Layer_Filter_Price::RANGE_CALCULATION_MANUAL, diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Yesno.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Yesno.php index aec989e7bc..1c26199109 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Yesno.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Yesno.php @@ -44,4 +44,17 @@ public function toOptionArray() ); } + /** + * Get options in "key-value" format + * + * @return array + */ + public function toArray() + { + return array( + 0 => Mage::helper('adminhtml')->__('No'), + 1 => Mage::helper('adminhtml')->__('Yes'), + ); + } + } diff --git a/app/code/core/Mage/Adminhtml/controllers/Permissions/RoleController.php b/app/code/core/Mage/Adminhtml/controllers/Permissions/RoleController.php index 3ff5a131e5..88ce01896a 100644 --- a/app/code/core/Mage/Adminhtml/controllers/Permissions/RoleController.php +++ b/app/code/core/Mage/Adminhtml/controllers/Permissions/RoleController.php @@ -185,7 +185,7 @@ public function saveRoleAction() } try { - $roleName = Mage::helper('adminhtml')->stripTags($this->getRequest()->getParam('rolename', false)); + $roleName = $this->getRequest()->getParam('rolename', false); $role->setName($roleName) ->setPid($this->getRequest()->getParam('parent_id', false)) diff --git a/app/code/core/Mage/Adminhtml/controllers/System/BackupController.php b/app/code/core/Mage/Adminhtml/controllers/System/BackupController.php index 0267626e67..7dfa172529 100644 --- a/app/code/core/Mage/Adminhtml/controllers/System/BackupController.php +++ b/app/code/core/Mage/Adminhtml/controllers/System/BackupController.php @@ -105,8 +105,8 @@ public function createAction() if (!$turnedOn) { $response->setError( - Mage::helper('backup')->__("Warning! System couldn't put store on the maintenance mode.") . ' ' - . Mage::helper('backup')->__("Please deselect the sufficient check-box, if you want to continue backup's creation") + Mage::helper('backup')->__('You do not have sufficient permissions to enable Maintenance Mode during this operation.') + . ' ' . Mage::helper('backup')->__('Please either unselect the "Put store on the maintenance mode" checkbox or update your permissions to proceed with the backup."') ); $backupManager->setErrorMessage(Mage::helper('backup')->__("System couldn't put store on the maintenance mode")); return $this->getResponse()->setBody($response->toJson()); @@ -233,8 +233,8 @@ public function rollbackAction() if (!$turnedOn) { $response->setError( - Mage::helper('backup')->__("Warning! System couldn't put store on the maintenance mode.") . ' ' - . Mage::helper('backup')->__("Please deselect the sufficient check-box, if you want to continue rollback processing") + Mage::helper('backup')->__('You do not have sufficient permissions to enable Maintenance Mode during this operation.') + . ' ' . Mage::helper('backup')->__('Please either unselect the "Put store on the maintenance mode" checkbox or update your permissions to proceed with the rollback."') ); $backupManager->setErrorMessage(Mage::helper('backup')->__("System couldn't put store on the maintenance mode")); return $this->getResponse()->setBody($response->toJson()); diff --git a/app/code/core/Mage/Adminhtml/controllers/Tax/RateController.php b/app/code/core/Mage/Adminhtml/controllers/Tax/RateController.php index 5ba26a8d42..074f57092b 100644 --- a/app/code/core/Mage/Adminhtml/controllers/Tax/RateController.php +++ b/app/code/core/Mage/Adminhtml/controllers/Tax/RateController.php @@ -110,11 +110,9 @@ public function saveAction() $this->getResponse()->setRedirect($this->getUrl("*/*/")); return true; } catch (Mage_Core_Exception $e) { - //save entered by the user values in session, for re-rendering of form. Mage::getSingleton('adminhtml/session')->setFormData($ratePost); Mage::getSingleton('adminhtml/session')->addError($e->getMessage()); } catch (Exception $e) { - //Mage::getSingleton('adminhtml/session')->addError(Mage::helper('tax')->__('An error occurred while saving this rate.')); Mage::getSingleton('adminhtml/session')->addError($e->getMessage()); } diff --git a/app/code/core/Mage/Adminhtml/etc/config.xml b/app/code/core/Mage/Adminhtml/etc/config.xml index 16d9da1205..8b8af78a8a 100644 --- a/app/code/core/Mage/Adminhtml/etc/config.xml +++ b/app/code/core/Mage/Adminhtml/etc/config.xml @@ -174,6 +174,11 @@ + + + email + + system_emails_forgot_email_template general diff --git a/app/code/core/Mage/Api/Helper/Data.php b/app/code/core/Mage/Api/Helper/Data.php index b604fc672b..2307dcc3fe 100644 --- a/app/code/core/Mage/Api/Helper/Data.php +++ b/app/code/core/Mage/Api/Helper/Data.php @@ -35,6 +35,16 @@ class Mage_Api_Helper_Data extends Mage_Core_Helper_Abstract { const XML_PATH_API_WSI = 'api/config/compliance_wsi'; + /** + * Method to find adapter code depending on WS-I compatibility setting + * + * @return string + */ + public function getV2AdapterCode() + { + return $this->isComplianceWSI() ? 'soap_wsi' : 'soap_v2'; + } + /** * @return boolean */ @@ -165,7 +175,11 @@ public function clearWsiFootprints(&$obj) foreach ($objectKeys as $key) { if (is_object($obj->$key) && isset($obj->$key->complexObjectArray)) { - $obj->$key = $obj->$key->complexObjectArray; + if (is_array($obj->$key->complexObjectArray)) { + $obj->$key = $obj->$key->complexObjectArray; + } else { // for one element array + $obj->$key = array($obj->$key->complexObjectArray); + } $modifiedKeys[] = $key; } } diff --git a/app/code/core/Mage/Api/Model/Config.php b/app/code/core/Mage/Api/Model/Config.php index 625cc3af01..31ff058bc4 100644 --- a/app/code/core/Mage/Api/Model/Config.php +++ b/app/code/core/Mage/Api/Model/Config.php @@ -71,6 +71,24 @@ protected function _construct() return $this; } + /** + * Retrieve adapter aliases from config. + * + * @return array + */ + public function getAdapterAliases() + { + $aliases = array(); + + foreach ($this->getNode('adapter_aliases')->children() as $alias => $adapter) { + $aliases[$alias] = array( + (string) $adapter->suggest_class, // model class name + (string) $adapter->suggest_method // model method name + ); + } + return $aliases; + } + /** * Retrieve all adapters * diff --git a/app/code/core/Mage/Api/Model/Role.php b/app/code/core/Mage/Api/Model/Role.php index 70268c7667..1376281bfc 100644 --- a/app/code/core/Mage/Api/Model/Role.php +++ b/app/code/core/Mage/Api/Model/Role.php @@ -25,7 +25,7 @@ */ /** - * Enter description here ... + * Role item model * * @method Mage_Api_Model_Resource_Role _getResource() * @method Mage_Api_Model_Resource_Role getResource() @@ -48,6 +48,9 @@ */ class Mage_Api_Model_Role extends Mage_Core_Model_Abstract { + /** + * Initialize resource + */ protected function _construct() { $this->_init('api/role'); diff --git a/app/code/core/Mage/Api/Model/Roles.php b/app/code/core/Mage/Api/Model/Roles.php index 3b4173608f..553fd3ea7d 100644 --- a/app/code/core/Mage/Api/Model/Roles.php +++ b/app/code/core/Mage/Api/Model/Roles.php @@ -41,6 +41,8 @@ * @method Mage_Api_Model_Roles setUserId(int $value) * @method string getRoleName() * @method Mage_Api_Model_Roles setRoleName(string $value) + * @method string getName() + * @method Mage_Api_Model_Role setName() setName(string $name) * * @category Mage * @package Mage_Api @@ -48,6 +50,14 @@ */ class Mage_Api_Model_Roles extends Mage_Core_Model_Abstract { + /** + * Filters + * + * @var array + */ + protected $_filters; + + protected function _construct() { $this->_init('api/roles'); @@ -84,8 +94,10 @@ public function getRoleUsers() return $this->getResource()->getRoleUsers($this); } - protected function _buildResourcesArray(Varien_Simplexml_Element $resource=null, $parentName=null, $level=0, $represent2Darray=null, $rawNodes = false, $module = 'adminhtml') - { + protected function _buildResourcesArray( + Varien_Simplexml_Element $resource = null, $parentName = null, $level = 0, $represent2Darray = null, + $rawNodes = false, $module = 'adminhtml' + ) { static $result; if (is_null($resource)) { @@ -94,7 +106,9 @@ protected function _buildResourcesArray(Varien_Simplexml_Element $resource=null, $level = -1; } else { $resourceName = $parentName; - if ($resource->getName()!='title' && $resource->getName()!='sort_order' && $resource->getName() != 'children') { + if ($resource->getName()!='title' && $resource->getName()!='sort_order' + && $resource->getName() != 'children' + ) { $resourceName = (is_null($parentName) ? '' : $parentName.'/').$resource->getName(); //assigning module for its' children nodes @@ -135,4 +149,33 @@ protected function _buildResourcesArray(Varien_Simplexml_Element $resource=null, } } + /** + * Filter data before save + * + * @return Mage_Api_Model_Roles + */ + protected function _beforeSave() + { + $this->filter(); + parent::_beforeSave(); + return $this; + } + + /** + * Filter set data + * + * @return Mage_Api_Model_Roles + */ + public function filter() + { + $data = $this->getData(); + if (!$this->_filters || !$data) { + return $this; + } + /** @var $filter Mage_Core_Model_Input_Filter */ + $filter = Mage::getModel('core/input_filter'); + $filter->setFilters($this->_filters); + $this->setData($filter->filter($data)); + return $this; + } } diff --git a/app/code/core/Mage/Api/Model/Server.php b/app/code/core/Mage/Api/Model/Server.php index 9a24a319ac..a9589c5819 100644 --- a/app/code/core/Mage/Api/Model/Server.php +++ b/app/code/core/Mage/Api/Model/Server.php @@ -43,35 +43,89 @@ class Mage_Api_Model_Server /** * Web service adapter * - * @var Mage_Api_Model_Server_Adaper_Interface + * @var Mage_Api_Model_Server_Adapter_Interface */ protected $_adapter; - public function init(Mage_Api_Controller_Action $controller, $adapter='default', $handler='default') + /** + * Complex retrieve adapter code by calling auxiliary model method + * + * @param string $alias Alias name + * @return string|null Returns NULL if no alias found + */ + public function getAdapterCodeByAlias($alias) + { + /** @var $config Mage_Api_Model_Config */ + $config = Mage::getSingleton('api/config'); + $aliases = $config->getAdapterAliases(); + + if (!isset($aliases[$alias])) { + return null; + } + $object = Mage::getModel($aliases[$alias][0]); + $method = $aliases[$alias][1]; + + if (!method_exists($object, $method)) { + Mage::throwException(Mage::helper('api')->__('Can not find webservice adapter.')); + } + return $object->$method(); + } + + /** + * Initialize server components + * + * @param Mage_Api_Controller_Action $controller + * @param string $adapter Adapter name + * @param string $handler Handler name + * @return Mage_Api_Model_Server + */ + public function init(Mage_Api_Controller_Action $controller, $adapter = 'default', $handler = 'default') + { + $this->initialize($adapter, $handler); + + $this->_adapter->setController($controller); + + return $this; + } + + /** + * Initialize server components. Lightweight implementation of init() method + * + * @param string $adapterCode Adapter code + * @param string $handler OPTIONAL Handler name (if not specified, it will be found from config) + * @return Mage_Api_Model_Server + */ + public function initialize($adapterCode, $handler = null) { - $adapters = Mage::getSingleton('api/config')->getActiveAdapters(); - $handlers = Mage::getSingleton('api/config')->getHandlers(); - $this->_api = $adapter; - if (isset($adapters[$adapter])) { - $adapterModel = Mage::getModel((string) $adapters[$adapter]->model); - /* @var $adapterModel Mage_Api_Model_Server_Adapter_Interface */ + /** @var $helper Mage_Api_Model_Config */ + $helper = Mage::getSingleton('api/config'); + $adapters = $helper->getActiveAdapters(); + + if (isset($adapters[$adapterCode])) { + /** @var $adapterModel Mage_Api_Model_Server_Adapter_Interface */ + $adapterModel = Mage::getModel((string) $adapters[$adapterCode]->model); + if (!($adapterModel instanceof Mage_Api_Model_Server_Adapter_Interface)) { Mage::throwException(Mage::helper('api')->__('Invalid webservice adapter specified.')); } - $this->_adapter = $adapterModel; - $this->_adapter->setController($controller); + $this->_api = $adapterCode; + + // get handler code from config if no handler passed as argument + if (null === $handler && !empty($adapters[$adapterCode]->handler)) { + $handler = (string) $adapters[$adapterCode]->handler; + } + $handlers = $helper->getHandlers(); if (!isset($handlers->$handler)) { Mage::throwException(Mage::helper('api')->__('Invalid webservice handler specified.')); } - $handlerClassName = Mage::getConfig()->getModelClassName((string) $handlers->$handler->model); + $this->_adapter->setHandler($handlerClassName); } else { Mage::throwException(Mage::helper('api')->__('Invalid webservice adapter specified.')); } - return $this; } @@ -96,7 +150,7 @@ public function getApiName() /** * Retrieve web service adapter * - * @return Mage_Api_Model_Server_Adaper_Interface + * @return Mage_Api_Model_Server_Adapter_Interface */ public function getAdapter() { diff --git a/app/code/core/Mage/Api/Model/Server/Adapter/Soap.php b/app/code/core/Mage/Api/Model/Server/Adapter/Soap.php index 7f84c82a99..abc69335bf 100644 --- a/app/code/core/Mage/Api/Model/Server/Adapter/Soap.php +++ b/app/code/core/Mage/Api/Model/Server/Adapter/Soap.php @@ -77,13 +77,22 @@ public function setController(Mage_Api_Controller_Action $controller) } /** - * Retrive webservice api controller + * Retrive webservice api controller. If no controller have been set - emulate it by the use of Varien_Object * - * @return Mage_Api_Controller_Action + * @return Mage_Api_Controller_Action|Varien_Object */ public function getController() { - return $this->getData('controller'); + $controller = $this->getData('controller'); + + if (null === $controller) { + $controller = new Varien_Object( + array('request' => Mage::app()->getRequest(), 'response' => Mage::app()->getResponse()) + ); + + $this->setData('controller', $controller); + } + return $controller; } /** @@ -181,7 +190,7 @@ public function fault($code, $message) /** * Check whether Soap extension is loaded * - * @return boolean + * @return boolean */ protected function _extensionLoaded() { @@ -199,7 +208,9 @@ protected function getWsdlUrl($params = null, $withAuth = true) $urlModel = Mage::getModel('core/url') ->setUseSession(false); - $wsdlUrl = ($params !== null)? $urlModel->getUrl('*/*/*', $params) : $urlModel->getUrl('*/*/*'); + $wsdlUrl = $params !== null + ? $urlModel->getUrl('*/*/*', array('_current' => true, '_query' => $params)) + : $urlModel->getUrl('*/*/*'); if( $withAuth ) { $phpAuthUser = $this->getController()->getRequest()->getServer('PHP_AUTH_USER', false); @@ -227,9 +238,12 @@ protected function _instantiateServer() do { $retry = false; try { - $this->_soap = new Zend_Soap_Server($this->getWsdlUrl(array("wsdl" => 1)), array('encoding' => $apiConfigCharset)); + $this->_soap = new Zend_Soap_Server( + $this->getWsdlUrl(array("wsdl" => 1)), array('encoding' => $apiConfigCharset) + ); } catch (SoapFault $e) { - if (false !== strpos($e->getMessage(), "can't import schema from 'http://schemas.xmlsoap.org/soap/encoding/'")) { + $importMessage = "can't import schema from 'http://schemas.xmlsoap.org/soap/encoding/'"; + if (false !== strpos($e->getMessage(), $importMessage)) { $retry = true; sleep(1); } else { diff --git a/app/code/core/Mage/Api/Model/Server/Adapter/Xmlrpc.php b/app/code/core/Mage/Api/Model/Server/Adapter/Xmlrpc.php index 18b8bebeaf..0241284402 100644 --- a/app/code/core/Mage/Api/Model/Server/Adapter/Xmlrpc.php +++ b/app/code/core/Mage/Api/Model/Server/Adapter/Xmlrpc.php @@ -77,25 +77,33 @@ public function setController(Mage_Api_Controller_Action $controller) } /** - * Retrive webservice api controller + * Retrive webservice api controller. If no controller have been set - emulate it by the use of Varien_Object * - * @return Mage_Api_Controller_Action + * @return Mage_Api_Controller_Action|Varien_Object */ public function getController() { - return $this->getData('controller'); + $controller = $this->getData('controller'); + + if (null === $controller) { + $controller = new Varien_Object( + array('request' => Mage::app()->getRequest(), 'response' => Mage::app()->getResponse()) + ); + + $this->setData('controller', $controller); + } + return $controller; } /** * Run webservice * - * @param Mage_Api_Controller_Action $controller * @return Mage_Api_Model_Server_Adapter_Xmlrpc */ public function run() { $apiConfigCharset = Mage::getStoreConfig("api/config/charset"); - + $this->_xmlRpc = new Zend_XmlRpc_Server(); $this->_xmlRpc->setEncoding($apiConfigCharset) ->setClass($this->getHandler()); diff --git a/app/code/core/Mage/Api/Model/Server/Handler/Abstract.php b/app/code/core/Mage/Api/Model/Server/Handler/Abstract.php index 366e4b2ccd..2661d131fb 100644 --- a/app/code/core/Mage/Api/Model/Server/Handler/Abstract.php +++ b/app/code/core/Mage/Api/Model/Server/Handler/Abstract.php @@ -110,7 +110,7 @@ protected function _isAllowed($resource, $privilege=null) /** * Check session expiration * - * @return boolean + * @return boolean */ protected function _isSessionExpired () { @@ -210,10 +210,14 @@ protected function _prepareResourceModelName($resource) * @param string $apiKey * @return string */ - public function login($username, $apiKey) + public function login($username, $apiKey = null) { - $this->_startSession(); + if (empty($username) || empty($apiKey)) { + return $this->_fault('invalid_request_param'); + } + try { + $this->_startSession(); $this->_getSession()->login($username, $apiKey); } catch (Exception $e) { return $this->_fault('access_denied'); @@ -225,7 +229,7 @@ public function login($username, $apiKey) * Call resource functionality * * @param string $sessionId - * @param string $resourcePath + * @param string $apiPath * @param array $args * @return mixed */ diff --git a/app/code/core/Mage/Api/Model/Server/Wsi/Adapter/Soap.php b/app/code/core/Mage/Api/Model/Server/Wsi/Adapter/Soap.php index f4badbaf8e..327c4ad315 100644 --- a/app/code/core/Mage/Api/Model/Server/Wsi/Adapter/Soap.php +++ b/app/code/core/Mage/Api/Model/Server/Wsi/Adapter/Soap.php @@ -77,21 +77,17 @@ public function run() ->clearHeaders() ->setHeader('Content-Type','text/xml; charset='.$apiConfigCharset) ->setBody( - preg_replace( - '/(\>\<)/i', - ">\n<", + str_replace( + '', + "\n", str_replace( - '', - "\n", - str_replace( - '', - "\n", - preg_replace( - '/<\?xml version="([^\"]+)"([^\>]+)>/i', - '', - $this->_soap->handle() - ) - ) + '', + "\n", + preg_replace( + '/<\?xml version="([^\"]+)"([^\>]+)>/i', + '', + $this->_soap->handle() + ) ) ) ); diff --git a/app/code/core/Mage/Api/Model/Server/Wsi/Handler.php b/app/code/core/Mage/Api/Model/Server/Wsi/Handler.php index fa829c3e18..b1b6711b94 100644 --- a/app/code/core/Mage/Api/Model/Server/Wsi/Handler.php +++ b/app/code/core/Mage/Api/Model/Server/Wsi/Handler.php @@ -91,11 +91,8 @@ public function __call ($function, $args) * @param string $apiKey * @return string */ - public function login($username, $apiKey=null) + public function login($username, $apiKey = null) { - - /** @todo implement WS-I support check */ -// if (is_object($username)) { $apiKey = $username->apiKey; $username = $username->username; @@ -110,7 +107,7 @@ public function login($username, $apiKey=null) * Return called class and method names * * @param String $apiPath - * @return Array + * @return Array */ protected function _getResourceName($apiPath){ @@ -139,7 +136,7 @@ protected function _getResourceName($apiPath){ /** * Return an array of parameters for the callable method. - * + * * @param String $modelName * @param String $methodName * @return Array of ReflectionParameter @@ -171,7 +168,7 @@ public function prepareArgs($params, $args) { if($parameter->isOptional()){ $callArgs[$pName] = $parameter->getDefaultValue(); } else { - Mage::logException(new Exception("Required parameter \"$pName\" is missing.", 0, null)); + Mage::logException(new Exception("Required parameter \"$pName\" is missing.", 0)); $this->_fault('invalid_request_param'); } } diff --git a/app/code/core/Mage/Api/Model/Session.php b/app/code/core/Mage/Api/Model/Session.php index c97d4b38f2..57f0c51d94 100644 --- a/app/code/core/Mage/Api/Model/Session.php +++ b/app/code/core/Mage/Api/Model/Session.php @@ -39,7 +39,7 @@ class Mage_Api_Model_Session extends Mage_Core_Model_Session_Abstract public function start($sessionName=null) { // parent::start($sessionName=null); - $this->_currentSessId = md5(time() . $sessionName); + $this->_currentSessId = md5(time() . uniqid('', true) . $sessionName); $this->sessionIds[] = $this->getSessionId(); return $this; } @@ -83,10 +83,6 @@ public function clear() { public function login($username, $apiKey) { - if (empty($username) || empty($apiKey)) { - return; - } - $user = Mage::getModel('api/user') ->setSessid($this->getSessionId()) ->login($username, $apiKey); @@ -157,7 +153,7 @@ public function isAllowed($resource, $privilege=null) /** * Check session expiration * - * @return boolean + * @return boolean */ public function isSessionExpired ($user) { @@ -187,7 +183,7 @@ public function isLoggedIn($sessId = false) * Renew user by session ID if session not expired * * @param string $sessId - * @return boolean + * @return boolean */ protected function _renewBySessId ($sessId) { diff --git a/app/code/core/Mage/Api/Model/Wsdl/Config/Base.php b/app/code/core/Mage/Api/Model/Wsdl/Config/Base.php index fbe95f1f70..6e6540f1ec 100644 --- a/app/code/core/Mage/Api/Model/Wsdl/Config/Base.php +++ b/app/code/core/Mage/Api/Model/Wsdl/Config/Base.php @@ -45,6 +45,18 @@ class Mage_Api_Model_Wsdl_Config_Base extends Varien_Simplexml_Config public function __construct($sourceData=null) { $this->_elementClass = 'Mage_Api_Model_Wsdl_Config_Element'; + + // remove wsdl parameter from query + $queryParams = Mage::app()->getRequest()->getQuery(); + unset($queryParams['wsdl']); + + // set up default WSDL template variables + $this->_wsdlVariables = new Varien_Object( + array( + 'name' => 'Magento', + 'url' => htmlspecialchars(Mage::getUrl('*/*/*', array('_query' => $queryParams))) + ) + ); parent::__construct($sourceData); } @@ -78,20 +90,14 @@ public function getHandler() */ public function processFileData($text) { + /** @var $template Mage_Core_Model_Email_Template_Filter */ $template = Mage::getModel('core/email_template_filter'); - if (null === $this->_wsdlVariables) { - $this->_wsdlVariables = new Varien_Object(); - $this->_wsdlVariables->setUrl(Mage::getUrl('*/*/*')); - $this->_wsdlVariables->setName('Magento'); - $this->_wsdlVariables->setHandler($this->getHandler()); - } + $this->_wsdlVariables->setHandler($this->getHandler()); $template->setVariables(array('wsdl'=>$this->_wsdlVariables)); - $text = $template->filter($text); - - return $text; + return $template->filter($text); } public function addLoadedFile($file) @@ -113,4 +119,18 @@ public function loadFile($file) } return $this; } + + /** + * Set variable to be used in WSDL template processing + * + * @param string $key Varible key + * @param string $value Variable value + * @return Mage_Api_Model_Wsdl_Config_Base + */ + public function setWsdlVariable($key, $value) + { + $this->_wsdlVariables->setData($key, $value); + + return $this; + } } diff --git a/app/code/core/Mage/Api/etc/adminhtml.xml b/app/code/core/Mage/Api/etc/adminhtml.xml index 10bbb8116b..e2a4909b1e 100644 --- a/app/code/core/Mage/Api/etc/adminhtml.xml +++ b/app/code/core/Mage/Api/etc/adminhtml.xml @@ -37,11 +37,11 @@ 0 - Users + SOAP/XML-RPC - Users 10 - Roles + SOAP/XML-RPC - Roles 20 @@ -67,12 +67,14 @@ 25 - Users + SOAP/XML-RPC - Users adminhtml/api_user + 10 - Roles + SOAP/XML-RPC - Roles adminhtml/api_role + 20 diff --git a/app/code/core/Mage/Api/etc/api.xml b/app/code/core/Mage/Api/etc/api.xml index 6c33f82659..84aa261626 100644 --- a/app/code/core/Mage/Api/etc/api.xml +++ b/app/code/core/Mage/Api/etc/api.xml @@ -27,6 +27,12 @@ --> + + + Mage_Api_Helper_Data + getV2AdapterCode + + api/server_adapter_soap @@ -139,5 +145,148 @@ + + + + + + + Request is executed. + + + + Request is executed. Created new resource. + + + + Request is carried out. + + + + Parameter "%s" is not valid. + + + + + API version "%s" not found. + + + + You must provide an authenticated user for this method. + Token in request is not valid. + + + + Invalid API key + + + + Requested item %s not found. + Requested resource %s not found. + + + + Method "%s" is not allowed. + + + + Api version is required. + + + + + API version "%s" is deprecated. + + Resource "%s" is deprecated. + + + + + + There was unknown error while processing your request. + + There was internal error while processing your request. + + Server has internal error. %s: %s + + + + This resource is not implemented so far. + This method is not implemented so far. + + + + + + + + + + 200 + notification + + + + 201 + notification + + + + 202 + notification + + + + + 400 + error + + + + 400 + error + + + + 401 + error + + + + 403 + error + + + + 404 + error + + + + 405 + error + + + + 406 + error + + + + 410 + error + + + + 500 + error + + + + 501 + error + + + diff --git a/app/code/core/Mage/Api/etc/wsdl2.xml b/app/code/core/Mage/Api/etc/wsdl2.xml index 8d2db7c8a9..117cbcae32 100644 --- a/app/code/core/Mage/Api/etc/wsdl2.xml +++ b/app/code/core/Mage/Api/etc/wsdl2.xml @@ -18,6 +18,25 @@ + + + + + + + + + + + + + + + + + + + @@ -34,7 +53,7 @@ - + diff --git a/app/code/core/Mage/Api/etc/wsi.xml b/app/code/core/Mage/Api/etc/wsi.xml index 0db28ab286..9cac135868 100644 --- a/app/code/core/Mage/Api/etc/wsi.xml +++ b/app/code/core/Mage/Api/etc/wsi.xml @@ -33,7 +33,7 @@ - + diff --git a/app/code/core/Mage/Api2/Block/Adminhtml/Attribute.php b/app/code/core/Mage/Api2/Block/Adminhtml/Attribute.php new file mode 100644 index 0000000000..8a6c6453cb --- /dev/null +++ b/app/code/core/Mage/Api2/Block/Adminhtml/Attribute.php @@ -0,0 +1,48 @@ + + */ +class Mage_Api2_Block_Adminhtml_Attribute extends Mage_Adminhtml_Block_Widget_Grid_Container +{ + /** + * Construct grid container + */ + public function __construct() + { + parent::__construct(); + + $this->_blockGroup = 'api2'; + $this->_controller = 'adminhtml_attribute'; + $this->_headerText = $this->__('REST Attributes'); + $this->_removeButton('add'); + } +} diff --git a/app/code/core/Mage/Api2/Block/Adminhtml/Attribute/Buttons.php b/app/code/core/Mage/Api2/Block/Adminhtml/Attribute/Buttons.php new file mode 100644 index 0000000000..442c15b651 --- /dev/null +++ b/app/code/core/Mage/Api2/Block/Adminhtml/Attribute/Buttons.php @@ -0,0 +1,112 @@ + + */ +class Mage_Api2_Block_Adminhtml_Attribute_Buttons extends Mage_Adminhtml_Block_Template +{ + /** + * Construct + */ + public function __construct() + { + parent::__construct(); + $this->setTemplate('api2/attribute/buttons.phtml'); + } + + /** + * Prepare global layout + * + * @return Mage_Core_Block_Abstract + */ + protected function _prepareLayout() + { + $buttons = array( + 'backButton' => array( + 'label' => $this->__('Back'), + 'onclick' => sprintf("window.location.href='%s';", $this->getUrl('*/*/')), + 'class' => 'back' + ), + 'saveButton' => array( + 'label' => $this->__('Save'), + 'onclick' => 'form.submit(); return false;', + 'class' => 'save' + ), + ); + + foreach ($buttons as $name => $data) { + $button = $this->getLayout()->createBlock('adminhtml/widget_button')->setData($data); + $this->setChild($name, $button); + } + + return parent::_prepareLayout(); + } + + /** + * Get back button HTML + * + * @return string + */ + public function getBackButtonHtml() + { + return $this->getChildHtml('backButton'); + } + + /** + * Get reset button HTML + * + * @return string + */ + public function getResetButtonHtml() + { + return $this->getChildHtml('resetButton'); + } + + /** + * Get save button HTML + * + * @return string + */ + public function getSaveButtonHtml() + { + return $this->getChildHtml('saveButton'); + } + + /** + * Get block caption + * + * @return string + */ + public function getCaption() + { + return $this->__('Edit'); + } +} diff --git a/app/code/core/Mage/Api2/Block/Adminhtml/Attribute/Edit.php b/app/code/core/Mage/Api2/Block/Adminhtml/Attribute/Edit.php new file mode 100644 index 0000000000..3e59a51a09 --- /dev/null +++ b/app/code/core/Mage/Api2/Block/Adminhtml/Attribute/Edit.php @@ -0,0 +1,61 @@ + + */ +class Mage_Api2_Block_Adminhtml_Attribute_Edit extends Mage_Adminhtml_Block_Widget_Form_Container +{ + /** + * Initialize edit form container + */ + public function __construct() + { + $this->_objectId = 'id'; + $this->_blockGroup = 'api2'; + $this->_controller = 'adminhtml_attribute'; + + parent::__construct(); + + $this->_updateButton('save', 'label', $this->__('Save')) + ->_removeButton('delete'); + } + + /** + * Retrieve text for header element depending on loaded page + * + * @return string + */ + public function getHeaderText() + { + $userTypes = Mage_Api2_Model_Auth_User::getUserTypes(); + return $this->__('Edit attribute rules for %s Role', $userTypes[$this->getRequest()->getParam('type')]); + } +} diff --git a/app/code/core/Mage/Api2/Block/Adminhtml/Attribute/Edit/Form.php b/app/code/core/Mage/Api2/Block/Adminhtml/Attribute/Edit/Form.php new file mode 100644 index 0000000000..b917c9340f --- /dev/null +++ b/app/code/core/Mage/Api2/Block/Adminhtml/Attribute/Edit/Form.php @@ -0,0 +1,58 @@ + + */ +class Mage_Api2_Block_Adminhtml_Attribute_Edit_Form extends Mage_Adminhtml_Block_Widget_Form +{ + /** + * Prepare form before rendering HTML + * + * @return Mage_Api2_Block_Adminhtml_Attribute_Edit_Form + */ + protected function _prepareForm() + { + $form = new Varien_Data_Form(array( + 'id' => 'edit_form', + 'action' => $this->getData('action'), + 'method' => 'post' + )); + + + $form->setAction($this->getUrl('*/*/save', array('type' => $this->getRequest()->getParam('type')))) + ->setUseContainer(true); + + $this->setForm($form); + + return parent::_prepareForm(); + } +} diff --git a/app/code/core/Mage/Api2/Block/Adminhtml/Attribute/Grid.php b/app/code/core/Mage/Api2/Block/Adminhtml/Attribute/Grid.php new file mode 100644 index 0000000000..6e06ca5ed5 --- /dev/null +++ b/app/code/core/Mage/Api2/Block/Adminhtml/Attribute/Grid.php @@ -0,0 +1,107 @@ + + */ +class Mage_Api2_Block_Adminhtml_Attribute_Grid extends Mage_Adminhtml_Block_Widget_Grid +{ + /** + * Set grid ID + * + * @param array $attributes + */ + public function __construct($attributes = array()) + { + parent::__construct($attributes); + $this->setId('api2_attributes'); + } + + /** + * Collection object set up + */ + protected function _prepareCollection() + { + $collection = new Varien_Data_Collection(); + + foreach (Mage_Api2_Model_Auth_User::getUserTypes() as $type => $label) { + $collection->addItem( + new Varien_Object(array('user_type_name' => $label, 'user_type_code' => $type)) + ); + } + + $this->setCollection($collection); + } + + /** + * Prepare grid columns + * + * @return Mage_Api2_Block_Adminhtml_Attribute_Grid + */ + protected function _prepareColumns() + { + $this->addColumn('user_type_name', array( + 'header' => $this->__('User Type'), + 'index' => 'user_type_name' + )); + + return parent::_prepareColumns(); + } + + /** + * Disable unnecessary functionality + * + * @return Mage_Api2_Block_Adminhtml_Attribute_Grid + */ + public function _prepareLayout() + { + $this->setFilterVisibility(false); + $this->setPagerVisibility(false); + + return $this; + } + + /** + * Get row URL + * + * @param Varien_Object $row + * @return string|null + */ + public function getRowUrl($row) + { + /** @var $session Mage_Admin_Model_Session */ + $session = Mage::getSingleton('admin/session'); + if ($session->isAllowed('system/api/attributes/edit')) { + return $this->getUrl('*/*/edit', array('type' => $row->getUserTypeCode())); + } + + return null; + } +} diff --git a/app/code/core/Mage/Api2/Block/Adminhtml/Attribute/Tab/Resource.php b/app/code/core/Mage/Api2/Block/Adminhtml/Attribute/Tab/Resource.php new file mode 100644 index 0000000000..f05d1f661e --- /dev/null +++ b/app/code/core/Mage/Api2/Block/Adminhtml/Attribute/Tab/Resource.php @@ -0,0 +1,141 @@ + + * @method Mage_Api2_Model_Acl_Global_Role getRole() + * @method Mage_Api2_Block_Adminhtml_Attribute_Tab_Resource setRole(Mage_Api2_Model_Acl_Global_Role $role) + */ +class Mage_Api2_Block_Adminhtml_Attribute_Tab_Resource extends Mage_Adminhtml_Block_Widget_Form + implements Mage_Adminhtml_Block_Widget_Tab_Interface +{ + /** + * Tree model + * + * @var Mage_Api2_Model_Acl_Global_Rule_Tree + */ + protected $_treeModel = false; + + /** + * Constructor + */ + public function __construct() + { + parent::__construct(); + + $this->setId('api2_attribute_section_resources') + ->setData('default_dir', Varien_Db_Select::SQL_ASC) + ->setData('default_sort', 'sort_order') + ->setData('title', $this->__('Attribute Rules Information')) + ->setData('use_ajax', true); + + $this->_treeModel = Mage::getModel( + 'api2/acl_global_rule_tree', + array('type' => Mage_Api2_Model_Acl_Global_Rule_Tree::TYPE_ATTRIBUTE)); + + /** @var $permissions Mage_Api2_Model_Acl_Filter_Attribute_ResourcePermission */ + $permissions = Mage::getModel('api2/acl_filter_attribute_resourcePermission'); + $permissions->setFilterValue($this->getRequest()->getParam('type')); + $this->_treeModel->setResourcesPermissions($permissions->getResourcesPermissions()) + ->setHasEntityOnlyAttributes($permissions->getHasEntityOnlyAttributes()); + } + + /** + * Get Json Representation of Resource Tree + * + * @return string + */ + public function getResTreeJson() + { + /** @var $helper Mage_Core_Helper_Data */ + $helper = Mage::helper('core'); + return $helper->jsonEncode($this->_treeModel->getTreeResources()); + } + + /** + * Check if everything is allowed + * + * @return boolean + */ + public function getEverythingAllowed() + { + return $this->_treeModel->getEverythingAllowed(); + } + + /** + * Check if tree has entity only attributes + * + * @return bool + */ + public function hasEntityOnlyAttributes() + { + return $this->_treeModel->getHasEntityOnlyAttributes(); + } + + /** + * Get tab label + * + * @return string + */ + public function getTabLabel() + { + return $this->__('ACL Attribute Rules'); + } + + /** + * Get tab title + * + * @return string + */ + public function getTabTitle() + { + return $this->getTabLabel(); + } + + /** + * Whether tab is available + * + * @return bool + */ + public function canShowTab() + { + return true; + } + + /** + * Whether tab is hidden + * + * @return bool + */ + public function isHidden() + { + return false; + } +} diff --git a/app/code/core/Mage/Api2/Block/Adminhtml/Attribute/Tabs.php b/app/code/core/Mage/Api2/Block/Adminhtml/Attribute/Tabs.php new file mode 100644 index 0000000000..a964a95fc9 --- /dev/null +++ b/app/code/core/Mage/Api2/Block/Adminhtml/Attribute/Tabs.php @@ -0,0 +1,47 @@ + + */ +class Mage_Api2_Block_Adminhtml_Attribute_Tabs extends Mage_Adminhtml_Block_Widget_Tabs +{ + /** + * Constructor + */ + public function __construct() + { + parent::__construct(); + + $this->setId('api2_attribute_section_main') + ->setDestElementId('edit_form') + ->setData('title', $this->__('ACL Attributes Information')); + } +} diff --git a/app/code/core/Mage/Api2/Block/Adminhtml/Permissions/User/Edit/Tab/Roles.php b/app/code/core/Mage/Api2/Block/Adminhtml/Permissions/User/Edit/Tab/Roles.php new file mode 100644 index 0000000000..6f3762bd41 --- /dev/null +++ b/app/code/core/Mage/Api2/Block/Adminhtml/Permissions/User/Edit/Tab/Roles.php @@ -0,0 +1,203 @@ + + */ +class Mage_Api2_Block_Adminhtml_Permissions_User_Edit_Tab_Roles + extends Mage_Adminhtml_Block_Widget_Grid + implements Mage_Adminhtml_Block_Widget_Tab_Interface +{ + /** + * Selected API2 roles for grid + * + * @var array + */ + protected $_selectedRoles; + + /** + * Constructor + * Prepare grid parameters + */ + public function __construct() + { + parent::__construct(); + + $this->setId('api2_roles_section') + ->setDefaultSort('sort_order') + ->setDefaultDir(Varien_Db_Select::SQL_ASC) + ->setTitle($this->__('REST Roles Information')) + ->setUseAjax(true); + } + + /** + * Prepare grid collection object + * + * @return Mage_Api2_Block_Adminhtml_Permissions_User_Edit_Tab_Roles + */ + protected function _prepareCollection() + { + /** @var $collection Mage_Api2_Model_Resource_Acl_Global_Role_Collection */ + $collection = Mage::getResourceModel('api2/acl_global_role_collection'); + $collection->addFieldToFilter('entity_id', array('nin' => Mage_Api2_Model_Acl_Global_Role::getSystemRoles())); + + $this->setCollection($collection); + + return parent::_prepareCollection(); + } + + /** + * Prepare grid columns + * + * @return Mage_Api2_Block_Adminhtml_Permissions_User_Edit_Tab_Roles + */ + protected function _prepareColumns() + { + $this->addColumn('assigned_user_role', array( + 'header_css_class' => 'a-center', + 'header' => $this->__('Assigned'), + 'type' => 'radio', + 'html_name' => 'api2_roles[]', + 'values' => $this->_getSelectedRoles(), + 'align' => 'center', + 'index' => 'entity_id' + )); + + $this->addColumn('role_name', array( + 'header' => $this->__('Role Name'), + 'index' => 'role_name' + )); + + return parent::_prepareColumns(); + } + + /** + * Add custom column filter to collection + * + * @param Mage_Adminhtml_Block_Widget_Grid_Column $column + * @return Mage_Api2_Block_Adminhtml_Permissions_User_Edit_Tab_Roles + */ + protected function _addColumnFilterToCollection($column) + { + if ($column->getId() == 'assigned_user_role') { + $userRoles = $this->_getSelectedRoles(); + if ($column->getFilter()->getValue()) { + $this->getCollection()->addFieldToFilter('entity_id', array('in' => $userRoles)); + } elseif (!empty($userRoles)) { + $this->getCollection()->addFieldToFilter('entity_id', array('nin' => $userRoles)); + } else { + $this->getCollection(); + } + } else { + parent::_addColumnFilterToCollection($column); + } + + return $this; + } + + /** + * Get selected API2 roles for grid + * + * @return array + */ + protected function _getSelectedRoles() + { + if (null === $this->_selectedRoles) { + $userRoles = array(); + + /* @var $user Mage_Admin_Model_User */ + $user = Mage::registry('permissions_user'); + if ($user->getId()) { + /** @var $collection Mage_Api2_Model_Resource_Acl_Global_Role_Collection */ + $collection = Mage::getResourceModel('api2/acl_global_role_collection'); + $collection->addFilterByAdminId($user->getId()); + + $userRoles = $collection->getAllIds(); + } + + $this->_selectedRoles = $userRoles; + } + + return $this->_selectedRoles; + } + + /** + * Prepare label for tab + * + * @return string + */ + public function getTabLabel() + { + return $this->__('REST Role'); + } + + /** + * Prepare title for tab + * + * @return string + */ + public function getTabTitle() + { + return $this->__('REST Role'); + } + + /** + * Returns status flag about this tab can be shown 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; + } + + /** + * Get controller action url for grid ajax actions + * + * @return string + */ + public function getGridUrl() + { + return $this->getUrl( + '*/api2_role/rolesGrid', + array('user_id' => Mage::registry('permissions_user')->getUserId()) + ); + } +} diff --git a/app/code/core/Mage/Api2/Block/Adminhtml/Roles.php b/app/code/core/Mage/Api2/Block/Adminhtml/Roles.php new file mode 100644 index 0000000000..3e69348fd6 --- /dev/null +++ b/app/code/core/Mage/Api2/Block/Adminhtml/Roles.php @@ -0,0 +1,56 @@ + + */ +class Mage_Api2_Block_Adminhtml_Roles extends Mage_Adminhtml_Block_Widget_Grid_Container +{ + /** + * Construct grid container + */ + public function __construct() + { + parent::__construct(); + + $this->_blockGroup = 'api2'; + $this->_controller = 'adminhtml_roles'; + $this->_headerText = Mage::helper('adminhtml')->__('REST Roles'); + + //check allow edit + /** @var $session Mage_Admin_Model_Session */ + $session = Mage::getSingleton('admin/session'); + if ($session->isAllowed('system/api/roles/add')) { + $this->_updateButton('add', 'label', $this->__('Add Admin Role')); + } else { + $this->_removeButton('add'); + } + } +} diff --git a/app/code/core/Mage/Api2/Block/Adminhtml/Roles/Buttons.php b/app/code/core/Mage/Api2/Block/Adminhtml/Roles/Buttons.php new file mode 100644 index 0000000000..34892fde06 --- /dev/null +++ b/app/code/core/Mage/Api2/Block/Adminhtml/Roles/Buttons.php @@ -0,0 +1,146 @@ + + * @method Mage_Api2_Block_Adminhtml_Roles_Buttons setRole(Mage_Api2_Model_Acl_Global_Role $role) + * @method Mage_Api2_Model_Acl_Global_Role getRole() + */ +class Mage_Api2_Block_Adminhtml_Roles_Buttons extends Mage_Adminhtml_Block_Template +{ + /** + * Construct + */ + public function __construct() + { + parent::__construct(); + $this->setTemplate('api2/role/buttons.phtml'); + } + + /** + * Preparing global layout + * + * @return Mage_Api2_Block_Adminhtml_Roles_Buttons + */ + protected function _prepareLayout() + { + $buttons = array( + 'backButton' => array( + 'label' => Mage::helper('adminhtml')->__('Back'), + 'onclick' => sprintf("window.location.href='%s';", $this->getUrl('*/*/')), + 'class' => 'back' + ), + 'resetButton' => array( + 'label' => Mage::helper('adminhtml')->__('Reset'), + 'onclick' => 'window.location.reload()' + ), + 'saveButton' => array( + 'label' => Mage::helper('adminhtml')->__('Save Role'), + 'onclick' => 'roleForm.submit(); return false;', + 'class' => 'save' + ), + 'deleteButton' => array( + 'label' => Mage::helper('adminhtml')->__('Delete Role'), + 'onclick' => '', //roleId is not set at this moment, so we set script later + 'class' => 'delete' + ), + ); + + foreach ($buttons as $name=>$data) { + $button = $this->getLayout()->createBlock('adminhtml/widget_button')->setData($data); + $this->setChild($name, $button); + } + + return parent::_prepareLayout(); + } + + /** + * Get back button HTML + * + * @return string + */ + public function getBackButtonHtml() + { + return $this->getChildHtml('backButton'); + } + + /** + * Get reset button HTML + * + * @return string + */ + public function getResetButtonHtml() + { + return $this->getChildHtml('resetButton'); + } + + /** + * Get save button HTML + * + * @return string + */ + public function getSaveButtonHtml() + { + return $this->getChildHtml('saveButton'); + } + + /** + * Get delete button HTML + * + * @return string + */ + public function getDeleteButtonHtml() + { + if(!$this->getRole() || !$this->getRole()->getId() + || Mage_Api2_Model_Acl_Global_Role::isSystemRole($this->getRole())) { + + return ''; + } + + $this->getChild('deleteButton')->setData('onclick', sprintf("deleteConfirm('%s', '%s')", + Mage::helper('adminhtml')->__('Are you sure you want to do this?'), + $this->getUrl('*/*/delete', array('id' => $this->getRole()->getId())) + )); + + return $this->getChildHtml('deleteButton'); + } + + /** + * Get block caption + * + * @return string + */ + public function getCaption() + { + return $this->getRole() && $this->getRole()->getId() + ? ($this->__('Edit Role') . " '{$this->escapeHtml($this->getRole()->getRoleName())}'") + : $this->__('Add New Role'); + } +} diff --git a/app/code/core/Mage/Api2/Block/Adminhtml/Roles/Grid.php b/app/code/core/Mage/Api2/Block/Adminhtml/Roles/Grid.php new file mode 100644 index 0000000000..0807b6b9a2 --- /dev/null +++ b/app/code/core/Mage/Api2/Block/Adminhtml/Roles/Grid.php @@ -0,0 +1,149 @@ + + */ +class Mage_Api2_Block_Adminhtml_Roles_Grid extends Mage_Adminhtml_Block_Widget_Grid +{ + /** + * Construct grid block + */ + public function __construct() + { + parent::__construct(); + $this->setId('rolesGrid'); + $this->setUseAjax(true); + $this->setSaveParametersInSession(true); + $this->setDefaultSort('entity_id') + ->setDefaultDir(Varien_Db_Select::SQL_DESC); + } + + /** + * Prepare collection + * + * @return Mage_Api2_Block_Adminhtml_Roles_Grid + */ + protected function _prepareCollection() + { + /** @var $collection Mage_Api2_Model_Resource_Acl_Global_Role_Collection */ + $collection = Mage::getModel('api2/acl_global_role')->getCollection(); + $this->setCollection($collection); + parent::_prepareCollection(); + return $this; + } + + /** + * Prepare columns + * + * @return Mage_Api2_Block_Adminhtml_Roles_Grid + */ + protected function _prepareColumns() + { + $this->addColumn('entity_id', array( + 'header' => Mage::helper('oauth')->__('ID'), + 'index' => 'entity_id', + 'align' => 'right', + 'width' => '50px', + )); + + $this->addColumn('role_name', array( + 'header' => Mage::helper('oauth')->__('Role Name'), + 'index' => 'role_name', + 'escape' => true, + )); + + $this->addColumn('tole_user_type', array( + 'header' => Mage::helper('oauth')->__('User Type'), + 'sortable' => false, + 'frame_callback' => array($this, 'decorateUserType') + )); + + $this->addColumn('created_at', array( + 'header' => Mage::helper('oauth')->__('Created At'), + 'index' => 'created_at' + )); + + parent::_prepareColumns(); + return $this; + } + + /** + * Get grid URL + * + * @return string + */ + public function getGridUrl() + { + return $this->getUrl('*/*/grid', array('_current' => true)); + } + + /** + * Get row URL + * + * @param Mage_Api2_Model_Acl_Global_Role $row + * @return string|null + */ + public function getRowUrl($row) + { + /** @var $session Mage_Admin_Model_Session */ + $session = Mage::getSingleton('admin/session'); + + if ($session->isAllowed('system/api/roles/edit')) { + return $this->getUrl('*/*/edit', array('id' => $row->getId())); + } + return null; + } + + /** + * Decorate 'User Type' column + * + * @param string $renderedValue Rendered value + * @param Mage_OAuth_Model_Token $row + * @param Mage_Adminhtml_Block_Widget_Grid_Column $column + * @param bool $isExport + * @return string + */ + public function decorateUserType($renderedValue, $row, $column, $isExport) + { + switch ($row->getEntityId()) { + case Mage_Api2_Model_Acl_Global_Role::ROLE_GUEST_ID: + $userType = Mage::helper('api2')->__('Guest'); + break; + case Mage_Api2_Model_Acl_Global_Role::ROLE_CUSTOMER_ID: + $userType = Mage::helper('api2')->__('Customer'); + break; + default: + $userType = Mage::helper('api2')->__('Admin'); + break; + } + return $userType; + } +} diff --git a/app/code/core/Mage/Api2/Block/Adminhtml/Roles/Tab/Info.php b/app/code/core/Mage/Api2/Block/Adminhtml/Roles/Tab/Info.php new file mode 100644 index 0000000000..2c587c06b3 --- /dev/null +++ b/app/code/core/Mage/Api2/Block/Adminhtml/Roles/Tab/Info.php @@ -0,0 +1,131 @@ + + * @method Mage_Api2_Model_Acl_Global_Role getRole() + * @method Mage_Api2_Block_Adminhtml_Roles_Tab_Info setRole(Mage_Api2_Model_Acl_Global_Role $role) + */ +class Mage_Api2_Block_Adminhtml_Roles_Tab_Info extends Mage_Adminhtml_Block_Widget_Form + implements Mage_Adminhtml_Block_Widget_Tab_Interface +{ + /** + * Prepare form object + */ + protected function _prepareForm() + { + $form = new Varien_Data_Form(); + + $fieldset = $form->addFieldset('base_fieldset', array( + 'legend' => Mage::helper('adminhtml')->__('Role Information') + )); + + $data = array( + 'name' => 'role_name', + 'label' => Mage::helper('adminhtml')->__('Role Name'), + 'id' => 'role_name', + 'class' => 'required-entry', + 'required' => true, + ); + + if ($this->isHidden()) { + /** @var $helper Mage_Core_Helper_Data */ + $helper = Mage::helper('core'); + + $data['note'] = Mage::helper('api2')->__( + '%s role is protected.', + $helper->escapeHtml($this->getRole()->getRoleName()) + ); + $data['readonly'] = 'readonly'; + } + $fieldset->addField('role_name', 'text', $data); + + $fieldset->addField('entity_id', 'hidden', + array( + 'name' => 'id', + ) + ); + + $fieldset->addField('in_role_users', 'hidden', + array( + 'name' => 'in_role_users', + 'id' => 'in_role_userz', + ) + ); + + $fieldset->addField('in_role_users_old', 'hidden', array('name' => 'in_role_users_old')); + + if ($this->getRole()) { + $form->setValues($this->getRole()->getData()); + } + $this->setForm($form); + } + + /** + * Get tab label + * + * @return string + */ + public function getTabLabel() + { + return Mage::helper('api2')->__('Role Info'); + } + + /** + * Get tab title + * + * @return string + */ + public function getTabTitle() + { + return $this->getTabLabel(); + } + + /** + * Whether tab is available + * + * @return bool + */ + public function canShowTab() + { + return true; + } + + /** + * Whether tab is hidden + * + * @return bool + */ + public function isHidden() + { + return $this->getRole() && Mage_Api2_Model_Acl_Global_Role::isSystemRole($this->getRole()); + } +} diff --git a/app/code/core/Mage/Api2/Block/Adminhtml/Roles/Tab/Resources.php b/app/code/core/Mage/Api2/Block/Adminhtml/Roles/Tab/Resources.php new file mode 100644 index 0000000000..22a9e68726 --- /dev/null +++ b/app/code/core/Mage/Api2/Block/Adminhtml/Roles/Tab/Resources.php @@ -0,0 +1,153 @@ + + * @method Mage_Api2_Model_Acl_Global_Role getRole() + * @method Mage_Api2_Block_Adminhtml_Roles_Tab_Resources setRole(Mage_Api2_Model_Acl_Global_Role $role) + */ +class Mage_Api2_Block_Adminhtml_Roles_Tab_Resources extends Mage_Adminhtml_Block_Widget_Form + implements Mage_Adminhtml_Block_Widget_Tab_Interface +{ + /** + * Role model + * + * @var Mage_Api2_Model_Acl_Global_Role + */ + protected $_role; + + /** + * Tree model + * + * @var Mage_Api2_Model_Acl_Global_Rule_Tree + */ + protected $_treeModel = false; + + /** + * Constructor + */ + public function __construct() + { + parent::__construct(); + + $this->setId('api2_role_section_resources') + ->setData('default_dir', Varien_Db_Select::SQL_ASC) + ->setData('default_sort', 'sort_order') + ->setData('title', Mage::helper('api2')->__('Api Rules Information')) + ->setData('use_ajax', true); + + $this->_treeModel = Mage::getModel( + 'api2/acl_global_rule_tree', array('type' => Mage_Api2_Model_Acl_Global_Rule_Tree::TYPE_PRIVILEGE) + ); + } + + /** + * Get Json Representation of Resource Tree + * + * @return string + */ + public function getResTreeJson() + { + $this->_prepareTreeModel(); + /** @var $helper Mage_Core_Helper_Data */ + $helper = Mage::helper('core'); + return $helper->jsonEncode($this->_treeModel->getTreeResources()); + } + + /** + * Prepare tree model + * + * @return Mage_Api2_Block_Adminhtml_Roles_Tab_Resources + */ + public function _prepareTreeModel() + { + $role = $this->getRole(); + if ($role) { + $permissionModel = $role->getPermissionModel(); + $permissionModel->setFilterValue($role); + $this->_treeModel->setResourcesPermissions($permissionModel->getResourcesPermissions()); + } else { + $role = Mage::getModel('api2/acl_global_role'); + } + $this->_treeModel->setRole($role); + return $this; + } + + /** + * Check if everything is allowed + * + * @return boolean + */ + public function getEverythingAllowed() + { + $this->_prepareTreeModel(); + return $this->_treeModel->getEverythingAllowed(); + } + + /** + * Get tab label + * + * @return string + */ + public function getTabLabel() + { + return Mage::helper('api2')->__('Role API Resources'); + } + + /** + * Get tab title + * + * @return string + */ + public function getTabTitle() + { + return $this->getTabLabel(); + } + + /** + * Whether tab is available + * + * @return bool + */ + public function canShowTab() + { + return true; + } + + /** + * Whether tab is hidden + * + * @return bool + */ + public function isHidden() + { + return false; + } +} diff --git a/app/code/core/Mage/Api2/Block/Adminhtml/Roles/Tab/Users.php b/app/code/core/Mage/Api2/Block/Adminhtml/Roles/Tab/Users.php new file mode 100644 index 0000000000..ff8dcdb45d --- /dev/null +++ b/app/code/core/Mage/Api2/Block/Adminhtml/Roles/Tab/Users.php @@ -0,0 +1,239 @@ + + * @method Mage_Api2_Model_Acl_Global_Role getRole() + * @method Mage_Api2_Block_Adminhtml_Roles_Tab_Users setRole(Mage_Api2_Model_Acl_Global_Role $role) + * @method Mage_Api2_Block_Adminhtml_Roles_Tab_Users setUsers(array $users) + * @method Mage_Admin_Model_Resource_User_Collection getCollection() + */ +class Mage_Api2_Block_Adminhtml_Roles_Tab_Users extends Mage_Adminhtml_Block_Widget_Grid + implements Mage_Adminhtml_Block_Widget_Tab_Interface +{ + /** + * Construct grid block + */ + public function __construct() + { + parent::__construct(); + $this->setId('roleUsersGrid'); + $this->setData('use_ajax', true); + $this->setSaveParametersInSession(true); + $this->setDefaultSort('user_id') + ->setDefaultDir(Varien_Db_Select::SQL_DESC); + $this->setDefaultFilter(array('filter_in_role_users'=>1)); + } + + /** + * Prepare collection + * + * @return Mage_Api2_Block_Adminhtml_Roles_Tab_Users + */ + protected function _prepareCollection() + { + /** @var $collection Mage_Admin_Model_Resource_User_Collection */ + $collection = Mage::getModel('admin/user')->getCollection(); + $collection->getSelect()->joinLeft( + array('acl' => $collection->getTable('api2/acl_user')), + 'acl.admin_id = main_table.user_id', + 'role_id' + ); + if ($this->getRole() && $this->getRole()->getId()) { + $collection->addFilter('acl.role_id', $this->getRole()->getId()); + } + + $this->setCollection($collection); + parent::_prepareCollection(); + return $this; + } + + /** + * Prepare columns + * + * @return Mage_Api2_Block_Adminhtml_Roles_Tab_Users + */ + protected function _prepareColumns() + { + $this->addColumn('filter_in_role_users', array( + 'header_css_class' => 'a-center', + 'type' => 'checkbox', + 'name' => 'filter_in_role_users', + 'values' => $this->getUsers(), + 'align' => 'center', + 'index' => 'user_id' + )); + + $this->addColumn('user_id', array( + 'header' => Mage::helper('api2')->__('ID'), 'index' => 'user_id', 'align' => 'right', 'width' => '50px', + )); + + $this->addColumn('username', array( + 'header' => Mage::helper('adminhtml')->__('User Name'), 'align' => 'left', 'index' => 'username' + )); + + $this->addColumn('firstname', array( + 'header' => Mage::helper('adminhtml')->__('First Name'), 'align' => 'left', 'index' => 'firstname' + )); + + $this->addColumn('lastname', array( + 'header' => Mage::helper('adminhtml')->__('Last Name'), 'align' => 'left', 'index' => 'lastname' + )); + + return parent::_prepareColumns(); + } + + /** + * Get grid URL + * + * @return string + */ + public function getGridUrl() + { + return $this->getUrl('*/*/usersGrid', array('_current' => true)); + } + + /** + * Get row URL + * + * @param Mage_Api2_Model_Acl_Global_Role $row + * @return string|null + */ + public function getRowUrl($row) + { + return null; + } + + /** + * Get tab label + * + * @return string + */ + public function getTabLabel() + { + return Mage::helper('api2')->__('Role Users'); + } + + /** + * Get tab title + * + * @return string + */ + public function getTabTitle() + { + return $this->getTabLabel(); + } + + /** + * Whether tab is available + * + * @return bool + */ + public function canShowTab() + { + return !$this->isHidden(); + } + + /** + * Whether tab is hidden + * + * @return bool + */ + public function isHidden() + { + return $this->getRole() && Mage_Api2_Model_Acl_Global_Role::isSystemRole($this->getRole()); + } + + /** + * Render block only when not hidden + * + * @return string + */ + public function _toHtml() + { + if (!$this->isHidden()) { + return parent::_toHtml(); + } + return ''; + } + + /** + * @param Mage_Adminhtml_Block_Widget_Grid_Column $column + * @return Mage_Api2_Block_Adminhtml_Roles_Tab_Users + */ + protected function _addColumnFilterToCollection($column) + { + if ($column->getId() == 'filter_in_role_users') { + $inRoleIds = $this->getUsers(); + if (empty($inRoleIds)) { + $inRoleIds = 0; + } + + if ($column->getFilter()->getValue()) { + $this->getCollection()->addFieldToFilter('user_id', array('in' => $inRoleIds)); + } else { + if($inRoleIds) { + $this->getCollection()->addFieldToFilter('user_id', array('nin' => $inRoleIds)); + } + } + } else { + parent::_addColumnFilterToCollection($column); + } + return $this; + } + + /** + * Get users + * + * @param bool $json + * @return array|string + */ + public function getUsers($json = false) + { + $users = $this->getData('users'); + + if ($json) { + if ($users === array()) { + return '{}'; + } + $jsonUsers = array(); + foreach($users as $usrId) { + $jsonUsers[$usrId] = 0; + } + /** @var $helper Mage_Core_Helper_Data */ + $helper = Mage::helper('core'); + $result = $helper->jsonEncode((object) $jsonUsers); + } else { + $result = array_values($users); + } + + return $result; + } +} diff --git a/app/code/core/Mage/Api2/Block/Adminhtml/Roles/Tabs.php b/app/code/core/Mage/Api2/Block/Adminhtml/Roles/Tabs.php new file mode 100644 index 0000000000..9de62eaaaf --- /dev/null +++ b/app/code/core/Mage/Api2/Block/Adminhtml/Roles/Tabs.php @@ -0,0 +1,65 @@ + + * @method Mage_Api2_Block_Adminhtml_Roles_Tabs setRole(Mage_Api2_Model_Acl_Global_Role $role) + * @method Mage_Api2_Model_Acl_Global_Role getRole() + */ +class Mage_Api2_Block_Adminhtml_Roles_Tabs extends Mage_Adminhtml_Block_Widget_Tabs +{ + + /** + * Constructor + */ + public function __construct() + { + parent::__construct(); + $this->setId('role_info_tabs'); + $this->setDestElementId('role_edit_form'); + $this->setData('title', Mage::helper('api2')->__('Role Information')); + } + + /** + * Hook before html rendering + * + * @return Mage_Api2_Block_Adminhtml_Roles_Tabs + */ + protected function _beforeToHtml() + { + $role = $this->getRole(); + if ($role && Mage_Api2_Model_Acl_Global_Role::isSystemRole($role)) { + $this->setActiveTab('api2_role_section_resources'); + } else { + $this->setActiveTab('api2_role_section_info'); + } + return parent::_beforeToHtml(); + } +} diff --git a/app/code/core/Mage/Api2/Exception.php b/app/code/core/Mage/Api2/Exception.php new file mode 100644 index 0000000000..4752b0c641 --- /dev/null +++ b/app/code/core/Mage/Api2/Exception.php @@ -0,0 +1,50 @@ + + */ +class Mage_Api2_Exception extends Exception +{ + /** + * Exception constructor + * + * @param string $message + * @param int $code + */ + public function __construct($message, $code) + { + if ($code <= 100 || $code >= 599) { + throw new Exception(sprintf('Invalid Exception code "%d"', $code)); + } + + parent::__construct($message, $code); + } +} diff --git a/app/code/core/Mage/Api2/Helper/Data.php b/app/code/core/Mage/Api2/Helper/Data.php new file mode 100644 index 0000000000..48e04c793d --- /dev/null +++ b/app/code/core/Mage/Api2/Helper/Data.php @@ -0,0 +1,198 @@ + + */ +class Mage_Api2_Helper_Data extends Mage_Core_Helper_Abstract +{ + /** + * Request interpret adapters + */ + const XML_PATH_API2_REQUEST_INTERPRETERS = 'global/api2/request/interpreters'; + + /** + * Response render adapters + */ + const XML_PATH_API2_RESPONSE_RENDERS = 'global/api2/response/renders'; + + /**#@+ + * Config paths + */ + const XML_PATH_AUTH_ADAPTERS = 'global/api2/auth_adapters'; + const XML_PATH_USER_TYPES = 'global/api2/user_types'; + /**#@- */ + + /** + * Compare order to be used in adapters list sort + * + * @param int $a + * @param int $b + * @return int + */ + protected static function _compareOrder($a, $b) + { + if ($a['order'] == $b['order']) { + return 0; + } + return ($a['order'] < $b['order']) ? -1 : 1; + } + + /** + * Retrieve Auth adapters info from configuration file as array + * + * @param bool $enabledOnly + * @return array + */ + public function getAuthAdapters($enabledOnly = false) + { + $adapters = Mage::getConfig()->getNode(self::XML_PATH_AUTH_ADAPTERS); + + if (!$adapters) { + return array(); + } + $adapters = $adapters->asArray(); + + if ($enabledOnly) { + foreach ($adapters as $adapter) { + if (empty($adapter['enabled'])) { + unset($adapters); + } + } + $adapters = (array) $adapters; + } + uasort($adapters, array('Mage_Api2_Helper_Data', '_compareOrder')); + + return $adapters; + } + + /** + * Retrieve enabled user types in form of user type => user model pairs + * + * @return array + */ + public function getUserTypes() + { + $userModels = array(); + $types = Mage::getConfig()->getNode(self::XML_PATH_USER_TYPES); + + if ($types) { + foreach ($types->asArray() as $type => $params) { + if (!empty($params['allowed'])) { + $userModels[$type] = $params['model']; + } + } + } + return $userModels; + } + + /** + * Get interpreter type for Request body according to Content-type HTTP header + * + * @return array + */ + public function getRequestInterpreterAdapters() + { + return (array) Mage::app()->getConfig()->getNode(self::XML_PATH_API2_REQUEST_INTERPRETERS); + } + + /** + * Get interpreter type for Request body according to Content-type HTTP header + * + * @return array + */ + public function getResponseRenderAdapters() + { + return (array) Mage::app()->getConfig()->getNode(self::XML_PATH_API2_RESPONSE_RENDERS); + } + + /** + * Check API type support + * + * @param string $type + * @return bool + */ + public function isApiTypeSupported($type) + { + return in_array($type, Mage_Api2_Model_Server::getApiTypes()); + } + + /** + * Get allowed attributes of a rule + * + * @param string $userType + * @param string $resourceId + * @param string $operation One of Mage_Api2_Model_Resource::OPERATION_ATTRIBUTE_... constant + * @return array + */ + public function getAllowedAttributes($userType, $resourceId, $operation) + { + /** @var $resource Mage_Api2_Model_Resource_Acl_Filter_Attribute */ + $resource = Mage::getResourceModel('api2/acl_filter_attribute'); + + $attributes = $resource->getAllowedAttributes($userType, $resourceId, $operation); + + return ($attributes === false || $attributes === null ? array() : explode(',', $attributes)); + } + + /** + * Check if ALL attributes are allowed + * + * @param string $userType + * @return bool + */ + public function isAllAttributesAllowed($userType) + { + /** @var $resource Mage_Api2_Model_Resource_Acl_Filter_Attribute */ + $resource = Mage::getResourceModel('api2/acl_filter_attribute'); + + return $resource->isAllAttributesAllowed($userType); + } + + /** + * Get operation type for specified operation + * + * @param string $operation One of Mage_Api2_Model_Resource::OPERATION_... constant + * @return string One of Mage_Api2_Model_Resource::OPERATION_ATTRIBUTE_... constant + * @throws Exception + */ + public function getTypeOfOperation($operation) + { + if (Mage_Api2_Model_Resource::OPERATION_RETRIEVE === $operation) { + return Mage_Api2_Model_Resource::OPERATION_ATTRIBUTE_READ; + } elseif (Mage_Api2_Model_Resource::OPERATION_CREATE === $operation) { + return Mage_Api2_Model_Resource::OPERATION_ATTRIBUTE_WRITE; + } elseif (Mage_Api2_Model_Resource::OPERATION_UPDATE === $operation) { + return Mage_Api2_Model_Resource::OPERATION_ATTRIBUTE_WRITE; + } else { + throw new Exception('Can not determine operation type'); + } + } +} diff --git a/app/code/core/Mage/Api2/Model/Acl.php b/app/code/core/Mage/Api2/Model/Acl.php new file mode 100644 index 0000000000..296e519c7f --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Acl.php @@ -0,0 +1,187 @@ + + */ +class Mage_Api2_Model_Acl extends Zend_Acl +{ + /** + * REST ACL roles collection + * + * @var Mage_Api2_Model_Resource_Acl_Global_Role_Collection + */ + protected $_rolesCollection; + + /** + * API2 config model instance + * + * @var Mage_Api2_Model_Config + */ + protected $_config; + + /** + * Resource type of request + * + * @var string + */ + protected $_resourceType; + + /** + * Operation of request + * + * @var string + */ + protected $_operation; + + /** + * Constructor + * + * @param array $options + */ + public function __construct($options) + { + if (!isset($options['resource_type']) || empty($options['resource_type'])) { + throw new Exception("Passed parameter 'resource_type' is wrong."); + } + if (!isset($options['operation']) || empty($options['operation'])) { + throw new Exception("Passed parameter 'operation' is wrong."); + } + $this->_resourceType = $options['resource_type']; + $this->_operation = $options['operation']; + + $this->_setResources(); + $this->_setRoles(); + $this->_setRules(); + } + + /** + * Retrieve REST ACL roles collection + * + * @return Mage_Api2_Model_Resource_Acl_Global_Role_Collection + */ + protected function _getRolesCollection() + { + if (null === $this->_rolesCollection) { + $this->_rolesCollection = Mage::getResourceModel('api2/acl_global_role_collection'); + } + return $this->_rolesCollection; + } + + /** + * Retrieve API2 config model instance + * + * @return Mage_Api2_Model_Config + */ + protected function _getConfig() + { + if (null === $this->_config) { + $this->_config = Mage::getModel('api2/config'); + } + return $this->_config; + } + + /** + * Retrieve resources types and set into ACL + * + * @return Mage_Api2_Model_Acl + */ + protected function _setResources() + { + foreach ($this->_getConfig()->getResourcesTypes() as $type) { + $this->addResource($type); + } + return $this; + } + + /** + * Retrieve roles from DB and set into ACL + * + * @return Mage_Api2_Model_Acl + */ + protected function _setRoles() + { + /** @var $role Mage_Api2_Model_Acl_Global_Role */ + foreach ($this->_getRolesCollection() as $role) { + $this->addRole($role->getId()); + } + return $this; + } + + /** + * Retrieve rules data from DB and inject it into ACL + * + * @return Mage_Api2_Model_Acl + */ + protected function _setRules() + { + /** @var $rulesCollection Mage_Api2_Model_Resource_Acl_Global_Rule_Collection */ + $rulesCollection = Mage::getResourceModel('api2/acl_global_rule_collection'); + + /** @var $rule Mage_Api2_Model_Acl_Global_Rule */ + foreach ($rulesCollection as $rule) { + if (Mage_Api2_Model_Acl_Global_Rule::RESOURCE_ALL === $rule->getResourceId()) { + if (in_array($rule->getRoleId(), Mage_Api2_Model_Acl_Global_Role::getSystemRoles())) { + /** @var $role Mage_Api2_Model_Acl_Global_Role */ + $role = $this->_getRolesCollection()->getItemById($rule->getRoleId()); + $privileges = $this->_getConfig()->getResourceUserPrivileges( + $this->_resourceType, + $role->getConfigNodeName() + ); + + if (!array_key_exists($this->_operation, $privileges)) { + continue; + } + } + + $this->allow($rule->getRoleId()); + } else { + $this->allow($rule->getRoleId(), $rule->getResourceId(), $rule->getPrivilege()); + } + } + return $this; + } + + /** + * Adds a Role having an identifier unique to the registry + * OVERRIDE to allow numeric roles identifiers + * + * @param int $roleId Role identifier + * @param Zend_Acl_Role_Interface|string|array $parents + * @return Zend_Acl Provides a fluent interface + */ + public function addRole($roleId, $parents = null) + { + if (!is_numeric($roleId)) { + throw new Exception('Invalid role identifier'); + } + return parent::addRole((string) $roleId); + } +} diff --git a/app/code/core/Mage/Api2/Model/Acl/Filter.php b/app/code/core/Mage/Api2/Model/Acl/Filter.php new file mode 100644 index 0000000000..85517d0516 --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Acl/Filter.php @@ -0,0 +1,193 @@ + + */ +class Mage_Api2_Model_Acl_Filter +{ + /** + * Attributes allowed for use + * + * @var array + */ + protected $_allowedAttributes; + + /** + * A list of attributes to be included into output + * + * @var array + */ + protected $_attributesToInclude; + + /** + * Associated resource model + * + * @var Mage_Api2_Model_Resource + */ + protected $_resource; + + /** + * Object constructor + * + * @param Mage_Api2_Model_Resource $resource + */ + public function __construct(Mage_Api2_Model_Resource $resource) + { + $this->_resource = $resource; + } + + /** + * Return only the data which keys are allowed + * + * @param array $allowedAttributes List of attributes available to use + * @param array $data Associative array attribute to value + * @return array + */ + protected function _filter(array $allowedAttributes, array $data) + { + foreach ($data as $attribute => $value) { + if (!in_array($attribute, $allowedAttributes)) { + unset($data[$attribute]); + } + } + return $data; + } + + /** + * Strip attributes in of collection items + * + * @param array $items + * @return array + */ + public function collectionIn($items) + { + foreach ($items as &$data) { + $data = is_array($data) ? $this->in($data) : array(); + } + return $items; + } + + /** + * Strip attributes out of collection items + * + * @param array $items + * @return array + */ + public function collectionOut($items) + { + foreach ($items as &$data) { + $data = $this->out($data); + } + return $items; + } + + /** + * Fetch array of allowed attributes for given resource type, operation and user type. + * + * @param string $operationType OPTIONAL One of Mage_Api2_Model_Resource::OPERATION_ATTRIBUTE_... constant + * @return array + */ + public function getAllowedAttributes($operationType = null) + { + if (null === $this->_allowedAttributes) { + /** @var $helper Mage_Api2_Helper_Data */ + $helper = Mage::helper('api2/data'); + + if (null === $operationType) { + $operationType = $helper->getTypeOfOperation($this->_resource->getOperation()); + } + if ($helper->isAllAttributesAllowed($this->_resource->getUserType())) { + $this->_allowedAttributes = array_keys($this->_resource->getAvailableAttributes( + $this->_resource->getUserType(), $operationType + )); + } else { + $this->_allowedAttributes = $helper->getAllowedAttributes( + $this->_resource->getUserType(), $this->_resource->getResourceType(), $operationType + ); + } + // force attributes to be no filtered + foreach ($this->_resource->getForcedAttributes() as $forcedAttr) { + if (!in_array($forcedAttr, $this->_allowedAttributes)) { + $this->_allowedAttributes[] = $forcedAttr; + } + } + } + return $this->_allowedAttributes; + } + + /** + * Retrieve a list of attributes to be included in output based on available and requested attributes + * + * @return array + */ + public function getAttributesToInclude() + { + if (null === $this->_attributesToInclude) { + $allowedAttrs = $this->getAllowedAttributes(Mage_Api2_Model_Resource::OPERATION_ATTRIBUTE_READ); + $requestedAttrs = $this->_resource->getRequest()->getRequestedAttributes(); + + if ($requestedAttrs) { + foreach ($allowedAttrs as $allowedAttr) { + if (in_array($allowedAttr, $requestedAttrs)) { + $this->_attributesToInclude[] = $allowedAttr; + } + } + } else { + $this->_attributesToInclude = $allowedAttrs; + } + } + return $this->_attributesToInclude; + } + + /** + * Filter data for write operations + * + * @param array $requestData + * @return array + */ + public function in(array $requestData) + { + $allowedAttributes = $this->getAllowedAttributes(Mage_Api2_Model_Resource::OPERATION_ATTRIBUTE_WRITE); + + return $this->_filter($allowedAttributes, $requestData); + } + + /** + * Filter data before output + * + * @param array $retrievedData + * @return array + */ + public function out(array $retrievedData) + { + return $this->_filter($this->getAttributesToInclude(), $retrievedData); + } +} diff --git a/app/code/core/Mage/Api2/Model/Acl/Filter/Attribute.php b/app/code/core/Mage/Api2/Model/Acl/Filter/Attribute.php new file mode 100644 index 0000000000..4dab281917 --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Acl/Filter/Attribute.php @@ -0,0 +1,77 @@ + + * @method Mage_Api2_Model_Resource_Acl_Filter_Attribute_Collection getCollection() + * @method Mage_Api2_Model_Resource_Acl_Filter_Attribute_Collection getResourceCollection() + * @method Mage_Api2_Model_Resource_Acl_Filter_Attribute getResource() + * @method Mage_Api2_Model_Resource_Acl_Filter_Attribute _getResource() + * @method string getUserType() + * @method Mage_Api2_Model_Acl_Filter_Attribute setUserType() setUserType(string $type) + * @method string getResourceId() + * @method Mage_Api2_Model_Acl_Filter_Attribute setResourceId() setResourceId(string $resource) + * @method string getOperation() + * @method Mage_Api2_Model_Acl_Filter_Attribute setOperation() setOperation(string $operation) + * @method string getAllowedAttributes() + * @method Mage_Api2_Model_Acl_Filter_Attribute setAllowedAttributes() setAllowedAttributes(string $attributes) + */ +class Mage_Api2_Model_Acl_Filter_Attribute extends Mage_Core_Model_Abstract +{ + /** + * Permissions model + * + * @var Mage_Api2_Model_Acl_Filter_Attribute_ResourcePermission + */ + protected $_permissionModel; + + /** + * Initialize resource model + * + * @return void + */ + protected function _construct() + { + $this->_init('api2/acl_filter_attribute'); + } + + /** + * Get pairs resources-permissions for current attribute + * + * @return Mage_Api2_Model_Acl_Filter_Attribute_ResourcePermission + */ + public function getPermissionModel() + { + if (null == $this->_permissionModel) { + $this->_permissionModel = Mage::getModel('api2/acl_filter_attribute_resourcePermission'); + } + return $this->_permissionModel; + } +} diff --git a/app/code/core/Mage/Api2/Model/Acl/Filter/Attribute/Operation.php b/app/code/core/Mage/Api2/Model/Acl/Filter/Attribute/Operation.php new file mode 100644 index 0000000000..1b19c00b0d --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Acl/Filter/Attribute/Operation.php @@ -0,0 +1,67 @@ + + */ +class Mage_Api2_Model_Acl_Filter_Attribute_Operation +{ + /** + * Get options paramets + * + * @return array + */ + static public function toOptionArray() + { + return array( + array( + 'value' => Mage_Api2_Model_Resource::OPERATION_ATTRIBUTE_READ, + 'label' => Mage::helper('api2')->__('Read') + ), + array( + 'value' => Mage_Api2_Model_Resource::OPERATION_ATTRIBUTE_WRITE, + 'label' => Mage::helper('api2')->__('Write') + ) + ); + } + + /** + * Get options in "key-value" format + * + * @return array + */ + static public function toArray() + { + return array( + Mage_Api2_Model_Resource::OPERATION_ATTRIBUTE_READ => Mage::helper('api2')->__('Read'), + Mage_Api2_Model_Resource::OPERATION_ATTRIBUTE_WRITE => Mage::helper('api2')->__('Write') + ); + } +} diff --git a/app/code/core/Mage/Api2/Model/Acl/Filter/Attribute/ResourcePermission.php b/app/code/core/Mage/Api2/Model/Acl/Filter/Attribute/ResourcePermission.php new file mode 100644 index 0000000000..161883174f --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Acl/Filter/Attribute/ResourcePermission.php @@ -0,0 +1,181 @@ + + */ +class Mage_Api2_Model_Acl_Filter_Attribute_ResourcePermission + implements Mage_Api2_Model_Acl_PermissionInterface +{ + /** + * Resources permissions + * + * @var array + */ + protected $_resourcesPermissions; + + /** + * Filter item value + * + * @var string + */ + protected $_userType; + + /** + * Flag if resource has entity only attributes + * + * @var bool + */ + protected $_hasEntityOnlyAttributes = false; + + /** + * Get resources permissions for selected role + * + * @return array + */ + public function getResourcesPermissions() + { + if (null === $this->_resourcesPermissions) { + $rulesPairs = array(); + + if ($this->_userType) { + $allowedAttributes = array(); + + /** @var $rules Mage_Api2_Model_Resource_Acl_Filter_Attribute_Collection */ + $rules = Mage::getResourceModel('api2/acl_filter_attribute_collection'); + $rules->addFilterByUserType($this->_userType); + + foreach ($rules as $rule) { + if (Mage_Api2_Model_Acl_Global_Rule::RESOURCE_ALL === $rule->getResourceId()) { + $rulesPairs[$rule->getResourceId()] = Mage_Api2_Model_Acl_Global_Rule_Permission::TYPE_ALLOW; + } + + /** @var $rule Mage_Api2_Model_Acl_Filter_Attribute */ + if (null !== $rule->getAllowedAttributes()) { + $allowedAttributes[$rule->getResourceId()][$rule->getOperation()] = explode( + ',', $rule->getAllowedAttributes() + ); + } + } + + /** @var $config Mage_Api2_Model_Config */ + $config = Mage::getModel('api2/config'); + + /** @var $operationSource Mage_Api2_Model_Acl_Filter_Attribute_Operation */ + $operationSource = Mage::getModel('api2/acl_filter_attribute_operation'); + + foreach ($config->getResourcesTypes() as $resource) { + $resourceUserPrivileges = $config->getResourceUserPrivileges($resource, $this->_userType); + + if (!$resourceUserPrivileges) { // skip user without any privileges for resource + continue; + } + $operations = $operationSource->toArray(); + + if (empty($resourceUserPrivileges[Mage_Api2_Model_Resource::OPERATION_CREATE]) + && empty($resourceUserPrivileges[Mage_Api2_Model_Resource::OPERATION_UPDATE]) + ) { + unset($operations[Mage_Api2_Model_Resource::OPERATION_ATTRIBUTE_WRITE]); + } + if (empty($resourceUserPrivileges[Mage_Api2_Model_Resource::OPERATION_RETRIEVE])) { + unset($operations[Mage_Api2_Model_Resource::OPERATION_ATTRIBUTE_READ]); + } + if (!$operations) { // skip resource without any operations allowed + continue; + } + try { + /** @var $resourceModel Mage_Api2_Model_Resource */ + $resourceModel = Mage::getModel($config->getResourceModel($resource)); + if ($resourceModel) { + $resourceModel->setResourceType($resource) + ->setUserType($this->_userType); + + foreach ($operations as $operation => $operationLabel) { + if (!$this->_hasEntityOnlyAttributes + && $config->getResourceEntityOnlyAttributes($resource, $this->_userType, $operation) + ) { + $this->_hasEntityOnlyAttributes = true; + } + $availableAttributes = $resourceModel->getAvailableAttributes( + $this->_userType, + $operation + ); + asort($availableAttributes); + foreach ($availableAttributes as $attribute => $attributeLabel) { + $status = isset($allowedAttributes[$resource][$operation]) + && in_array($attribute, $allowedAttributes[$resource][$operation]) + ? Mage_Api2_Model_Acl_Global_Rule_Permission::TYPE_ALLOW + : Mage_Api2_Model_Acl_Global_Rule_Permission::TYPE_DENY; + + $rulesPairs[$resource]['operations'][$operation]['attributes'][$attribute] = array( + 'status' => $status, + 'title' => $attributeLabel + ); + } + } + } + } catch (Exception $e) { + // getModel() throws exception when application is in development mode + Mage::logException($e); + } + } + } + $this->_resourcesPermissions = $rulesPairs; + } + return $this->_resourcesPermissions; + } + + /** + * Set filter value + * + * Set user type + * + * @param string $userType + * @return Mage_Api2_Model_Acl_Filter_Attribute_ResourcePermission + */ + public function setFilterValue($userType) + { + if (!array_key_exists($userType, Mage_Api2_Model_Auth_User::getUserTypes())) { + throw new Exception('Unknown user type.'); + } + $this->_userType = $userType; + return $this; + } + + /** + * Get flag value + * + * @return bool + */ + public function getHasEntityOnlyAttributes() + { + return $this->_hasEntityOnlyAttributes; + } +} diff --git a/app/code/core/Mage/Api2/Model/Acl/Global.php b/app/code/core/Mage/Api2/Model/Acl/Global.php new file mode 100644 index 0000000000..a20b4ae3a6 --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Acl/Global.php @@ -0,0 +1,65 @@ + + */ +class Mage_Api2_Model_Acl_Global +{ + /** + * Check if the operation is allowed on resources of given type type for given user type/role + * + * @param Mage_Api2_Model_Auth_User_Abstract $apiUser + * @param string $resourceType + * @param string $operation + * @return boolean + * @throws Mage_Api2_Exception + */ + public function isAllowed(Mage_Api2_Model_Auth_User_Abstract $apiUser, $resourceType, $operation) + { + // skip user without role, e.g. Customer + if (null === $apiUser->getRole()) { + return true; + } + /** @var $aclInstance Mage_Api2_Model_Acl */ + $aclInstance = Mage::getSingleton( + 'api2/acl', + array('resource_type' => $resourceType, 'operation' => $operation) + ); + + if (!$aclInstance->hasRole($apiUser->getRole())) { + throw new Mage_Api2_Exception('Role not found', Mage_Api2_Model_Server::HTTP_UNAUTHORIZED); + } + if (!$aclInstance->has($resourceType)) { + throw new Mage_Api2_Exception('Resource not found', Mage_Api2_Model_Server::HTTP_NOT_FOUND); + } + return $aclInstance->isAllowed($apiUser->getRole(), $resourceType, $operation); + } +} diff --git a/app/code/core/Mage/Api2/Model/Acl/Global/Role.php b/app/code/core/Mage/Api2/Model/Acl/Global/Role.php new file mode 100644 index 0000000000..512361c93a --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Acl/Global/Role.php @@ -0,0 +1,185 @@ + + * @method Mage_Api2_Model_Resource_Acl_Global_Role_Collection getCollection() + * @method Mage_Api2_Model_Resource_Acl_Global_Role_Collection getResourceCollection() + * @method Mage_Api2_Model_Resource_Acl_Global_Role getResource() + * @method Mage_Api2_Model_Resource_Acl_Global_Role _getResource() + * @method string getCreatedAt() + * @method Mage_Api2_Model_Acl_Global_Role setCreatedAt() setCreatedAt(string $createdAt) + * @method string getUpdatedAt() + * @method Mage_Api2_Model_Acl_Global_Role setUpdatedAt() setUpdatedAt(string $updatedAt) + * @method string getRoleName() + * @method Mage_Api2_Model_Acl_Global_Role setRoleName() setRoleName(string $roleName) + */ +class Mage_Api2_Model_Acl_Global_Role extends Mage_Core_Model_Abstract +{ + /**#@+ + * System roles identifiers + */ + const ROLE_GUEST_ID = 1; + const ROLE_CUSTOMER_ID = 2; + /**#@-*/ + + /**#@+ + * Config node identifiers + */ + const ROLE_CONFIG_NODE_NAME_GUEST = 'guest'; + const ROLE_CONFIG_NODE_NAME_CUSTOMER = 'customer'; + const ROLE_CONFIG_NODE_NAME_ADMIN = 'admin'; + /**#@-*/ + + /** + * Permissions model + * + * @var Mage_Api2_Model_Acl_Global_Rule_ResourcePermission + */ + protected $_permissionModel; + + /** + * Initialize resource model + * + * @return void + */ + protected function _construct() + { + $this->_init('api2/acl_global_role'); + } + + /** + * Before save actions + * + * @return Mage_Api2_Model_Acl_Global_Role + */ + protected function _beforeSave() + { + if ($this->isObjectNew() && null === $this->getCreatedAt()) { + $this->setCreatedAt(Varien_Date::now()); + } else { + $this->setUpdatedAt(Varien_Date::now()); + } + + //check and protect guest role + if (Mage_Api2_Model_Acl_Global_Role::isSystemRole($this) + && $this->getRoleName() != $this->getOrigData('role_name')) { + + /** @var $helper Mage_Core_Helper_Data */ + $helper = Mage::helper('core'); + + Mage::throwException( + Mage::helper('api2')->__('%s role is a special one and can\'t be changed.', + $helper->escapeHtml($this->getRoleName())) + ); + } + + parent::_beforeSave(); + return $this; + } + + /** + * Perform checks before role delete + * + * @return Mage_Api2_Model_Acl_Global_Role + */ + protected function _beforeDelete() + { + if (Mage_Api2_Model_Acl_Global_Role::isSystemRole($this)) { + /** @var $helper Mage_Core_Helper_Data */ + $helper = Mage::helper('core'); + + Mage::throwException( + Mage::helper('api2')->__('%s role is a special one and can\'t be deleted.', + $helper->escapeHtml($this->getRoleName())) + ); + } + + parent::_beforeDelete(); + return $this; + } + + /** + * Get pairs resources-permissions for current role + * + * @return Mage_Api2_Model_Acl_Global_Rule_ResourcePermission + */ + public function getPermissionModel() + { + if (null == $this->_permissionModel) { + $this->_permissionModel = Mage::getModel('api2/acl_global_rule_resourcePermission'); + } + return $this->_permissionModel; + } + + /** + * Retrieve system roles + * + * @return array + */ + static public function getSystemRoles() + { + return array( + self::ROLE_GUEST_ID, + self::ROLE_CUSTOMER_ID + ); + } + + /** + * Get role system belonging + * + * @param Mage_Api2_Model_Acl_Global_Role $role + * @return bool + */ + public static function isSystemRole($role) + { + return in_array($role->getId(), self::getSystemRoles()); + } + + /** + * Get config node identifiers + * + * @return string + */ + public function getConfigNodeName() + { + switch ($this->getId()) { + case self::ROLE_GUEST_ID: + $roleNodeName = self::ROLE_CONFIG_NODE_NAME_GUEST; + break; + case self::ROLE_CUSTOMER_ID: + $roleNodeName = self::ROLE_CONFIG_NODE_NAME_CUSTOMER; + break; + default: + $roleNodeName = self::ROLE_CONFIG_NODE_NAME_ADMIN; + } + return $roleNodeName; + } +} diff --git a/app/code/core/Mage/Api2/Model/Acl/Global/Rule.php b/app/code/core/Mage/Api2/Model/Acl/Global/Rule.php new file mode 100644 index 0000000000..31f76c21bb --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Acl/Global/Rule.php @@ -0,0 +1,65 @@ + + * @method Mage_Api2_Model_Resource_Acl_Global_Rule_Collection getCollection() + * @method Mage_Api2_Model_Resource_Acl_Global_Rule_Collection getResourceCollection() + * @method Mage_Api2_Model_Resource_Acl_Global_Rule getResource() + * @method Mage_Api2_Model_Resource_Acl_Global_Rule _getResource() + * @method int getRoleId() + * @method Mage_Api2_Model_Acl_Global_Rule setRoleId() setRoleId(int $roleId) + * @method string getResourceId() + * @method Mage_Api2_Model_Acl_Global_Rule setResourceId() setResourceId(string $resource) + * @method string getPrivilege() + * @method int getPermission() + * @method Mage_Api2_Model_Acl_Global_Rule setPermission() setPermission(int $permission) + * @method string getPrivilege() + * @method Mage_Api2_Model_Acl_Global_Rule setPrivilege() setPrivilege(string $privilege) + * @method string getAllowedAttributes() + * @method Mage_Api2_Model_Acl_Global_Rule setAllowedAttributes() setAllowedAttributes(string $allowedAttributes) + */ +class Mage_Api2_Model_Acl_Global_Rule extends Mage_Core_Model_Abstract +{ + /** + * Root resource ID "all" + */ + const RESOURCE_ALL = 'all'; + + /** + * Initialize resource model + * + * @return void + */ + protected function _construct() + { + $this->_init('api2/acl_global_rule'); + } +} diff --git a/app/code/core/Mage/Api2/Model/Acl/Global/Rule/Permission.php b/app/code/core/Mage/Api2/Model/Acl/Global/Rule/Permission.php new file mode 100644 index 0000000000..2c96262518 --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Acl/Global/Rule/Permission.php @@ -0,0 +1,74 @@ + + */ +class Mage_Api2_Model_Acl_Global_Rule_Permission +{ + /**#@+ + * Source keys + */ + const TYPE_ALLOW = 1; + const TYPE_DENY = 0; + /**#@-*/ + + /** + * Get options parameters + * + * @return array + */ + static public function toOptionArray() + { + return array( + array( + 'value' => self::TYPE_DENY, + 'label' => Mage::helper('api2')->__('Deny') + ), + array( + 'value' => self::TYPE_ALLOW, + 'label' => Mage::helper('api2')->__('Allow') + ), + ); + } + + /** + * Get options in "key-value" format + * + * @return array + */ + static public function toArray() + { + return array( + self::TYPE_DENY => Mage::helper('api2')->__('Deny'), + self::TYPE_ALLOW => Mage::helper('api2')->__('Allow'), + ); + } +} diff --git a/app/code/core/Mage/Api2/Model/Acl/Global/Rule/Privilege.php b/app/code/core/Mage/Api2/Model/Acl/Global/Rule/Privilege.php new file mode 100644 index 0000000000..f105df9402 --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Acl/Global/Rule/Privilege.php @@ -0,0 +1,77 @@ + + */ +class Mage_Api2_Model_Acl_Global_Rule_Privilege +{ + /** + * Get options parameters + * + * @return array + */ + static public function toOptionArray() + { + return array( + array( + 'value' => Mage_Api2_Model_Resource::OPERATION_CREATE, + 'label' => Mage::helper('api2')->__('Create') + ), + array( + 'value' => Mage_Api2_Model_Resource::OPERATION_RETRIEVE, + 'label' => Mage::helper('api2')->__('Retrieve') + ), + array( + 'value' => Mage_Api2_Model_Resource::OPERATION_UPDATE, + 'label' => Mage::helper('api2')->__('Update') + ), + array( + 'value' => Mage_Api2_Model_Resource::OPERATION_DELETE, + 'label' => Mage::helper('api2')->__('Delete') + ) + ); + } + + /** + * Get options in "key-value" format + * + * @return array + */ + static public function toArray() + { + return array( + Mage_Api2_Model_Resource::OPERATION_CREATE => Mage::helper('api2')->__('Create'), + Mage_Api2_Model_Resource::OPERATION_RETRIEVE => Mage::helper('api2')->__('Retrieve'), + Mage_Api2_Model_Resource::OPERATION_UPDATE => Mage::helper('api2')->__('Update'), + Mage_Api2_Model_Resource::OPERATION_DELETE => Mage::helper('api2')->__('Delete') + ); + } +} diff --git a/app/code/core/Mage/Api2/Model/Acl/Global/Rule/ResourcePermission.php b/app/code/core/Mage/Api2/Model/Acl/Global/Rule/ResourcePermission.php new file mode 100644 index 0000000000..4904c98606 --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Acl/Global/Rule/ResourcePermission.php @@ -0,0 +1,122 @@ + + */ +class Mage_Api2_Model_Acl_Global_Rule_ResourcePermission + implements Mage_Api2_Model_Acl_PermissionInterface +{ + /** + * Resources permissions + * + * @var array + */ + protected $_resourcesPermissions; + + /** + * Role + * + * @var Mage_Api2_Model_Acl_Global_Role + */ + protected $_role; + + /** + * Get resources permissions for selected role + * + * @return array + */ + public function getResourcesPermissions() + { + if (null === $this->_resourcesPermissions) { + $roleConfigNodeName = $this->_role->getConfigNodeName(); + $rulesPairs = array(); + $allowedType = Mage_Api2_Model_Acl_Global_Rule_Permission::TYPE_ALLOW; + + if ($this->_role) { + /** @var $rules Mage_Api2_Model_Resource_Acl_Global_Rule_Collection */ + $rules = Mage::getResourceModel('api2/acl_global_rule_collection'); + $rules->addFilterByRoleId($this->_role->getId()); + + /** @var $rule Mage_Api2_Model_Acl_Global_Rule */ + foreach ($rules as $rule) { + $resourceId = $rule->getResourceId(); + $rulesPairs[$resourceId]['privileges'][$roleConfigNodeName][$rule->getPrivilege()] = $allowedType; + } + } else { + //make resource "all" as default for new item + $rulesPairs = array(Mage_Api2_Model_Acl_Global_Rule::RESOURCE_ALL => $allowedType); + } + + //set permissions to resources + /** @var $config Mage_Api2_Model_Config */ + $config = Mage::getModel('api2/config'); + /** @var $privilegeSource Mage_Api2_Model_Acl_Global_Rule_Privilege */ + $privilegeSource = Mage::getModel('api2/acl_global_rule_privilege'); + $privileges = array_keys($privilegeSource->toArray()); + + /** @var $node Varien_Simplexml_Element */ + foreach ($config->getResources() as $resourceType => $node) { + $resourceId = (string)$resourceType; + $allowedRoles = (array)$node->privileges; + $allowedPrivileges = array(); + if (isset($allowedRoles[$roleConfigNodeName])) { + $allowedPrivileges = $allowedRoles[$roleConfigNodeName]; + } + foreach ($privileges as $privilege) { + if (empty($allowedPrivileges[$privilege]) + && isset($rulesPairs[$resourceId][$roleConfigNodeName]['privileges'][$privilege]) + ) { + unset($rulesPairs[$resourceId][$roleConfigNodeName]['privileges'][$privilege]); + } elseif (!empty($allowedPrivileges[$privilege]) + && !isset($rulesPairs[$resourceId][$roleConfigNodeName]['privileges'][$privilege]) + ) { + $deniedType = Mage_Api2_Model_Acl_Global_Rule_Permission::TYPE_DENY; + $rulesPairs[$resourceId]['privileges'][$roleConfigNodeName][$privilege] = $deniedType; + } + } + } + $this->_resourcesPermissions = $rulesPairs; + } + return $this->_resourcesPermissions; + } + + /** + * Set filter value + * + * @param Mage_Api2_Model_Acl_Global_Role $role + */ + public function setFilterValue($role) + { + if ($role && $role->getId()) { + $this->_role = $role; + } + } +} diff --git a/app/code/core/Mage/Api2/Model/Acl/Global/Rule/Tree.php b/app/code/core/Mage/Api2/Model/Acl/Global/Rule/Tree.php new file mode 100644 index 0000000000..3b6ebb1069 --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Acl/Global/Rule/Tree.php @@ -0,0 +1,546 @@ + + */ +class Mage_Api2_Model_Acl_Global_Rule_Tree extends Mage_Core_Helper_Abstract +{ + /**#@+ + * Tree types + */ + const TYPE_ATTRIBUTE = 'attribute'; + const TYPE_PRIVILEGE = 'privilege'; + /**#@-*/ + + /**#@+ + * Names + */ + const NAME_CHILDREN = 'children'; + const NAME_PRIVILEGE = 'privilege'; + const NAME_OPERATION = 'operation'; + const NAME_ATTRIBUTE = 'attribute'; + const NAME_RESOURCE = 'resource'; + const NAME_RESOURCE_GROUPS = 'resource_groups'; + const NAME_GROUP = 'group'; + /**#@-*/ + + /** + * Separator for tree ID + */ + const ID_SEPARATOR = '-'; + + /** + * Role + * + * @var Mage_Api2_Model_Acl_Global_Role + */ + protected $_role; + + /** + * Resources permissions + * + * @var array + */ + protected $_resourcesPermissions; + + /** + * Resources from config model + * + * @var Varien_Simplexml_Element + */ + protected $_resourcesConfig; + + /** + * Exist privileges + * + * @var array + */ + protected $_existPrivileges; + + /** + * Exist operations + * + * @var array + */ + protected $_existOperations; + + /** + * Tree type + * + * @var string + */ + protected $_type; + + /** + * Initialized + * + * @var bool + */ + protected $_initialized = false; + + /** + * Flag if resource has entity only attributes + * + * @var bool + */ + protected $_hasEntityOnlyAttributes = false; + + /** + * Constructor + * + * In the constructor should be set tree type: attributes or privileges. + * Attributes for tree with resources, operations and attributes. + * Privileges for tree with resources and privileges. + * + * @param array $options + */ + public function __construct($options) + { + $this->_type = $options['type']; + + switch ($this->_type) { + case self::TYPE_ATTRIBUTE: + /** @var $operationSource Mage_Api2_Model_Acl_Filter_Attribute_Operation */ + $operationSource = Mage::getModel('api2/acl_filter_attribute_operation'); + $this->_existOperations = $operationSource->toArray(); + break; + + case self::TYPE_PRIVILEGE: + /** @var $privilegeSource Mage_Api2_Model_Acl_Global_Rule_Privilege */ + $privilegeSource = Mage::getModel('api2/acl_global_rule_privilege'); + $this->_existPrivileges = $privilegeSource->toArray(); + break; + + default: + throw new Exception(sprintf('Unknown tree type "%s".', $this->_type)); + break; + } + } + + /** + * Initialize block + * + * @return Mage_Api2_Model_Acl_Global_Rule_Tree + * @throws Exception + */ + protected function _init() + { + if ($this->_initialized) { + return $this; + } + + /** @var $config Mage_Api2_Model_Config */ + $config = Mage::getModel('api2/config'); + $this->_resourcesConfig = $config->getResourceGroups(); + + if ($this->_type == self::TYPE_ATTRIBUTE && !$this->_existOperations) { + throw new Exception('Operations is not set'); + } + + if ($this->_type == self::TYPE_PRIVILEGE && !$this->_existPrivileges) { + throw new Exception('Privileges is not set.'); + } + + return $this; + } + + /** + * Convert to array serialized post data from tree grid + * + * @return array + */ + public function getPostResources() + { + $isAll = Mage::app()->getRequest()->getParam(Mage_Api2_Model_Acl_Global_Rule::RESOURCE_ALL); + $allow = Mage_Api2_Model_Acl_Global_Rule_Permission::TYPE_ALLOW; + if ($isAll) { + $resources = array( + Mage_Api2_Model_Acl_Global_Rule::RESOURCE_ALL => array( + null => $allow + ) + ); + } else { + $resources = array(); + $checkedResources = explode(',', Mage::app()->getRequest()->getParam('resource')); + $prefixResource = self::NAME_RESOURCE . self::ID_SEPARATOR; + switch ($this->_type) { + case self::TYPE_PRIVILEGE: + $prefixPrivilege = self::NAME_PRIVILEGE . self::ID_SEPARATOR; + $nameResource = null; + foreach ($checkedResources as $i => $item) { + if (0 === strpos($item, $prefixResource)) { + $nameResource = substr($item, mb_strlen($prefixResource, 'UTF-8')); + $resources[$nameResource] = array(); + } elseif (0 === strpos($item, $prefixPrivilege)) { + $name = substr($item, mb_strlen($prefixPrivilege, 'UTF-8')); + $namePrivilege = str_replace($nameResource . self::ID_SEPARATOR, '', $name); + $resources[$nameResource][$namePrivilege] = $allow; + } else { + unset($checkedResources[$i]); + } + } + break; + + case self::TYPE_ATTRIBUTE: + $prefixOperation = self::NAME_OPERATION . self::ID_SEPARATOR; + $prefixAttribute = self::NAME_ATTRIBUTE . self::ID_SEPARATOR; + $nameResource = null; + foreach ($checkedResources as $i => $item) { + if (0 === strpos($item, $prefixResource)) { + $nameResource = substr($item, mb_strlen($prefixResource, 'UTF-8')); + $resources[$nameResource] = array(); + } elseif (0 === strpos($item, $prefixOperation)) { + $name = substr($item, mb_strlen($prefixOperation, 'UTF-8')); + $operationName = str_replace($nameResource . self::ID_SEPARATOR, '', $name); + $resources[$nameResource][$operationName] = array(); + } elseif (0 === strpos($item, $prefixAttribute)) { + $name = substr($item, mb_strlen($prefixOperation, 'UTF-8')); + $attributeName = str_replace( + $nameResource . self::ID_SEPARATOR . $operationName . self::ID_SEPARATOR, + '', + $name + ); + $resources[$nameResource][$operationName][$attributeName] = $allow; + } else { + unset($checkedResources[$i]); + } + } + break; + + //no default + } + } + return $resources; + } + + /** + * Check if everything is allowed + * + * @return boolean + */ + public function getEverythingAllowed() + { + $this->_init(); + + $all = Mage_Api2_Model_Acl_Global_Rule::RESOURCE_ALL; + return !empty($this->_resourcesPermissions[$all]); + } + + /** + * Get tree resources + * + * @return array + */ + public function getTreeResources() + { + $this->_init(); + $root = $this->_getTreeNode($this->_resourcesConfig, 1); + return isset($root[self::NAME_CHILDREN]) ? $root[self::NAME_CHILDREN] : array(); + } + + /** + * Get tree node + * + * @param Varien_Simplexml_Element|array $node + * @param int $level + * @return array + */ + protected function _getTreeNode($node, $level = 0) + { + $item = array(); + + $isResource = false; + $isGroup = false; + $name = null; + + if ($level != 0) { + $name = $node->getName(); + if (!(int) $node->resource) { + if (self::NAME_RESOURCE_GROUPS != $name) { + $isGroup = true; + $item['id'] = self::NAME_GROUP . self::ID_SEPARATOR . $name; + } + $item['text'] = (string) $node->title; + } else { + $isResource = true; + $item['id'] = self::NAME_RESOURCE . self::ID_SEPARATOR . $name; + $item['text'] = $this->__('%s', (string) $node->title); + } + $item['checked'] = false; + $item['sort_order'] = isset($node->sort_order) ? (string) $node->sort_order : 0; + } + if (isset($node->children)) { + $children = $node->children->children(); + } else { + $children = $node->children(); + } + + if (empty($children)) { + /** + * Node doesn't have any child nodes + * and it should be skipped + */ + return $item; + } + + $item[self::NAME_CHILDREN] = array(); + + if ($isResource) { + if (self::TYPE_ATTRIBUTE == $this->_type) { + if (!$this->_addOperations($item, $node, $name)) { + return null; + } + } elseif (self::TYPE_PRIVILEGE == $this->_type) { + if (!$this->_addPrivileges($item, $node, $name)) { + return null; + } + } + } + + /** @var $child Varien_Simplexml_Element */ + foreach ($children as $child) { + if ($child->getName() != 'title' && $child->getName() != 'sort_order') { + if (!(string) $child->title) { + continue; + } + + if ($level != 0) { + $subNode = $this->_getTreeNode($child, $level + 1); + if (!$subNode) { + continue; + } + //if sub-node check then check current node + if (!empty($subNode['checked'])) { + $item['checked'] = true; + } + $item[self::NAME_CHILDREN][] = $subNode; + } else { + $item = $this->_getTreeNode($child, $level + 1); + } + } + } + if (!empty($item[self::NAME_CHILDREN])) { + usort($item[self::NAME_CHILDREN], array($this, '_sortTree')); + } elseif ($isGroup) { + //skip empty group + return null; + } + return $item; + } + + /** + * Add privileges + * + * @param array $item Tree node + * @param Varien_Simplexml_Element $node XML node + * @param string $name Resource name + * @return bool + */ + protected function _addPrivileges(&$item, Varien_Simplexml_Element $node, $name) + { + $roleConfigNodeName = $this->getRole()->getConfigNodeName(); + $possibleList = array(); + if (isset($node->privileges)) { + $possibleRoles = $node->privileges->asArray(); + if (isset($possibleRoles[$roleConfigNodeName])) { + $possibleList = $possibleRoles[$roleConfigNodeName]; + } + } + + if (!$possibleList) { + return false; + } + + $cnt = 0; + foreach ($this->_existPrivileges as $key => $title) { + if (empty($possibleList[$key])) { + continue; + } + $checked = !empty($this->_resourcesPermissions[$name]['privileges'][$roleConfigNodeName][$key]); + $item['checked'] = $checked ? $checked : $item['checked']; + $subItem = array( + 'id' => self::NAME_PRIVILEGE . self::ID_SEPARATOR . $name . self::ID_SEPARATOR . $key, + 'text' => $title, + 'checked' => $checked, + 'sort_order' => ++$cnt, + ); + $item[self::NAME_CHILDREN][] = $subItem; + } + return true; + } + + /** + * Add operation + * + * @param array $item Tree node + * @param Varien_Simplexml_Element $node XML node + * @param string $name Resource name + * @return bool + */ + protected function _addOperations(&$item, Varien_Simplexml_Element $node, $name) + { + $cnt = 0; + foreach ($this->_existOperations as $key => $title) { + $subItem = array( + 'id' => self::NAME_OPERATION . self::ID_SEPARATOR . $name . self::ID_SEPARATOR . $key, + 'text' => $title, + 'checked' => false, + 'sort_order' => ++$cnt, + ); + + if (!empty($this->_resourcesPermissions[$name]['operations'][$key]['attributes'])) { + if (!$this->_addAttribute($subItem, $node, $name, $key)) { + $cnt--; + continue; + } + } else { + $cnt--; + continue; + } + if (!empty($subItem['checked'])) { + $item['checked'] = true; + } + $item[self::NAME_CHILDREN][] = $subItem; + } + if (!$cnt) { + return false; + } + return true; + } + + /** + * Add privileges + * + * @param array $item Tree node + * @param Varien_Simplexml_Element $node XML node + * @param string $name Node name + * @param string $privilege Privilege name + * @return bool + */ + protected function _addAttribute(&$item, Varien_Simplexml_Element $node, $name, $privilege) + { + $cnt = 0; + foreach ($this->_resourcesPermissions[$name]['operations'][$privilege]['attributes'] as $key => $attribute) { + $title = $attribute['title']; + $status = $attribute['status']; + + $checked = $status == Mage_Api2_Model_Acl_Global_Rule_Permission::TYPE_ALLOW; + $item['checked'] = $checked ? $checked : $item['checked']; + $item[self::NAME_CHILDREN][] = array( + 'id' => self::NAME_ATTRIBUTE . self::ID_SEPARATOR . $name . self::ID_SEPARATOR . $privilege + . self::ID_SEPARATOR . $key, + 'text' => $title, + 'checked' => $checked, + 'sort_order' => ++$cnt, + ); + } + + return true; + } + + /** + * Compare two nodes of the Resource Tree + * + * @param array $a + * @param array $b + * @return int + */ + protected function _sortTree($a, $b) + { + return $a['sort_order'] < $b['sort_order'] ? -1 : ($a['sort_order'] > $b['sort_order'] ? 1 : 0); + } + + /** + * Set role + * + * @param Mage_Api2_Model_Acl_Global_Role $role + * @return Mage_Api2_Model_Acl_Global_Rule_Tree + */ + public function setRole($role) + { + $this->_role = $role; + return $this; + } + + /** + * Get role + * + * @return Mage_Api2_Model_Acl_Global_Role + */ + public function getRole() + { + return $this->_role; + } + + /** + * Set resources permissions + * + * @param array $resourcesPermissions + * @return Mage_Api2_Model_Acl_Global_Rule_Tree + */ + public function setResourcesPermissions($resourcesPermissions) + { + $this->_resourcesPermissions = $resourcesPermissions; + return $this; + } + + /** + * Get resources permissions + * + * @return array + */ + public function getResourcesPermissions() + { + return $this->_resourcesPermissions; + } + + /** + * Set has entity only attributes flag + * + * @param bool $hasEntityOnlyAttributes + * @return Mage_Api2_Model_Acl_Global_Rule_Tree + */ + public function setHasEntityOnlyAttributes($hasEntityOnlyAttributes) + { + $this->_hasEntityOnlyAttributes = $hasEntityOnlyAttributes; + return $this; + } + + /** + * Get has entity only attributes flag + * + * @return bool + */ + public function getHasEntityOnlyAttributes() + { + return $this->_hasEntityOnlyAttributes; + } +} diff --git a/app/code/core/Mage/Api2/Model/Acl/PermissionInterface.php b/app/code/core/Mage/Api2/Model/Acl/PermissionInterface.php new file mode 100644 index 0000000000..04afbf7c7a --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Acl/PermissionInterface.php @@ -0,0 +1,52 @@ + + */ +interface Mage_Api2_Model_Acl_PermissionInterface +{ + /** + * Get ACL resources permissions + * + * Get permissions list with set permissions + * + * @return array + */ + public function getResourcesPermissions(); + + /** + * Set filter value + * + * @param mixed $filterValue + * @return Mage_Api2_Model_Acl_PermissionInterface + */ + public function setFilterValue($filterValue); +} diff --git a/app/code/core/Mage/Api2/Model/Auth.php b/app/code/core/Mage/Api2/Model/Auth.php new file mode 100644 index 0000000000..37a79c528b --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Auth.php @@ -0,0 +1,80 @@ + + */ +class Mage_Api2_Model_Auth +{ + /** + * Use this type if no authentication adapter is applied + */ + const DEFAULT_USER_TYPE = 'guest'; + + /** + * Figure out API user type and create user model instance + * + * @param Mage_Api2_Model_Request $request + * @throws Exception + * @return Mage_Api2_Model_Auth_User_Abstract + */ + public function authenticate(Mage_Api2_Model_Request $request) + { + /** @var $helper Mage_Api2_Helper_Data */ + $helper = Mage::helper('api2/data'); + $userTypes = $helper->getUserTypes(); + + if (!$userTypes) { + throw new Exception('No allowed user types found'); + } + /** @var $authAdapter Mage_Api2_Model_Auth_Adapter */ + $authAdapter = Mage::getModel('api2/auth_adapter'); + $userParamsObj = $authAdapter->getUserParams($request); + + if (!isset($userTypes[$userParamsObj->type])) { + throw new Mage_Api2_Exception( + 'Invalid user type or type is not allowed', Mage_Api2_Model_Server::HTTP_UNAUTHORIZED + ); + } + /** @var $userModel Mage_Api2_Model_Auth_User_Abstract */ + $userModel = Mage::getModel($userTypes[$userParamsObj->type]); + + if (!$userModel instanceof Mage_Api2_Model_Auth_User_Abstract) { + throw new Exception('User model must to extend Mage_Api2_Model_Auth_User_Abstract'); + } + // check user type consistency + if ($userModel->getType() != $userParamsObj->type) { + throw new Exception('User model type does not match appropriate type in config'); + } + $userModel->setUserId($userParamsObj->id); + + return $userModel; + } +} diff --git a/app/code/core/Mage/Api2/Model/Auth/Adapter.php b/app/code/core/Mage/Api2/Model/Auth/Adapter.php new file mode 100644 index 0000000000..783196f600 --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Auth/Adapter.php @@ -0,0 +1,93 @@ + + */ +class Mage_Api2_Model_Auth_Adapter +{ + /** + * Adapter models + * + * @var array + */ + protected $_adapters = array(); + + /** + * Load adapters configuration and create adapters models + * + * @return Mage_Api2_Model_Auth_Adapter + * @throws Exception + */ + protected function _initAdapters() + { + /** @var $helper Mage_Api2_Helper_Data */ + $helper = Mage::helper('api2'); + + foreach ($helper->getAuthAdapters(true) as $adapterKey => $adapterParams) { + $adapterModel = Mage::getModel($adapterParams['model']); + + if (!$adapterModel instanceof Mage_Api2_Model_Auth_Adapter_Abstract) { + throw new Exception('Authentication adapter must to extend Mage_Api2_Model_Auth_Adapter_Abstract'); + } + $this->_adapters[$adapterKey] = $adapterModel; + } + if (!$this->_adapters) { + throw new Exception('No active authentication adapters found'); + } + return $this; + } + + /** + * Process request and figure out an API user type and its identifier + * + * Returns stdClass object with two properties: type and id + * + * @param Mage_Api2_Model_Request $request + * @return stdClass + */ + public function getUserParams(Mage_Api2_Model_Request $request) + { + $this->_initAdapters(); + + foreach ($this->_adapters as $adapterModel) { + /** @var $adapterModel Mage_Api2_Model_Auth_Adapter_Abstract */ + if ($adapterModel->isApplicableToRequest($request)) { + $userParams = $adapterModel->getUserParams($request); + + if (null !== $userParams->type) { + return $userParams; + } + throw new Mage_Api2_Exception('Can not determine user type', Mage_Api2_Model_Server::HTTP_UNAUTHORIZED); + } + } + return (object) array('type' => Mage_Api2_Model_Auth::DEFAULT_USER_TYPE, 'id' => null); + } +} diff --git a/app/code/core/Mage/Api2/Model/Auth/Adapter/Abstract.php b/app/code/core/Mage/Api2/Model/Auth/Adapter/Abstract.php new file mode 100644 index 0000000000..bbae92f6cd --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Auth/Adapter/Abstract.php @@ -0,0 +1,53 @@ + + */ +abstract class Mage_Api2_Model_Auth_Adapter_Abstract +{ + /** + * Process request and figure out an API user type and its identifier + * + * Returns stdClass object with two properties: type and id + * + * @param Mage_Api2_Model_Request $request + * @return stdClass + */ + abstract public function getUserParams(Mage_Api2_Model_Request $request); + + /** + * Check if request contains authentication info for adapter + * + * @param Mage_Api2_Model_Request $request + * @return boolean + */ + abstract public function isApplicableToRequest(Mage_Api2_Model_Request $request); +} diff --git a/app/code/core/Mage/Api2/Model/Auth/Adapter/Oauth.php b/app/code/core/Mage/Api2/Model/Auth/Adapter/Oauth.php new file mode 100644 index 0000000000..c8a437bb26 --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Auth/Adapter/Oauth.php @@ -0,0 +1,78 @@ + + */ +class Mage_Api2_Model_Auth_Adapter_Oauth extends Mage_Api2_Model_Auth_Adapter_Abstract +{ + /** + * Process request and figure out an API user type and its identifier + * + * Returns stdClass object with two properties: type and id + * + * @param Mage_Api2_Model_Request $request + * @return stdClass + */ + public function getUserParams(Mage_Api2_Model_Request $request) + { + /** @var $oauthServer Mage_OAuth_Model_Server */ + $oauthServer = Mage::getModel('oAuth/server', $request); + $userParamsObj = (object) array('type' => null, 'id' => null); + + try { + $token = $oauthServer->checkAccessRequest(); + $userType = $token->getUserType(); + + if (Mage_OAuth_Model_Token::USER_TYPE_ADMIN == $userType) { + $userParamsObj->id = $token->getAdminId(); + } else { + $userParamsObj->id = $token->getCustomerId(); + } + $userParamsObj->type = $userType; + } catch (Exception $e) { + throw new Mage_Api2_Exception($oauthServer->reportProblem($e), Mage_Api2_Model_Server::HTTP_UNAUTHORIZED); + } + return $userParamsObj; + } + + /** + * Check if request contains authentication info for adapter + * + * @param Mage_Api2_Model_Request $request + * @return boolean + */ + public function isApplicableToRequest(Mage_Api2_Model_Request $request) + { + $headerValue = $request->getHeader('Authorization'); + + return $headerValue && 'oauth' === strtolower(substr($headerValue, 0, 5)); + } +} diff --git a/app/code/core/Mage/Api2/Model/Auth/User.php b/app/code/core/Mage/Api2/Model/Auth/User.php new file mode 100644 index 0000000000..49ba4cd593 --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Auth/User.php @@ -0,0 +1,61 @@ + + */ +class Mage_Api2_Model_Auth_User +{ + /** + * Get options in "key-value" format + * + * @param boolean $asOptionArray OPTIONAL If TRUE - return an options array, plain array - otherwise + * @return array + */ + static public function getUserTypes($asOptionArray = false) + { + $userTypes = array(); + + /** @var $helper Mage_Api2_Helper_Data */ + $helper = Mage::helper('api2'); + + foreach ($helper->getUserTypes() as $modelPath) { + /** @var $userModel Mage_Api2_Model_Auth_User_Abstract */ + $userModel = Mage::getModel($modelPath); + + if ($asOptionArray) { + $userTypes[] = array('value' => $userModel->getType(), 'label' => $userModel->getLabel()); + } else { + $userTypes[$userModel->getType()] = $userModel->getLabel(); + } + } + return $userTypes; + } +} diff --git a/app/code/core/Mage/Api2/Model/Auth/User/Abstract.php b/app/code/core/Mage/Api2/Model/Auth/User/Abstract.php new file mode 100644 index 0000000000..10aa60e39e --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Auth/User/Abstract.php @@ -0,0 +1,96 @@ + + */ +abstract class Mage_Api2_Model_Auth_User_Abstract +{ + /** + * Customer/Admin identifier + * + * @var int + */ + protected $_userId; + + /** + * User Role + * + * @var int + */ + protected $_role; + + /** + * Retrieve user human-readable label + * + * @return string + */ + public function getLabel() + { + return $this->getType(); + } + + /** + * Retrieve user role + * + * @return int + */ + abstract public function getRole(); + + /** + * Retrieve user type + * + * @return string + */ + abstract public function getType(); + + /** + * Retrieve user identifier + * + * @return int + */ + public function getUserId() + { + return $this->_userId; + } + + /** + * Set user identifier + * + * @param int $userId User identifier + * @return Mage_Api2_Model_Auth_User_Abstract + */ + public function setUserId($userId) + { + $this->_userId = $userId; + + return $this; + } +} diff --git a/app/code/core/Mage/Api2/Model/Auth/User/Admin.php b/app/code/core/Mage/Api2/Model/Auth/User/Admin.php new file mode 100644 index 0000000000..7a87d8a226 --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Auth/User/Admin.php @@ -0,0 +1,106 @@ + + */ +class Mage_Api2_Model_Auth_User_Admin extends Mage_Api2_Model_Auth_User_Abstract +{ + /** + * User type + */ + const USER_TYPE = 'admin'; + + /** + * Retrieve user human-readable label + * + * @return string + */ + public function getLabel() + { + return Mage::helper('api2')->__('Admin'); + } + + /** + * Retrieve user role + * + * @return int + * @throws Exception + */ + public function getRole() + { + if (!$this->_role) { + if (!$this->getUserId()) { + throw new Exception('Admin identifier is not set'); + } + + /** @var $collection Mage_Api2_Model_Resource_Acl_Global_Role_Collection */ + $collection = Mage::getModel('api2/acl_global_role')->getCollection(); + $collection->addFilterByAdminId($this->getUserId()); + + /** @var $role Mage_Api2_Model_Acl_Global_Role */ + $role = $collection->getFirstItem(); + if (!$role->getId()) { + throw new Exception('Admin role not found'); + } + + $this->setRole($role->getId()); + } + + return $this->_role; + } + + /** + * Retrieve user type + * + * @return string + */ + public function getType() + { + return self::USER_TYPE; + } + + /** + * Set user role + * + * @param int $role + * @return Mage_Api2_Model_Auth_User_Admin + * @throws Exception + */ + public function setRole($role) + { + if ($this->_role) { + throw new Exception('Admin role has been already set'); + } + $this->_role = $role; + + return $this; + } +} diff --git a/app/code/core/Mage/Api2/Model/Auth/User/Customer.php b/app/code/core/Mage/Api2/Model/Auth/User/Customer.php new file mode 100644 index 0000000000..20cdb14680 --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Auth/User/Customer.php @@ -0,0 +1,80 @@ + + */ +class Mage_Api2_Model_Auth_User_Customer extends Mage_Api2_Model_Auth_User_Abstract +{ + /** + * User type + */ + const USER_TYPE = 'customer'; + + /** + * Retrieve user human-readable label + * + * @return string + */ + public function getLabel() + { + return Mage::helper('api2')->__('Customer'); + } + + /** + * Retrieve user type + * + * @return string + */ + public function getType() + { + return self::USER_TYPE; + } + + /** + * Retrieve user role + * + * @return int + */ + public function getRole() + { + if (!$this->_role) { + /** @var $role Mage_Api2_Model_Acl_Global_Role */ + $role = Mage::getModel('api2/acl_global_role')->load(Mage_Api2_Model_Acl_Global_Role::ROLE_CUSTOMER_ID); + if (!$role->getId()) { + throw new Exception('Customer role not found'); + } + + $this->_role = Mage_Api2_Model_Acl_Global_Role::ROLE_CUSTOMER_ID; + } + + return $this->_role; + } +} diff --git a/app/code/core/Mage/Api2/Model/Auth/User/Guest.php b/app/code/core/Mage/Api2/Model/Auth/User/Guest.php new file mode 100644 index 0000000000..c9367f18d3 --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Auth/User/Guest.php @@ -0,0 +1,80 @@ + + */ +class Mage_Api2_Model_Auth_User_Guest extends Mage_Api2_Model_Auth_User_Abstract +{ + /** + * User type + */ + const USER_TYPE = 'guest'; + + /** + * Retrieve user human-readable label + * + * @return string + */ + public function getLabel() + { + return Mage::helper('api2')->__('Guest'); + } + + /** + * Retrieve user type + * + * @return string + */ + public function getType() + { + return self::USER_TYPE; + } + + /** + * Retrieve user role + * + * @return int + */ + public function getRole() + { + if (!$this->_role) { + /** @var $role Mage_Api2_Model_Acl_Global_Role */ + $role = Mage::getModel('api2/acl_global_role')->load(Mage_Api2_Model_Acl_Global_Role::ROLE_GUEST_ID); + if (!$role->getId()) { + throw new Exception('Guest role not found'); + } + + $this->_role = Mage_Api2_Model_Acl_Global_Role::ROLE_GUEST_ID; + } + + return $this->_role; + } +} diff --git a/app/code/core/Mage/Api2/Model/Config.php b/app/code/core/Mage/Api2/Model/Config.php new file mode 100644 index 0000000000..89048609cc --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Config.php @@ -0,0 +1,449 @@ + + */ +class Mage_Api2_Model_Config extends Varien_Simplexml_Config +{ + /** + * Node name of resource groups + */ + const NODE_RESOURCE_GROUPS = 'resource_groups'; + + /** + * Id for config cache + */ + const CACHE_ID = 'config_api2'; + + /** + * Tag name for config cache + */ + const CACHE_TAG = 'CONFIG_API2'; + + /** + * Is resources added to group + * + * @var boolean + */ + protected $_resourcesGrouped = false; + + /** + * Constructor + * Initializes XML for this configuration + * Local cache configuration + * + * @param string|Varien_Simplexml_Element|null $sourceData + */ + public function __construct($sourceData = null) + { + parent::__construct($sourceData); + + $canUserCache = Mage::app()->useCache('config'); + if ($canUserCache) { + $this->setCacheId(self::CACHE_ID) + ->setCacheTags(array(self::CACHE_TAG)) + ->setCacheChecksum(null) + ->setCache(Mage::app()->getCache()); + + if ($this->loadCache()) { + return; + } + } + + // Load data of config files api2.xml + $config = Mage::getConfig()->loadModulesConfiguration('api2.xml'); + $this->setXml($config->getNode('api2')); + + if ($canUserCache) { + $this->saveCache(); + } + } + + /** + * Fetch all routes of the given api type from config files api2.xml + * + * @param string $apiType + * @throws Mage_Api2_Exception + * @return array + */ + public function getRoutes($apiType) + { + /** @var $helper Mage_Api2_Helper_Data */ + $helper = Mage::helper('api2'); + if (!$helper->isApiTypeSupported($apiType)) { + throw new Mage_Api2_Exception(sprintf('API type "%s" is not supported', $apiType), + Mage_Api2_Model_Server::HTTP_BAD_REQUEST); + } + + $routes = array(); + foreach ($this->getResources() as $resourceKey => $resource) { + if (!$resource->routes) { + continue; + } + + /** @var $routes Varien_Simplexml_Element */ + foreach ($resource->routes->children() as $route) { + $arguments = array( + Mage_Api2_Model_Route_Abstract::PARAM_ROUTE => (string)$route->route, + Mage_Api2_Model_Route_Abstract::PARAM_DEFAULTS => array( + 'model' => (string)$resource->model, + 'type' => (string)$resourceKey, + 'action_type' => (string)$route->action_type + ) + ); + + $routes[] = Mage::getModel('api2/route_' . $apiType, $arguments); + } + } + return $routes; + } + + /** + * Retrieve all resources from config files api2.xml + * + * @return Varien_Simplexml_Element + */ + public function getResources() + { + return $this->getNode('resources')->children(); + } + + /** + * Retrieve all resources types + * + * @return array + */ + public function getResourcesTypes() + { + $list = array(); + + foreach ($this->getResources() as $resourceType => $resourceCfg) { + $list[] = (string) $resourceType; + } + return $list; + } + + /** + * Retrieve all resource groups from config files api2.xml + * + * @return Varien_Simplexml_Element|boolean + */ + public function getResourceGroups() + { + $groups = $this->getXpath('//' . self::NODE_RESOURCE_GROUPS); + if (!$groups) { + return false; + } + + /** @var $groups Varien_Simplexml_Element */ + $groups = $groups[0]; + + if (!$this->_resourcesGrouped) { + /** @var $node Varien_Simplexml_Element */ + foreach ($this->getResources() as $node) { + $result = $node->xpath('group'); + if (!$result) { + continue; + } + $groupName = (string) $result[0]; + if ($groupName) { + $result = $groups->xpath('.//' . $groupName); + if (!$result) { + continue; + } + + /** @var $group Varien_Simplexml_Element */ + $group = $result[0]; + + if (!isset($group->children)) { + $children = new Varien_Simplexml_Element(''); + } else { + $children = $group->children; + } + $node->resource = 1; + $children->appendChild($node); + $group->appendChild($children); + } + } + } + return $groups; + } + + /** + * Retrieve resource group from config files api2.xml + * + * @param string $name + * @return Mage_Core_Model_Config_Element|boolean + */ + public function getResourceGroup($name) + { + $group = $this->getResourceGroups()->xpath('.//' . $name); + if (!$group) { + return false; + } + return $group[0]; + } + + /** + * Retrieve resource by type (node) + * + * @param string $node + * @return Varien_Simplexml_Element|boolean + */ + public function getResource($node) + { + return $this->getNode('resources/' . $node); + } + + /** + * Retrieve resource attributes + * + * @param string $node + * @return array + */ + public function getResourceAttributes($node) + { + $attributes = $this->getNode('resources/' . $node . '/attributes'); + return $attributes ? (array) $attributes : array(); + } + + /** + * Get excluded attributes of API resource + * + * @param string $resource + * @param string $userType + * @param string $operation + * @return array + */ + public function getResourceExcludedAttributes($resource, $userType, $operation) + { + $node = $this->getNode('resources/' . $resource . '/exclude_attributes/' . $userType . '/' . $operation); + $exclAttributes = array(); + + if ($node) { + foreach ($node->children() as $attribute => $status) { + if ((string) $status) { + $exclAttributes[] = $attribute; + } + } + } + return $exclAttributes; + } + + /** + * Get forced attributes of API resource + * + * @param string $resource + * @param string $userType + * @return array + */ + public function getResourceForcedAttributes($resource, $userType) + { + $node = $this->getNode('resources/' . $resource . '/force_attributes/' . $userType); + $forcedAttributes = array(); + + if ($node) { + foreach ($node->children() as $attribute => $status) { + if ((string) $status) { + $forcedAttributes[] = $attribute; + } + } + } + return $forcedAttributes; + } + + /** + * Get included attributes + * + * @param string $resource API resource ID + * @param string $userType API user type + * @param string $operationType Type of operation: one of Mage_Api2_Model_Resource::OPERATION_ATTRIBUTE_... constant + * @return array + */ + public function getResourceIncludedAttributes($resource, $userType, $operationType) + { + $node = $this->getNode('resources/' . $resource . '/include_attributes/' . $userType . '/' . $operationType); + $inclAttributes = array(); + + if ($node) { + foreach ($node->children() as $attribute => $status) { + if ((string) $status) { + $inclAttributes[] = $attribute; + } + } + } + return $inclAttributes; + } + + /** + * Get entity only attributes + * + * @param string $resource API resource ID + * @param string $userType API user type + * @param string $operationType Type of operation: one of Mage_Api2_Model_Resource::OPERATION_ATTRIBUTE_... constant + * @return array + */ + public function getResourceEntityOnlyAttributes($resource, $userType, $operationType) + { + $node = $this->getNode('resources/' . $resource . '/entity_only_attributes/' . $userType . '/' . + $operationType); + $entityOnlyAttributes = array(); + + if ($node) { + foreach ($node->children() as $attribute => $status) { + if ((string) $status) { + $entityOnlyAttributes[] = $attribute; + } + } + } + return $entityOnlyAttributes; + } + + /** + * Retrieve resource working model + * + * @param string $node + * @return string + */ + public function getResourceWorkingModel($node) + { + return (string)$this->getNode('resources/' . $node . '/working_model'); + } + + /** + * Get resource allowed versions sorted in reverse order + * + * @param string $node + * @return array + * @throws Exception + */ + public function getVersions($node) + { + $element = $this->getNode('resources/' . $node . '/versions'); + if (!$element) { + throw new Exception( + sprintf('Resource "%s" does not have node in config.', htmlspecialchars($node)) + ); + } + + $versions = explode(',', (string)$element); + if (count(array_filter($versions, 'is_numeric')) != count($versions)) { + throw new Exception(sprintf('Invalid resource "%s" versions in config.', htmlspecialchars($node))); + } + + rsort($versions, SORT_NUMERIC); + + return $versions; + } + + /** + * Retrieve resource model + * + * @param string $node + * @return string + */ + public function getResourceModel($node) + { + return (string)$this->getNode('resources/' . $node . '/model'); + } + + /** + * Retrieve API user privileges for specified resource + * + * @param string $resource + * @param string $userType + * @return array + */ + public function getResourceUserPrivileges($resource, $userType) + { + $attributes = $this->getNode('resources/' . $resource . '/privileges/' . $userType); + return $attributes ? (array)$attributes : array(); + } + + /** + * Retrieve resource subresources + * + * @param string $node + * @return array + */ + public function getResourceSubresources($node) + { + $subresources = $this->getNode('resources/' . $node . '/subresources'); + return $subresources ? (array)$subresources : array(); + } + + /** + * Get validation config by validator type + * + * @param string $resourceType + * @param string $validatorType + * @return array + */ + public function getValidationConfig($resourceType, $validatorType) + { + $config = $this->getNode('resources/' . $resourceType . '/validators/' . $validatorType); + return $config ? $config->asArray() : array(); + } + + /** + * Get latest version of resource model. If second arg is specified - use it as a limiter + * + * @param string $resourceType Resource type + * @param int $lowerOrEqualsTo OPTIONAL If specified - return version equal or lower to + * @return int + */ + public function getResourceLastVersion($resourceType, $lowerOrEqualsTo = null) + { + $availVersions = $this->getVersions($resourceType); // already ordered in reverse order + $useVersion = reset($availVersions); + + if (null !== $lowerOrEqualsTo) { + foreach ($availVersions as $availVersion) { + if ($availVersion <= $lowerOrEqualsTo) { + $useVersion = $availVersion; + break; + } + } + } + return (int)$useVersion; + } + + /** + * Get route with Mage_Api2_Model_Resource::ACTION_TYPE_ENTITY type + * + * @param string $node + * @return string + */ + public function getRouteWithEntityTypeAction($node) + { + return (string)$this->getNode('resources/' . $node . '/routes/route_entity/route'); + } +} diff --git a/app/code/core/Mage/Api2/Model/Dispatcher.php b/app/code/core/Mage/Api2/Model/Dispatcher.php new file mode 100644 index 0000000000..857ff131fa --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Dispatcher.php @@ -0,0 +1,162 @@ + + */ +class Mage_Api2_Model_Dispatcher +{ + /** + * Template for retrieve resource class name + */ + const RESOURCE_CLASS_TEMPLATE = ':resource_:api_:user_v:version'; + + /** + * API User object + * + * @var Mage_Api2_Model_Auth_User_Abstract + */ + protected $_apiUser; + + /** + * Instantiate resource class, set parameters to the instance, run resource internal dispatch method + * + * @param Mage_Api2_Model_Request $request + * @param Mage_Api2_Model_Response $response + * @return Mage_Api2_Model_Dispatcher + * @throws Mage_Api2_Exception + */ + public function dispatch(Mage_Api2_Model_Request $request, Mage_Api2_Model_Response $response) + { + if (!$request->getModel() || !$request->getApiType()) { + throw new Mage_Api2_Exception( + 'Request does not contains all necessary data', Mage_Api2_Model_Server::HTTP_BAD_REQUEST + ); + } + $model = self::loadResourceModel( + $request->getModel(), + $request->getApiType(), + $this->getApiUser()->getType(), + $this->getVersion($request->getResourceType(), $request->getVersion()) + ); + + $model->setRequest($request); + $model->setResponse($response); + $model->setApiUser($this->getApiUser()); + + $model->dispatch(); + + return $this; + } + + /** + * Pack resource model class path from components and try to load it + * + * @param string $apiType API type + * @param string $userType API User type (e.g. admin, customer, guest) + * @param int $version Requested version + * @return Mage_Api2_Model_Resource + * @throws Mage_Api2_Exception + */ + public static function loadResourceModel($model, $apiType, $userType, $version) + { + $class = strtr( + self::RESOURCE_CLASS_TEMPLATE, + array(':resource' => $model, ':api' => $apiType, ':user' => $userType, ':version' => $version) + ); + + try { + /** @var $modelObj Mage_Api2_Model_Resource */ + $modelObj = Mage::getModel($class); + } catch (Exception $e) { + // getModel() throws exception when in application is in development mode - skip it to next check + } + if (empty($modelObj) || !$modelObj instanceof Mage_Api2_Model_Resource) { + throw new Mage_Api2_Exception('Resource not found', Mage_Api2_Model_Server::HTTP_NOT_FOUND); + } + return $modelObj; + } + + /** + * Set API user object + * + * @param Mage_Api2_Model_Auth_User_Abstract $apiUser + * @return Mage_Api2_Model_Dispatcher + */ + public function setApiUser(Mage_Api2_Model_Auth_User_Abstract $apiUser) + { + $this->_apiUser = $apiUser; + + return $this; + } + + /** + * Get API user object + * + * @return Mage_Api2_Model_Auth_User_Abstract + */ + public function getApiUser() + { + if (!$this->_apiUser) { + throw new Exception('API user is not set.'); + } + + return $this->_apiUser; + } + + /** + * Get correct version of the resource model + * + * @param string $resourceType + * @param string|bool $requestedVersion + * @return int + * @throws Mage_Api2_Exception + */ + public function getVersion($resourceType, $requestedVersion) + { + if (false !== $requestedVersion && !preg_match('/^[1-9]\d*$/', $requestedVersion)) { + throw new Mage_Api2_Exception( + sprintf('Invalid version "%s" requested.', htmlspecialchars($requestedVersion)), + Mage_Api2_Model_Server::HTTP_BAD_REQUEST + ); + } + return $this->getConfig()->getResourceLastVersion($resourceType, $requestedVersion); + } + + /** + * Get config + * + * @return Mage_Api2_Model_Config + */ + public function getConfig() + { + return Mage::getModel('api2/config'); + } +} diff --git a/app/code/core/Mage/Api2/Model/Multicall.php b/app/code/core/Mage/Api2/Model/Multicall.php new file mode 100644 index 0000000000..9ade4b02a3 --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Multicall.php @@ -0,0 +1,278 @@ + + */ +class Mage_Api2_Model_Multicall +{ + + /** + * @var Mage_Api2_Model_Request + */ + protected $_parentCallRequest; + + /** + * @var string + */ + protected $_parentResourceId; + + /** + * Multicall to subresources of specified resource + * + * @param string $parentResourceId + * @param string $parentResourceName + * @param Mage_Api2_Model_Request $parentCallRequest + * @return Mage_Api2_Model_Response + */ + public function call($parentResourceId, $parentResourceName, Mage_Api2_Model_Request $parentCallRequest) + { + $this->_parentResourceId = $parentResourceId; + $this->_parentCallRequest = $parentCallRequest; + $subresources = $this->_getDeclaredSubresources($parentResourceName); + foreach ($subresources as $subresource) { + $this->_callSubresource($subresource); + } + + return $this->_getResponse(); + } + + /** + * Make call to specified subresource with data from request + * + * @param Mage_Core_Model_Config_Element $subresource + * @return Mage_Api2_Model_Multicall + */ + protected function _callSubresource($subresource) + { + $bodyParams = $this->_getRequest()->getBodyParams(); + // check if subresource data exists in request + $requestParamName = (string)$subresource->request_param_name; + if (!(is_array($bodyParams) && array_key_exists($requestParamName, $bodyParams) + && is_array($bodyParams[$requestParamName])) + ) { + return $this; + } + // make internal call + $subresourceType = (string)$subresource->type; + $requestData = $bodyParams[$requestParamName]; + switch ($subresourceType) { + case 'collection': + foreach ($requestData as $subresourceData) { + $this->_internalCall($subresource, $subresourceData); + } + break; + case 'instance': + default: + $this->_internalCall($subresource, $requestData); + break; + } + return $this; + } + + /** + * Make internal call to specified subresource on with specified data via API2 server + * + * @param Mage_Core_Model_Config_Element $subresource + * @param array $requestData + * @throws Mage_Api2_Exception + * @return Mage_Api2_Model_Multicall + */ + protected function _internalCall($subresource, $requestData) + { + try { + if (!is_array($requestData)) { + throw new Mage_Api2_Exception('Invalid data format', Mage_Api2_Model_Server::HTTP_BAD_REQUEST); + } + $subresourceIdKey = (string)$subresource->id_param_name; + /** @var $server Mage_Api2_Model_Server */ + $server = Mage::getSingleton('api2/server'); + + // create subresource item before linking it to main resource + if (!array_key_exists($subresourceIdKey, $requestData)) { + $subresourceCreateResourceName = (string)$subresource->create_resource_name; + $internalRequest = $this->_prepareRequest($subresourceCreateResourceName, $requestData); + /** @var $internalCreateResponse Mage_Api2_Model_Response */ + $internalCreateResponse = Mage::getModel('api2/response'); + $server->internalCall($internalRequest, $internalCreateResponse); + $createdSubresourceInstanceId = $this->_getCreatedResourceId($internalCreateResponse); + if (empty($createdSubresourceInstanceId)) { + throw new Mage_Api2_Exception('Error during subresource creation', + Mage_Api2_Model_Server::HTTP_INTERNAL_ERROR); + } + $requestData[$subresourceIdKey] = $createdSubresourceInstanceId; + } + + // link subresource to main resource + $subresourceName = (string)$subresource->name; + $parentResourceIdFieldName = (string)$subresource->parent_resource_id_field_name; + $internalRequest = $this->_prepareRequest($subresourceName, $requestData, $parentResourceIdFieldName); + + /** @var $internalResponse Mage_Api2_Model_Response */ + $internalResponse = Mage::getModel('api2/response'); + $server->internalCall($internalRequest, $internalResponse); + } catch (Exception $e) { + // TODO: implement strict mode + Mage::logException($e); + $this->_getResponse()->setException($e); + // TODO: Refactor partial success idintification process + $this->_getResponse()->setHttpResponseCode(Mage_Api2_Model_Server::HTTP_CREATED); + } + + if (isset($internalCreateResponse)) { + $this->_aggregateResponse($internalCreateResponse); + } + if (isset($internalResponse)) { + $this->_aggregateResponse($internalResponse); + } + + return $this; + } + + /** + * Prepare internal request + * + * @param string $subresourceName + * @param array $data + * @param string|null $parentResourceIdFieldName + * @return Mage_Api2_Model_Request_Internal + */ + protected function _prepareRequest($subresourceName, $data, $parentResourceIdFieldName = null) + { + $subresourceUri = $this->_createSubresourceUri($subresourceName, $parentResourceIdFieldName); + /** @var $internalRequest Mage_Api2_Model_Request_Internal */ + $internalRequest = Mage::getModel('api2/request_internal'); + $internalRequest->setRequestUri($subresourceUri); + $internalRequest->setBodyParams($data); + $internalRequest->setMethod('POST'); + return $internalRequest; + } + + /** + * Generate subresource uri + * + * @param string $subresourceName + * @param string $parentResourceIdFieldName + * @return string + */ + protected function _createSubresourceUri($subresourceName, $parentResourceIdFieldName = null) + { + /** @var $apiTypeRoute Mage_Api2_Model_Route_ApiType */ + $apiTypeRoute = Mage::getModel('api2/route_apiType'); + + $chain = $apiTypeRoute->chain( + new Zend_Controller_Router_Route($this->_getConfig()->getMainRoute($subresourceName)) + ); + $params = array(); + $params['api_type'] = 'rest'; + if (null !== $parentResourceIdFieldName) { + $params[$parentResourceIdFieldName] = $this->_parentResourceId; + } + $uri = $chain->assemble($params); + + return '/' . $uri; + } + + /** + * Retrieve list of subresources declared in configuration + * + * @param string $parentResourceName + * @return array + */ + protected function _getDeclaredSubresources($parentResourceName) + { + return $this->_getConfig()->getResourceSubresources($parentResourceName); + } + + /** + * Retrieve API2 config + * + * @return Mage_Api2_Model_Config + */ + protected function _getConfig() + { + return Mage::getSingleton('api2/config'); + } + + /** + * Retrieve global response + * + * @return Mage_Api2_Model_Response + */ + protected function _getResponse() + { + return Mage::getSingleton('api2/response'); + } + + /** + * Retrieve parent request + * + * @return Mage_Api2_Model_Request + */ + protected function _getRequest() + { + return $this->_parentCallRequest; + } + + /** + * Add internal call response to global response + * + * @param Mage_Api2_Model_Response $response + */ + protected function _aggregateResponse(Mage_Api2_Model_Response $response) + { + if ($response->isException()) { + $errors = $response->getException(); + // @TODO: add subresource prefix to error messages + foreach ($errors as $error) { + $this->_getResponse()->setException($error); + } + } + } + + /** + * Retrieve created resource id from response + * + * @param Mage_Api2_Model_Response $response + * @return string|int + */ + protected function _getCreatedResourceId($response) + { + $resourceId = 0; + $headers = $response->getHeaders(); + foreach ($headers as $header) { + if ($header['name'] == 'Location') { + list($resourceId) = array_reverse(explode('/', $header['value'])); + break; + } + } + return $resourceId; + } +} diff --git a/app/code/core/Mage/Api2/Model/Observer.php b/app/code/core/Mage/Api2/Model/Observer.php new file mode 100644 index 0000000000..ce54ab708a --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Observer.php @@ -0,0 +1,86 @@ + + */ +class Mage_Api2_Model_Observer +{ + /** + * Save relation of admin user to API2 role + * + * @param Varien_Event_Observer $observer + * @return void + */ + public function saveAdminToRoleRelation(Varien_Event_Observer $observer) + { + /** @var $user Mage_Admin_Model_User Object */ + $user = $observer->getObject(); + + if ($user->hasData('api2_roles')) { + $roles = $user->getData('api2_roles'); + + if (!is_array($roles) || !isset($roles[0])) { + throw new Exception('API2 roles property has wrong data format.'); + } + + /** @var $resourceModel Mage_Api2_Model_Resource_Acl_Global_Role */ + $resourceModel = Mage::getResourceModel('api2/acl_global_role'); + $resourceModel->saveAdminToRoleRelation($user->getId(), $roles[0]); + } + } + + /** + * After save attribute if it is not visible on front remove it from Attribute ACL + * + * @param Varien_Event_Observer $observer + * @return Mage_Api2_Model_Observer + */ + public function catalogAttributeSaveAfter(Varien_Event_Observer $observer) + { + /** @var $attribute Mage_Catalog_Model_Resource_Eav_Attribute */ + $attribute = $observer->getEvent()->getAttribute(); + if ($attribute->getIsUserDefined() && $attribute->dataHasChangedFor('is_visible_on_front') + && !$attribute->getIsVisibleOnFront()) { + /** @var $collection Mage_Api2_Model_Resource_Acl_Filter_Attribute_Collection */ + $collection = Mage::getResourceModel('api2/acl_filter_attribute_collection'); + /** @var $aclFilter Mage_Api2_Model_Acl_Filter_Attribute */ + foreach ($collection as $aclFilter) { + if ($aclFilter->getResourceId() != Mage_Api2_Model_Acl_Global_Rule::RESOURCE_ALL) { + $allowedAttributes = explode(',', $aclFilter->getAllowedAttributes()); + $allowedAttributes = array_diff($allowedAttributes, array($attribute->getAttributeCode())); + $aclFilter->setAllowedAttributes(implode(',', $allowedAttributes))->save(); + } + } + } + + return $this; + } +} diff --git a/app/code/core/Mage/Api2/Model/Renderer.php b/app/code/core/Mage/Api2/Model/Renderer.php new file mode 100644 index 0000000000..2e5b2d21cf --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Renderer.php @@ -0,0 +1,83 @@ + + */ +abstract class Mage_Api2_Model_Renderer +{ + /** + * Get Renderer of given type + * + * @param array|string $acceptTypes + * @throws Mage_Api2_Exception + * @throws Exception + * @return Mage_Api2_Model_Renderer_Interface + */ + public static function factory($acceptTypes) + { + /** @var $helper Mage_Api2_Helper_Data */ + $helper = Mage::helper('api2'); + $adapters = $helper->getResponseRenderAdapters(); + + if (!is_array($acceptTypes)) { + $acceptTypes = array($acceptTypes); + } + + $type = null; + $adapterPath = null; + foreach ($acceptTypes as $type) { + foreach ($adapters as $item) { + $itemType = $item->type; + if ($type == $itemType + || $type == current(explode('/', $itemType)) . '/*' || $type == '*/*' + ) { + $adapterPath = $item->model; + break 2; + } + } + } + + //if server can't respond in any of accepted types it SHOULD send 406(not acceptable) + if (null === $adapterPath) { + throw new Mage_Api2_Exception( + 'Server can not understand Accept HTTP header media type.', + Mage_Api2_Model_Server::HTTP_NOT_ACCEPTABLE + ); + } + + $adapter = Mage::getModel($adapterPath); + if (!$adapter) { + throw new Exception(sprintf('Response renderer adapter for content type "%s" not found.', $type)); + } + + return $adapter; + } +} diff --git a/app/code/core/Mage/Api2/Model/Renderer/Interface.php b/app/code/core/Mage/Api2/Model/Renderer/Interface.php new file mode 100644 index 0000000000..c833863d90 --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Renderer/Interface.php @@ -0,0 +1,50 @@ + + */ +interface Mage_Api2_Model_Renderer_Interface +{ + /** + * Render content in a certain format + * + * @param array|object $data + * @return string + */ + public function render($data); + + /** + * Get MIME type generated by renderer + * + * @return string + */ + public function getMimeType(); +} diff --git a/app/code/core/Mage/Api2/Model/Renderer/Json.php b/app/code/core/Mage/Api2/Model/Renderer/Json.php new file mode 100644 index 0000000000..e0d6750333 --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Renderer/Json.php @@ -0,0 +1,61 @@ + + */ +class Mage_Api2_Model_Renderer_Json implements Mage_Api2_Model_Renderer_Interface +{ + /** + * Adapter mime type + */ + const MIME_TYPE = 'application/json'; + + /** + * Convert Array to JSON + * + * @param array|object $data + * @return string + */ + public function render($data) + { + return Zend_Json::encode($data); + } + + /** + * Get MIME type generated by renderer + * + * @return string + */ + public function getMimeType() + { + return self::MIME_TYPE; + } +} diff --git a/app/code/core/Mage/Api2/Model/Renderer/Query.php b/app/code/core/Mage/Api2/Model/Renderer/Query.php new file mode 100644 index 0000000000..4aece41353 --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Renderer/Query.php @@ -0,0 +1,62 @@ + + */ +class Mage_Api2_Model_Renderer_Query implements Mage_Api2_Model_Renderer_Interface +{ + /** + * Adapter mime type + */ + const MIME_TYPE = 'text/plain'; + + /** + * Convert Array to URL-encoded query string + * + * @param array|object $data + * @return string + */ + public function render($data) + { + $query = http_build_query($data); + return $query; + } + + /** + * Get MIME type generated by renderer + * + * @return string + */ + public function getMimeType() + { + return self::MIME_TYPE; + } +} diff --git a/app/code/core/Mage/Api2/Model/Renderer/Xml.php b/app/code/core/Mage/Api2/Model/Renderer/Xml.php new file mode 100644 index 0000000000..02e3c0429d --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Renderer/Xml.php @@ -0,0 +1,162 @@ + + */ +class Mage_Api2_Model_Renderer_Xml implements Mage_Api2_Model_Renderer_Interface +{ + /** + * Adapter mime type + */ + const MIME_TYPE = 'application/xml'; + + /** + * Default name for item of non-associative array + */ + const ARRAY_NON_ASSOC_ITEM_NAME = 'data_item'; + + /** + * Chars for replacement in the tag name + * + * @var array + */ + protected $_replacementInTagName = array( + '!' => '', '"' => '', '#' => '', '$' => '', '%' => '', '&' => '', '\'' => '', + '(' => '', ')' => '', '*' => '', '+' => '', ',' => '', '/' => '', ';' => '', + '<' => '', '=' => '', '>' => '', '?' => '', '@' => '', '[' => '', '\\' => '', + ']' => '', '^' => '', '`' => '', '{' => '', '|' => '', '}' => '', '~' => '', + ' ' => '_', ':' => '_' + ); + + /** + * Chars for replacement in the tag value + * + * @var array + */ + protected $_replacementInTagValue = array( + '&' => '&' // replace "&" with HTML entity, because by default not replaced + ); + + /** + * Protected pattern for check chars in the begin of tag name + * + * @var string + */ + protected $_protectedTagNamePattern = '/^[0-9,.-]/'; + + /** + * Convert Array to XML + * + * @param mixed $data + * @return string + */ + public function render($data) + { + /* @var $writer Mage_Api2_Model_Renderer_Xml_Writer */ + $writer = Mage::getModel('api2/renderer_xml_writer', array( + 'config' => new Zend_Config($this->_prepareData($data, true)) + )); + return $writer->render(); + } + + /** + * Prepare convert data + * + * @param array|Varien_Object $data + * @param bool $root + * @return array + * @throws Exception + */ + protected function _prepareData($data, $root = false) + { + if (!is_array($data) && !is_object($data)) { + if ($root) { + $data = array($data); + } else { + throw new Exception('Prepare data must be an object or an array.'); + } + } + $data = $data instanceof Varien_Object ? $data->toArray() : (array)$data; + $isAssoc = !preg_match('/^\d+$/', implode(array_keys($data), '')); + + $preparedData = array(); + foreach ($data as $key => $value) { + $value = is_array($value) || is_object($value) ? $this->_prepareData($value) : $this->_prepareValue($value); + if ($isAssoc) { + $preparedData[$this->_prepareKey($key)] = $value; + } else { + $preparedData[self::ARRAY_NON_ASSOC_ITEM_NAME][] = $value; + } + } + return $preparedData; + } + + /** + * Prepare value + * + * @param string $value + * @return string + */ + protected function _prepareValue($value) + { + return str_replace( + array_keys($this->_replacementInTagValue), + array_values($this->_replacementInTagValue), + $value + ); + } + + /** + * Prepare key and replace unavailable chars + * + * @param string $key + * @return string + */ + protected function _prepareKey($key) + { + $key = str_replace(array_keys($this->_replacementInTagName), array_values($this->_replacementInTagName), $key); + $key = trim($key, '_'); + if (preg_match($this->_protectedTagNamePattern, $key)) { + $key = self::ARRAY_NON_ASSOC_ITEM_NAME . '_' . $key; + } + return $key; + } + + /** + * Get MIME type generated by renderer + * + * @return string + */ + public function getMimeType() + { + return self::MIME_TYPE; + } +} diff --git a/app/code/core/Mage/Api2/Model/Renderer/Xml/Writer.php b/app/code/core/Mage/Api2/Model/Renderer/Xml/Writer.php new file mode 100644 index 0000000000..6d616c1f6f --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Renderer/Xml/Writer.php @@ -0,0 +1,80 @@ + + */ +class Mage_Api2_Model_Renderer_Xml_Writer extends Zend_Config_Writer_Xml +{ + /** + * Root node in XML output + */ + const XML_ROOT_NODE = 'magento_api'; + + /** + * Render a Zend_Config into a XML config string. + * OVERRIDE to avoid using zend-config string in XML + * + * @return string + */ + public function render() + { + $xml = new SimpleXMLElement('<' . self::XML_ROOT_NODE . '/>'); + $extends = $this->_config->getExtends(); + $sectionName = $this->_config->getSectionName(); + + if (is_string($sectionName)) { + $child = $xml->addChild($sectionName); + + $this->_addBranch($this->_config, $child, $xml); + } else { + foreach ($this->_config as $sectionName => $data) { + if (!($data instanceof Zend_Config)) { + $xml->addChild($sectionName, (string) $data); + } else { + $child = $xml->addChild($sectionName); + + if (isset($extends[$sectionName])) { + $child->addAttribute('zf:extends', $extends[$sectionName], Zend_Config_Xml::XML_NAMESPACE); + } + + $this->_addBranch($data, $child, $xml); + } + } + } + + $dom = dom_import_simplexml($xml)->ownerDocument; + $dom->formatOutput = true; + + $xmlString = $dom->saveXML(); + + return $xmlString; + } +} diff --git a/app/code/core/Mage/Api2/Model/Request.php b/app/code/core/Mage/Api2/Model/Request.php new file mode 100644 index 0000000000..6b40def624 --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Request.php @@ -0,0 +1,325 @@ + + */ +class Mage_Api2_Model_Request extends Zend_Controller_Request_Http +{ + /** + * Character set which must be used in request + */ + const REQUEST_CHARSET = 'utf-8'; + + /**#@+ + * Name of query ($_GET) parameters to use in navigation and so on + */ + const QUERY_PARAM_REQ_ATTRS = 'attrs'; + const QUERY_PARAM_PAGE_NUM = 'page'; + const QUERY_PARAM_PAGE_SIZE = 'limit'; + const QUERY_PARAM_ORDER_FIELD = 'order'; + const QUERY_PARAM_ORDER_DIR = 'dir'; + const QUERY_PARAM_FILTER = 'filter'; + /**#@- */ + + /** + * Interpreter adapter + * + * @var Mage_Api2_Model_Request_Interpreter_Interface + */ + protected $_interpreter; + + /** + * Body params + * + * @var array + */ + protected $_bodyParams; + + /** + * Constructor + * + * If a $uri is passed, the object will attempt to populate itself using + * that information. + * Override parent class to allow object instance get via Mage::getSingleton() + * + * @param string|Zend_Uri $uri + */ + public function __construct($uri = null) + { + parent::__construct($uri ? $uri : null); + } + + /** + * Get request interpreter + * + * @return Mage_Api2_Model_Request_Interpreter_Interface + */ + protected function _getInterpreter() + { + if (null === $this->_interpreter) { + $this->_interpreter = Mage_Api2_Model_Request_Interpreter::factory($this->getContentType()); + } + return $this->_interpreter; + } + + /** + * Retrieve accept types understandable by requester in a form of array sorted by quality descending + * + * @return array + */ + public function getAcceptTypes() + { + $qualityToTypes = array(); + $orderedTypes = array(); + + foreach (preg_split('/,\s*/', $this->getHeader('Accept')) as $definition) { + $typeWithQ = explode(';', $definition); + $mimeType = trim(array_shift($typeWithQ)); + + // check MIME type validity + if (!preg_match('~^([0-9a-z*+\-]+)(?:/([0-9a-z*+\-\.]+))?$~i', $mimeType)) { + continue; + } + $quality = '1.0'; // default value for quality + + if ($typeWithQ) { + $qAndValue = explode('=', $typeWithQ[0]); + + if (2 == count($qAndValue)) { + $quality = $qAndValue[1]; + } + } + $qualityToTypes[$quality][$mimeType] = true; + } + krsort($qualityToTypes); + + foreach ($qualityToTypes as $typeList) { + $orderedTypes += $typeList; + } + return array_keys($orderedTypes); + } + + /** + * Get api type from Request + * + * @return string + */ + public function getApiType() + { + // getParam() is not used to avoid parameter fetch from $_GET or $_POST + return isset($this->_params['api_type']) ? $this->_params['api_type'] : null; + } + + /** + * Fetch data from HTTP Request body + * + * @return array + */ + public function getBodyParams() + { + if (null == $this->_bodyParams) { + $this->_bodyParams = $this->_getInterpreter()->interpret((string)$this->getRawBody()); + } + return $this->_bodyParams; + } + + /** + * Get Content-Type of request + * + * @return string + * @throws Mage_Api2_Exception + */ + public function getContentType() + { + $headerValue = $this->getHeader('Content-Type'); + + if (!$headerValue) { + throw new Mage_Api2_Exception('Content-Type header is empty', Mage_Api2_Model_Server::HTTP_BAD_REQUEST); + } + if (!preg_match('~^([a-z\d/\-+.]+)(?:; *charset=(.+))?$~Ui', $headerValue, $matches)) { + throw new Mage_Api2_Exception('Invalid Content-Type header', Mage_Api2_Model_Server::HTTP_BAD_REQUEST); + } + // request encoding check if it is specified in header + if (isset($matches[2]) && self::REQUEST_CHARSET != strtolower($matches[2])) { + throw new Mage_Api2_Exception( + 'UTF-8 is the only supported charset', Mage_Api2_Model_Server::HTTP_BAD_REQUEST + ); + } + return $matches[1]; + } + + /** + * Get filter settings passed by API user + * + * @return mixed + */ + public function getFilter() + { + return $this->getQuery(self::QUERY_PARAM_FILTER); + } + + /** + * Get resource model class name + * + * @return string|null + */ + public function getModel() + { + // getParam() is not used to avoid parameter fetch from $_GET or $_POST + return isset($this->_params['model']) ? $this->_params['model'] : null; + } + + /** + * Retrieve one of CRUD operation dependent on HTTP method + * + * @return string + * @throws Mage_Api2_Exception + */ + public function getOperation() + { + if (!$this->isGet() && !$this->isPost() && !$this->isPut() && !$this->isDelete()) { + throw new Mage_Api2_Exception('Invalid request method', Mage_Api2_Model_Server::HTTP_BAD_REQUEST); + } + // Map HTTP methods to classic CRUD verbs + $operationByMethod = array( + 'GET' => Mage_Api2_Model_Resource::OPERATION_RETRIEVE, + 'POST' => Mage_Api2_Model_Resource::OPERATION_CREATE, + 'PUT' => Mage_Api2_Model_Resource::OPERATION_UPDATE, + 'DELETE' => Mage_Api2_Model_Resource::OPERATION_DELETE + ); + + return $operationByMethod[$this->getMethod()]; + } + + /** + * Get sort order direction requested by API user + * + * @return mixed + */ + public function getOrderDirection() + { + return $this->getQuery(self::QUERY_PARAM_ORDER_DIR); + } + + /** + * Get sort order field requested by API user + * + * @return mixed + */ + public function getOrderField() + { + return $this->getQuery(self::QUERY_PARAM_ORDER_FIELD); + } + + /** + * Retrieve page number requested by API user + * + * @return mixed + */ + public function getPageNumber() + { + return $this->getQuery(self::QUERY_PARAM_PAGE_NUM); + } + + /** + * Retrieve page size requested by API user + * + * @return mixed + */ + public function getPageSize() + { + return $this->getQuery(self::QUERY_PARAM_PAGE_SIZE); + } + + /** + * Get an array of attribute codes requested by API user + * + * @return array + */ + public function getRequestedAttributes() + { + $include = $this->getQuery(self::QUERY_PARAM_REQ_ATTRS, array()); + + //transform comma-separated list + if (!is_array($include)) { + $include = explode(',', $include); + } + return array_map('trim', $include); + } + + /** + * Retrieve resource type + * + * @return string + */ + public function getResourceType() + { + // getParam() is not used to avoid parameter fetch from $_GET or $_POST + return isset($this->_params['type']) ? $this->_params['type'] : null; + } + + /** + * Get Version header from headers + * + * @return string + */ + public function getVersion() + { + return $this->getHeader('Version'); + } + + /** + * Retrieve action type + * + * @return string|null + */ + public function getActionType() + { + // getParam() is not used to avoid parameter fetch from $_GET or $_POST + return isset($this->_params['action_type']) ? $this->_params['action_type'] : null; + } + + /** + * It checks if the array in the request body is an associative one. + * It is required for definition of the dynamic aaction type (multi or single) + * + * @return bool + */ + public function isAssocArrayInRequestBody() + { + $params = $this->getBodyParams(); + if (count($params)) { + $keys = array_keys($params); + return !is_numeric($keys[0]); + } + return false; + } +} diff --git a/app/code/core/Mage/Api2/Model/Request/Internal.php b/app/code/core/Mage/Api2/Model/Request/Internal.php new file mode 100644 index 0000000000..bfbfe1d3a8 --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Request/Internal.php @@ -0,0 +1,105 @@ + + */ +class Mage_Api2_Model_Request_Internal extends Mage_Api2_Model_Request +{ + /** + * Request body data + * + * @var array + */ + protected $_bodyParams; + + /** + * Request method + * + * @var string + */ + protected $_method; + + /** + * Fetch data from HTTP Request body + * + * @return array + */ + public function getBodyParams() + { + if ($this->_bodyParams === null) { + $this->_bodyParams = $this->_getInterpreter()->interpret((string) $this->getRawBody()); + } + return $this->_bodyParams; + } + + /** + * Set request body data + * + * @param array $data + * @return Mage_Api2_Model_Request + */ + public function setBodyParams($data) + { + $this->_bodyParams = $data; + return $this; + } + + /** + * Set HTTP request method for request emulation during internal call + * + * @param string $method + * @return Mage_Api2_Model_Request_Internal + */ + public function setMethod($method) + { + $availableMethod = array('GET', 'POST', 'PUT', 'DELETE'); + if (in_array($method, $availableMethod)) { + $this->_method = $method; + } else { + throw new Mage_Api2_Exception('Invalid method provided', Mage_Api2_Model_Server::HTTP_INTERNAL_ERROR); + } + return $this; + } + + /** + * Override parent method for request emulation during internal call + * + * @return string + */ + public function getMethod() + { + $method = $this->_method; + if (!$method) { + $method = parent::getMethod(); + } + return $method; + } +} diff --git a/app/code/core/Mage/Api2/Model/Request/Interpreter.php b/app/code/core/Mage/Api2/Model/Request/Interpreter.php new file mode 100644 index 0000000000..03322399d8 --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Request/Interpreter.php @@ -0,0 +1,76 @@ + + */ +abstract class Mage_Api2_Model_Request_Interpreter +{ + /** + * Request body interpreters factory + * + * @param string $type + * @return Mage_Api2_Model_Request_Interpreter_Interface + * @throws Exception|Mage_Api2_Exception + */ + public static function factory($type) + { + /** @var $helper Mage_Api2_Helper_Data */ + $helper = Mage::helper('api2/data'); + $adapters = $helper->getRequestInterpreterAdapters(); + + if (empty($adapters) || !is_array($adapters)) { + throw new Exception('Request interpreter adapters is not set.'); + } + + $adapterModel = null; + foreach ($adapters as $item) { + $itemType = $item->type; + if ($itemType == $type) { + $adapterModel = $item->model; + break; + } + } + + if ($adapterModel === null) { + throw new Mage_Api2_Exception( + sprintf('Server can not understand Content-Type HTTP header media type "%s"', $type), + Mage_Api2_Model_Server::HTTP_BAD_REQUEST + ); + } + + $adapter = Mage::getModel($adapterModel); + if (!$adapter) { + throw new Exception(sprintf('Request interpreter adapter "%s" not found.', $type)); + } + + return $adapter; + } +} diff --git a/app/code/core/Mage/Api2/Model/Request/Interpreter/Interface.php b/app/code/core/Mage/Api2/Model/Request/Interpreter/Interface.php new file mode 100644 index 0000000000..91a79ce921 --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Request/Interpreter/Interface.php @@ -0,0 +1,43 @@ + + */ +interface Mage_Api2_Model_Request_Interpreter_Interface +{ + /** + * Parse request body into array of params + * + * @param string $body Posted content from request + * @return array|null Return NULL if content is invalid + */ + public function interpret($body); +} diff --git a/app/code/core/Mage/Api2/Model/Request/Interpreter/Json.php b/app/code/core/Mage/Api2/Model/Request/Interpreter/Json.php new file mode 100644 index 0000000000..c533a8fe24 --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Request/Interpreter/Json.php @@ -0,0 +1,57 @@ + + */ +class Mage_Api2_Model_Request_Interpreter_Json implements Mage_Api2_Model_Request_Interpreter_Interface +{ + /** + * Parse Request body into array of params + * + * @param string $body Posted content from request + * @return array|null Return NULL if content is invalid + * @throws Exception|Mage_Api2_Exception + */ + public function interpret($body) + { + if (!is_string($body)) { + throw new Exception(sprintf('Invalid data type "%s". String expected.', gettype($body))); + } + + $decoded = Zend_Json::decode($body); + + if ($body != 'null' && $decoded === null) { + throw new Mage_Api2_Exception('Decoding error.', Mage_Api2_Model_Server::HTTP_BAD_REQUEST); + } + + return $decoded; + } +} diff --git a/app/code/core/Mage/Api2/Model/Request/Interpreter/Query.php b/app/code/core/Mage/Api2/Model/Request/Interpreter/Query.php new file mode 100644 index 0000000000..90a6bb145f --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Request/Interpreter/Query.php @@ -0,0 +1,77 @@ + + */ +class Mage_Api2_Model_Request_Interpreter_Query implements Mage_Api2_Model_Request_Interpreter_Interface +{ + /** + * URI validate pattern + */ + const URI_VALIDATE_PATTERN = "/^(?:%[[:xdigit:]]{2}|[A-Za-z0-9-_.!~*'()\[\];\/?:@&=+$,])*$/"; + + /** + * Parse request body into array of params + * + * @param string $body Posted content from request + * @return array Return always array + * @throws Exception|Mage_Api2_Exception + */ + public function interpret($body) + { + if (!is_string($body)) { + throw new Exception(sprintf('Invalid data type "%s". String expected.', gettype($body))); + } + + if (!$this->_validateQuery($body)) { + throw new Mage_Api2_Exception( + 'Invalid data type. Check Content-Type.', + Mage_Api2_Model_Server::HTTP_BAD_REQUEST + ); + } + + $data = array(); + parse_str($body, $data); + return $data; + } + + /** + * Returns true if and only if the query string passes validation. + * + * @param string $query The query to validate + * @return boolean + * @link http://www.faqs.org/rfcs/rfc2396.html + */ + protected function _validateQuery($query) + { + return preg_match(self::URI_VALIDATE_PATTERN, $query); + } +} diff --git a/app/code/core/Mage/Api2/Model/Request/Interpreter/Xml.php b/app/code/core/Mage/Api2/Model/Request/Interpreter/Xml.php new file mode 100644 index 0000000000..0c0e45b37c --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Request/Interpreter/Xml.php @@ -0,0 +1,150 @@ + + */ +class Mage_Api2_Model_Request_Interpreter_Xml implements Mage_Api2_Model_Request_Interpreter_Interface +{ + /** + * Default name for item of non-associative array + */ + const ARRAY_NON_ASSOC_ITEM_NAME = 'data_item'; + + /** + * Load error string. + * + * Is null if there was no error while loading + * + * @var string + */ + protected $_loadErrorStr = null; + + /** + * Parse Request body into array of params + * + * @param string $body Posted content from request + * @return array + * @throws Exception|Mage_Api2_Exception + */ + public function interpret($body) + { + if (!is_string($body)) { + throw new Exception(sprintf('Invalid data type "%s". String expected.', gettype($body))); + } + $body = false !== strpos($body, '' . PHP_EOL . $body; + + set_error_handler(array($this, '_loadErrorHandler')); // Warnings and errors are suppressed + $config = simplexml_load_string($body); + restore_error_handler(); + + // Check if there was a error while loading file + if ($this->_loadErrorStr !== null) { + throw new Mage_Api2_Exception('Decoding error.', Mage_Api2_Model_Server::HTTP_BAD_REQUEST); + } + + $xml = $this->_toArray($config); + return $xml; + } + + /** + * Returns an associativearray from a SimpleXMLElement. + * + * @param SimpleXMLElement $xmlObject Convert a SimpleXMLElement into an array + * @return array + */ + protected function _toArray(SimpleXMLElement $xmlObject) + { + $config = array(); + // Search for parent node values + if (count($xmlObject->attributes()) > 0) { + foreach ($xmlObject->attributes() as $key => $value) { + $value = (string)$value; + if (array_key_exists($key, $config)) { + if (!is_array($config[$key])) { + $config[$key] = array($config[$key]); + } + $config[$key][] = $value; + } else { + $config[$key] = $value; + } + } + } + + // Search for children + if (count($xmlObject->children()) > 0) { + foreach ($xmlObject->children() as $key => $value) { + if (count($value->children()) > 0) { + $value = $this->_toArray($value); + } else if (count($value->attributes()) > 0) { + $attributes = $value->attributes(); + if (isset($attributes['value'])) { + $value = (string)$attributes['value']; + } else { + $value = $this->_toArray($value); + } + } else { + $value = (string) $value; + } + if (array_key_exists($key, $config)) { + if (!is_array($config[$key]) || !array_key_exists(0, $config[$key])) { + $config[$key] = array($config[$key]); + } + $config[$key][] = $value; + } else { + if (self::ARRAY_NON_ASSOC_ITEM_NAME != $key) { + $config[$key] = $value; + } else { + $config[] = $value; + } + } + } + } + + return $config; + } + + /** + * Handle any errors from load xml + * + * @param integer $errno + * @param string $errstr + * @param string $errfile + * @param integer $errline + */ + protected function _loadErrorHandler($errno, $errstr, $errfile, $errline) + { + if ($this->_loadErrorStr === null) { + $this->_loadErrorStr = $errstr; + } else { + $this->_loadErrorStr .= (PHP_EOL . $errstr); + } + } +} diff --git a/app/code/core/Mage/Api2/Model/Resource.php b/app/code/core/Mage/Api2/Model/Resource.php new file mode 100644 index 0000000000..938858ffd5 --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Resource.php @@ -0,0 +1,1082 @@ + + * @method string _create() _create(array $filteredData) creation of an entity + * @method void _multiCreate() _multiCreate(array $filteredData) processing and creation of a collection + * @method array _retrieve() retrieving an entity + * @method array _retrieveCollection() retrieving a collection + * @method void _update() _update(array $filteredData) update of an entity + * @method void _multiUpdate() _multiUpdate(array $filteredData) update of a collection + * @method void _delete() deletion of an entity + * @method void _multidelete() _multidelete(array $requestData) deletion of a collection + */ +abstract class Mage_Api2_Model_Resource +{ + /**#@+ + * Action types + */ + const ACTION_TYPE_ENTITY = 'entity'; + const ACTION_TYPE_COLLECTION = 'collection'; + /**#@-*/ + + /**#@+ + * Operations. Resource method names + */ + const OPERATION_CREATE = 'create'; + const OPERATION_RETRIEVE = 'retrieve'; + const OPERATION_UPDATE = 'update'; + const OPERATION_DELETE = 'delete'; + /**#@-*/ + + /**#@+ + * Common operations for attributes + */ + const OPERATION_ATTRIBUTE_READ = 'read'; + const OPERATION_ATTRIBUTE_WRITE = 'write'; + /**#@-*/ + + /**#@+ + * Default error messages + */ + const RESOURCE_NOT_FOUND = 'Resource not found.'; + const RESOURCE_METHOD_NOT_ALLOWED = 'Resource does not support method.'; + const RESOURCE_METHOD_NOT_IMPLEMENTED = 'Resource method not implemented yet.'; + const RESOURCE_INTERNAL_ERROR = 'Resource internal error.'; + const RESOURCE_DATA_PRE_VALIDATION_ERROR = 'Resource data pre-validation error.'; + const RESOURCE_DATA_INVALID = 'Resource data invalid.'; //error while checking data inside method + const RESOURCE_UNKNOWN_ERROR = 'Resource unknown error.'; + const RESOURCE_REQUEST_DATA_INVALID = 'The request data is invalid.'; + /**#@-*/ + + /**#@+ + * Default collection resources error messages + */ + const RESOURCE_COLLECTION_PAGING_ERROR = 'Resource collection paging error.'; + const RESOURCE_COLLECTION_PAGING_LIMIT_ERROR = 'The paging limit exceeds the allowed number.'; + const RESOURCE_COLLECTION_ORDERING_ERROR = 'Resource collection ordering error.'; + const RESOURCE_COLLECTION_FILTERING_ERROR = 'Resource collection filtering error.'; + const RESOURCE_COLLECTION_ATTRIBUTES_ERROR = 'Resource collection including additional attributes error.'; + /**#@-*/ + + /**#@+ + * Default success messages + */ + const RESOURCE_UPDATED_SUCCESSFUL = 'Resource updated successful.'; + /**#@-*/ + + /**#@+ + * Collection page sizes + */ + const PAGE_SIZE_DEFAULT = 10; + const PAGE_SIZE_MAX = 100; + /**#@-*/ + + /** + * Request + * + * @var Mage_Api2_Model_Request + */ + protected $_request; + + /** + * Resource type + * + * @var string + */ + protected $_resourceType; + + /** + * Api type + * + * @var string + */ + protected $_apiType; + + /** + * API Version + * + * @var int + */ + protected $_version = null; + + /** + * Response + * + * @var Zend_Controller_Response_Http + */ + protected $_response; + + /** + * Attribute Filter + * + * @var Mage_Api2_Model_Acl_Filter + */ + protected $_filter; + + /** + * Renderer + * + * @var Mage_Api2_Model_Renderer_Interface + */ + protected $_renderer; + + /** + * Api user + * + * @var Mage_Api2_Model_Auth_User_Abstract + */ + protected $_apiUser; + + /** + * User type + * + * @var string + */ + protected $_userType; + + /** + * One of Mage_Api2_Model_Resource::ACTION_TYPE_... constant + * + * @var string + */ + protected $_actionType; + + /** + * One of Mage_Api2_Model_Resource::OPERATION_... constant + * + * @var string + */ + protected $_operation; + + /** + * If TRUE - no rendering will be done and dispatch will return data. Otherwise, by default + * + * @var bool + */ + protected $_returnData = false; + + /** + * @var Mage_Api2_Model_Multicall + */ + protected $_multicall; + + /** + * Dispatch + * To implement the functionality, you must create a method in the parent one. + * + * Action type is defined in api2.xml in the routes section and depends on entity (single object) + * or collection (several objects). + * + * HTTP_MULTI_STATUS is used for several status codes in the response + */ + public function dispatch() + { + switch ($this->getActionType() . $this->getOperation()) { + /* Create */ + case self::ACTION_TYPE_ENTITY . self::OPERATION_CREATE: + // Creation of objects is possible only when working with collection + $this->_critical(self::RESOURCE_METHOD_NOT_IMPLEMENTED); + break; + case self::ACTION_TYPE_COLLECTION . self::OPERATION_CREATE: + // If no of the methods(multi or single) is implemented, request body is not checked + if (!$this->_checkMethodExist('_create') && !$this->_checkMethodExist('_multiCreate')) { + $this->_critical(self::RESOURCE_METHOD_NOT_IMPLEMENTED); + } + // If one of the methods(multi or single) is implemented, request body must not be empty + $requestData = $this->getRequest()->getBodyParams(); + if (empty($requestData)) { + $this->_critical(self::RESOURCE_REQUEST_DATA_INVALID); + } + // The create action has the dynamic type which depends on data in the request body + if ($this->getRequest()->isAssocArrayInRequestBody()) { + $this->_errorIfMethodNotExist('_create'); + $filteredData = $this->getFilter()->in($requestData); + if (empty($filteredData)) { + $this->_critical(self::RESOURCE_REQUEST_DATA_INVALID); + } + $newItemLocation = $this->_create($filteredData); + $this->getResponse()->setHeader('Location', $newItemLocation); + } else { + $this->_errorIfMethodNotExist('_multiCreate'); + $filteredData = $this->getFilter()->collectionIn($requestData); + $this->_multiCreate($filteredData); + $this->_render($this->getResponse()->getMessages()); + $this->getResponse()->setHttpResponseCode(Mage_Api2_Model_Server::HTTP_MULTI_STATUS); + } + break; + /* Retrieve */ + case self::ACTION_TYPE_ENTITY . self::OPERATION_RETRIEVE: + $this->_errorIfMethodNotExist('_retrieve'); + $retrievedData = $this->_retrieve(); + $filteredData = $this->getFilter()->out($retrievedData); + $this->_render($filteredData); + break; + case self::ACTION_TYPE_COLLECTION . self::OPERATION_RETRIEVE: + $this->_errorIfMethodNotExist('_retrieveCollection'); + $retrievedData = $this->_retrieveCollection(); + $filteredData = $this->getFilter()->collectionOut($retrievedData); + $this->_render($filteredData); + break; + /* Update */ + case self::ACTION_TYPE_ENTITY . self::OPERATION_UPDATE: + $this->_errorIfMethodNotExist('_update'); + $requestData = $this->getRequest()->getBodyParams(); + if (empty($requestData)) { + $this->_critical(self::RESOURCE_REQUEST_DATA_INVALID); + } + $filteredData = $this->getFilter()->in($requestData); + if (empty($filteredData)) { + $this->_critical(self::RESOURCE_REQUEST_DATA_INVALID); + } + $this->_update($filteredData); + break; + case self::ACTION_TYPE_COLLECTION . self::OPERATION_UPDATE: + $this->_errorIfMethodNotExist('_multiUpdate'); + $requestData = $this->getRequest()->getBodyParams(); + if (empty($requestData)) { + $this->_critical(self::RESOURCE_REQUEST_DATA_INVALID); + } + $filteredData = $this->getFilter()->collectionIn($requestData); + $this->_multiUpdate($filteredData); + $this->_render($this->getResponse()->getMessages()); + $this->getResponse()->setHttpResponseCode(Mage_Api2_Model_Server::HTTP_MULTI_STATUS); + break; + /* Delete */ + case self::ACTION_TYPE_ENTITY . self::OPERATION_DELETE: + $this->_errorIfMethodNotExist('_delete'); + $this->_delete(); + break; + case self::ACTION_TYPE_COLLECTION . self::OPERATION_DELETE: + $this->_errorIfMethodNotExist('_multiDelete'); + $requestData = $this->getRequest()->getBodyParams(); + if (empty($requestData)) { + $this->_critical(self::RESOURCE_REQUEST_DATA_INVALID); + } + $this->_multiDelete($requestData); + $this->getResponse()->setHttpResponseCode(Mage_Api2_Model_Server::HTTP_MULTI_STATUS); + break; + default: + $this->_critical(self::RESOURCE_METHOD_NOT_IMPLEMENTED); + break; + } + } + + /** + * Trigger error for not-implemented operations + * + * @param $methodName + */ + protected function _errorIfMethodNotExist($methodName) + { + if (!$this->_checkMethodExist($methodName)) { + $this->_critical(self::RESOURCE_METHOD_NOT_IMPLEMENTED); + } + } + + /** + * Check method exist + * + * @param $methodName + * @return bool + */ + protected function _checkMethodExist($methodName) + { + return method_exists($this, $methodName); + } + + /** + * Get request + * + * @throws Exception + * @return Mage_Api2_Model_Request + */ + public function getRequest() + { + if (!$this->_request) { + throw new Exception('Request is not set.'); + } + return $this->_request; + } + + /** + * Set request + * + * @param Mage_Api2_Model_Request $request + * @return Mage_Api2_Model_Resource + */ + public function setRequest(Mage_Api2_Model_Request $request) + { + $this->setResourceType($request->getResourceType()); + $this->setApiType($request->getApiType()); + $this->_request = $request; + return $this; + } + + /** + * Get resource type + * If not exists get from Request + * + * @return string + */ + public function getResourceType() + { + if (!$this->_resourceType) { + $this->setResourceType($this->getRequest()->getResourceType()); + } + return $this->_resourceType; + } + + /** + * Set resource type + * + * @param string $resourceType + * @return Mage_Api2_Model_Resource + */ + public function setResourceType($resourceType) + { + $this->_resourceType = $resourceType; + return $this; + } + + /** + * Get API type + * If not exists get from Request. + * + * @return string + */ + public function getApiType() + { + if (!$this->_apiType) { + $this->setApiType($this->getRequest()->getApiType()); + } + return $this->_apiType; + } + + /** + * Set API type + * + * @param string $apiType + * @return Mage_Api2_Model_Resource + */ + public function setApiType($apiType) + { + $this->_apiType = $apiType; + return $this; + } + + /** + * Determine version from class name + * + * @return int + */ + public function getVersion() + { + if (null === $this->_version) { + if (preg_match('/^.+([1-9]\d*)$/', get_class($this), $matches) ) { + $this->setVersion($matches[1]); + } else { + throw new Exception('Can not determine version from class name'); + } + } + return $this->_version; + } + + /** + * Set API version + * + * @param int $version + */ + public function setVersion($version) + { + $this->_version = (int)$version; + } + + /** + * Get response + * + * @return Mage_Api2_Model_Response + */ + public function getResponse() + { + if (!$this->_response) { + throw new Exception('Response is not set.'); + } + return $this->_response; + } + + /** + * Set response + * + * @param Mage_Api2_Model_Response $response + */ + public function setResponse(Mage_Api2_Model_Response $response) + { + $this->_response = $response; + } + + /** + * Get filter if not exists create + * + * @return Mage_Api2_Model_Acl_Filter + */ + public function getFilter() + { + if (!$this->_filter) { + /** @var $filter Mage_Api2_Model_Acl_Filter */ + $filter = Mage::getModel('api2/acl_filter', $this); + $this->setFilter($filter); + } + return $this->_filter; + } + + /** + * Set filter + * + * @param Mage_Api2_Model_Acl_Filter $filter + */ + public function setFilter(Mage_Api2_Model_Acl_Filter $filter) + { + $this->_filter = $filter; + } + + /** + * Get renderer if not exists create + * + * @return Mage_Api2_Model_Renderer_Interface + */ + public function getRenderer() + { + if (!$this->_renderer) { + $renderer = Mage_Api2_Model_Renderer::factory($this->getRequest()->getAcceptTypes()); + $this->setRenderer($renderer); + } + + return $this->_renderer; + } + + /** + * Set renderer + * + * @param Mage_Api2_Model_Renderer_Interface $renderer + */ + public function setRenderer(Mage_Api2_Model_Renderer_Interface $renderer) + { + $this->_renderer = $renderer; + } + + /** + * Get user type + * If not exists get from apiUser + * + * @return string + */ + public function getUserType() + { + if (!$this->_userType) { + $this->setUserType($this->getApiUser()->getType()); + } + return $this->_userType; + } + + /** + * Set user type + * + * @param string $userType + * @return Mage_Api2_Model_Resource + */ + public function setUserType($userType) + { + $this->_userType = $userType; + return $this; + } + + /** + * Get API user + * + * @throws Exception + * @return Mage_Api2_Model_Auth_User_Abstract + */ + public function getApiUser() + { + if (!$this->_apiUser) { + throw new Exception('API user is not set.'); + } + return $this->_apiUser; + } + + /** + * Set API user + * + * @param Mage_Api2_Model_Auth_User_Abstract $apiUser + * @return Mage_Api2_Model_Resource + */ + public function setApiUser(Mage_Api2_Model_Auth_User_Abstract $apiUser) + { + $this->_apiUser = $apiUser; + return $this; + } + + /** + * Get action type + * If not exists get from Request + * + * @return string One of Mage_Api2_Model_Resource::ACTION_TYPE_... constant + */ + public function getActionType() + { + if (!$this->_actionType) { + $this->setActionType($this->getRequest()->getActionType()); + } + return $this->_actionType; + } + + /** + * Set route type + * + * @param string $actionType One of Mage_Api2_Model_Resource::ACTION_TYPE_... constant + * @return Mage_Api2_Model_Resource + */ + public function setActionType($actionType) + { + $this->_actionType = $actionType; + return $this; + } + + /** + * Get operation + * If not exists get from Request + * + * @return string One of Mage_Api2_Model_Resource::OPERATION_... constant + */ + public function getOperation() + { + if (!$this->_operation) { + $this->setOperation($this->getRequest()->getOperation()); + } + return $this->_operation; + } + + /** + * Set operation + * + * @param string $operation One of Mage_Api2_Model_Resource::OPERATION_... constant + * @return Mage_Api2_Model_Resource + */ + public function setOperation($operation) + { + $this->_operation = $operation; + return $this; + } + + /** + * Get API2 config + * + * @return Mage_Api2_Model_Config + */ + public function getConfig() + { + return Mage::getSingleton('api2/config'); + } + + /** + * Get working model + * + * @return Mage_Core_Model_Abstract + */ + public function getWorkingModel() + { + return Mage::getModel($this->getConfig()->getResourceWorkingModel($this->getResourceType())); + } + + /** + * Render data using registered Renderer + * + * @param mixed $data + */ + protected function _render($data) + { + $this->getResponse()->setMimeType($this->getRenderer()->getMimeType()) + ->setBody($this->getRenderer()->render($data)); + } + + /** + * Throw exception, critical error - stop execution + * + * @param string $message + * @param int $code + * @throws Mage_Api2_Exception + */ + protected function _critical($message, $code = null) + { + if ($code === null) { + $errors = $this->_getCriticalErrors(); + if (!isset($errors[$message])) { + throw new Exception( + sprintf('Invalid error "%s" or error code missed.', $message), + Mage_Api2_Model_Server::HTTP_INTERNAL_ERROR + ); + } + $code = $errors[$message]; + } + throw new Mage_Api2_Exception($message, $code); + } + + /** + * Retrieve array with critical errors mapped to HTTP codes + * + * @return array + */ + protected function _getCriticalErrors() + { + return array( + '' => Mage_Api2_Model_Server::HTTP_BAD_REQUEST, + self::RESOURCE_NOT_FOUND => Mage_Api2_Model_Server::HTTP_NOT_FOUND, + self::RESOURCE_METHOD_NOT_ALLOWED => Mage_Api2_Model_Server::HTTP_METHOD_NOT_ALLOWED, + self::RESOURCE_METHOD_NOT_IMPLEMENTED => Mage_Api2_Model_Server::HTTP_METHOD_NOT_ALLOWED, + self::RESOURCE_DATA_PRE_VALIDATION_ERROR => Mage_Api2_Model_Server::HTTP_BAD_REQUEST, + self::RESOURCE_INTERNAL_ERROR => Mage_Api2_Model_Server::HTTP_INTERNAL_ERROR, + self::RESOURCE_UNKNOWN_ERROR => Mage_Api2_Model_Server::HTTP_BAD_REQUEST, + self::RESOURCE_REQUEST_DATA_INVALID => Mage_Api2_Model_Server::HTTP_BAD_REQUEST, + self::RESOURCE_COLLECTION_PAGING_ERROR => Mage_Api2_Model_Server::HTTP_BAD_REQUEST, + self::RESOURCE_COLLECTION_PAGING_LIMIT_ERROR => Mage_Api2_Model_Server::HTTP_BAD_REQUEST, + self::RESOURCE_COLLECTION_ORDERING_ERROR => Mage_Api2_Model_Server::HTTP_BAD_REQUEST, + self::RESOURCE_COLLECTION_FILTERING_ERROR => Mage_Api2_Model_Server::HTTP_BAD_REQUEST, + self::RESOURCE_COLLECTION_ATTRIBUTES_ERROR => Mage_Api2_Model_Server::HTTP_BAD_REQUEST, + ); + } + + /** + * Add non-critical error + * + * @param string $message + * @param int $code + * @return Mage_Api2_Model_Resource + */ + protected function _error($message, $code) + { + $this->getResponse()->setException(new Mage_Api2_Exception($message, $code)); + return $this; + } + + /** + * Add success message + * + * @param string $message + * @param int $code + * @param array $params + * @return Mage_Api2_Model_Resource + */ + protected function _successMessage($message, $code, $params = array()) + { + $this->getResponse()->addMessage($message, $code, $params, Mage_Api2_Model_Response::MESSAGE_TYPE_SUCCESS); + return $this; + } + + /** + * Add error message + * + * @param string $message + * @param int $code + * @param array $params + * @return Mage_Api2_Model_Resource + */ + protected function _errorMessage($message, $code, $params = array()) + { + $this->getResponse()->addMessage($message, $code, $params, Mage_Api2_Model_Response::MESSAGE_TYPE_ERROR); + return $this; + } + + /** + * Set navigation parameters and apply filters from URL params + * + * @param Varien_Data_Collection_Db $collection + * @return Mage_Api2_Model_Resource + */ + final protected function _applyCollectionModifiers(Varien_Data_Collection_Db $collection) + { + $pageNumber = $this->getRequest()->getPageNumber(); + if ($pageNumber != abs($pageNumber)) { + $this->_critical(self::RESOURCE_COLLECTION_PAGING_ERROR); + } + + $pageSize = $this->getRequest()->getPageSize(); + if (null == $pageSize) { + $pageSize = self::PAGE_SIZE_DEFAULT; + } else { + if ($pageSize != abs($pageSize) || $pageSize > self::PAGE_SIZE_MAX) { + $this->_critical(self::RESOURCE_COLLECTION_PAGING_LIMIT_ERROR); + } + } + + $orderField = $this->getRequest()->getOrderField(); + + if (null !== $orderField) { + $operation = Mage_Api2_Model_Resource::OPERATION_ATTRIBUTE_READ; + if (!is_string($orderField) + || !array_key_exists($orderField, $this->getAvailableAttributes($this->getUserType(), $operation)) + ) { + $this->_critical(self::RESOURCE_COLLECTION_ORDERING_ERROR); + } + $collection->setOrder($orderField, $this->getRequest()->getOrderDirection()); + } + $collection->setCurPage($pageNumber)->setPageSize($pageSize); + + return $this->_applyFilter($collection); + } + + /** + * Validate filter data and apply it to collection if possible + * + * @param Varien_Data_Collection_Db $collection + * @return Mage_Api2_Model_Resource + */ + protected function _applyFilter(Varien_Data_Collection_Db $collection) + { + $filter = $this->getRequest()->getFilter(); + + if (!$filter) { + return $this; + } + if (!is_array($filter)) { + $this->_critical(self::RESOURCE_COLLECTION_FILTERING_ERROR); + } + if (method_exists($collection, 'addAttributeToFilter')) { + $methodName = 'addAttributeToFilter'; + } elseif (method_exists($collection, 'addFieldToFilter')) { + $methodName = 'addFieldToFilter'; + } else { + return $this; + } + $allowedAttributes = $this->getFilter()->getAllowedAttributes(self::OPERATION_ATTRIBUTE_READ); + + foreach ($filter as $filterEntry) { + if (!is_array($filterEntry) + || !array_key_exists('attribute', $filterEntry) + || !in_array($filterEntry['attribute'], $allowedAttributes) + ) { + $this->_critical(self::RESOURCE_COLLECTION_FILTERING_ERROR); + } + $attributeCode = $filterEntry['attribute']; + + unset($filterEntry['attribute']); + + try { + $collection->$methodName($attributeCode, $filterEntry); + } catch(Exception $e) { + $this->_critical(self::RESOURCE_COLLECTION_FILTERING_ERROR); + } + } + return $this; + } + + /** + * Perform multiple calls to subresources of specified resource + * + * @param string $resourceInstanceId + * @return Mage_Api2_Model_Response + */ + protected function _multicall($resourceInstanceId) + { + if (!$this->_multicall) { + $this->_multicall = Mage::getModel('api2/multicall'); + } + $resourceName = $this->getResourceType(); + return $this->_multicall->call($resourceInstanceId, $resourceName, $this->getRequest()); + } + + /** + * Create model of specified resource and configure it with current object attributes + * + * @param string $resourceId Resource identifier + * @param array $requestParams Parameters to be set to request + * @return Mage_Api2_Model_Resource + */ + protected function _getSubModel($resourceId, array $requestParams) + { + $resourceModel = Mage_Api2_Model_Dispatcher::loadResourceModel( + $this->getConfig()->getResourceModel($resourceId), + $this->getApiType(), + $this->getUserType(), + $this->getVersion() + ); + + /** @var $request Mage_Api2_Model_Request */ + $request = Mage::getModel('api2/request'); + + $request->setParams($requestParams); + + $resourceModel + ->setRequest($request) // request MUST be set first + ->setApiUser($this->getApiUser()) + ->setApiType($this->getApiType()) + ->setResourceType($resourceId) + ->setOperation($this->getOperation()) + ->setReturnData(true); + + return $resourceModel; + } + + /** + * Check ACL permission for specified resource with current other conditions + * + * @param string $resourceId Resource identifier + * @return bool + * @throws Exception + */ + protected function _isSubCallAllowed($resourceId) + { + /** @var $globalAcl Mage_Api2_Model_Acl_Global */ + $globalAcl = Mage::getSingleton('api2/acl_global'); + + try { + return $globalAcl->isAllowed($this->getApiUser(), $resourceId, $this->getOperation()); + } catch (Mage_Api2_Exception $e) { + throw new Exception('Invalid arguments for isAllowed() call'); + } + } + + /** + * Set 'returnData' flag + * + * @param boolean $flag + * @return Mage_Api2_Model_Resource + */ + public function setReturnData($flag) + { + $this->_returnData = $flag; + return $this; + } + + /** + * Get resource location + * + * @param Mage_Core_Model_Abstract $resource + * @return string URL + */ + protected function _getLocation($resource) + { + /* @var $apiTypeRoute Mage_Api2_Model_Route_ApiType */ + $apiTypeRoute = Mage::getModel('api2/route_apiType'); + + $chain = $apiTypeRoute->chain( + new Zend_Controller_Router_Route($this->getConfig()->getRouteWithEntityTypeAction($this->getResourceType())) + ); + $params = array( + 'api_type' => $this->getRequest()->getApiType(), + 'id' => $resource->getId() + ); + $uri = $chain->assemble($params); + + return '/' . $uri; + } + + /** + * Resource specific method to retrieve attributes' codes. May be overriden in child. + * + * @return array + */ + protected function _getResourceAttributes() + { + return array(); + } + + /** + * Get available attributes of API resource + * + * @param string $userType + * @param string $operation + * @return array + */ + public function getAvailableAttributes($userType, $operation) + { + $available = $this->getAvailableAttributesFromConfig(); + $excludedAttrs = $this->getExcludedAttributes($userType, $operation); + $includedAttrs = $this->getIncludedAttributes($userType, $operation); + $entityOnlyAttrs = $this->getEntityOnlyAttributes($userType, $operation); + $resourceAttrs = $this->_getResourceAttributes(); + + // if resource returns not-associative array - attributes' codes only + if (0 === key($resourceAttrs)) { + $resourceAttrs = array_combine($resourceAttrs, $resourceAttrs); + } + foreach ($resourceAttrs as $attrCode => $attrLabel) { + if (!isset($available[$attrCode])) { + $available[$attrCode] = empty($attrLabel) ? $attrCode : $attrLabel; + } + } + foreach (array_keys($available) as $code) { + if (in_array($code, $excludedAttrs) || ($includedAttrs && !in_array($code, $includedAttrs))) { + unset($available[$code]); + } + if (in_array($code, $entityOnlyAttrs)) { + $available[$code] .= ' *'; + } + } + return $available; + } + + /** + * Get excluded attributes for user type + * + * @param string $userType + * @param string $operation + * @return array + */ + public function getExcludedAttributes($userType, $operation) + { + return $this->getConfig()->getResourceExcludedAttributes($this->getResourceType(), $userType, $operation); + } + + /** + * Get forced attributes + * + * @return array + */ + public function getForcedAttributes() + { + return $this->getConfig()->getResourceForcedAttributes($this->getResourceType(), $this->getUserType()); + } + + /** + * Retrieve list of included attributes + * + * @param string $userType API user type + * @param string $operationType Type of operation: one of Mage_Api2_Model_Resource::OPERATION_ATTRIBUTE_... constant + * @return array + */ + public function getIncludedAttributes($userType, $operationType) + { + return $this->getConfig()->getResourceIncludedAttributes($this->getResourceType(), $userType, $operationType); + } + + /** + * Retrieve list of entity only attributes + * + * @param string $userType API user type + * @param string $operationType Type of operation: one of Mage_Api2_Model_Resource::OPERATION_ATTRIBUTE_... constant + * @return array + */ + public function getEntityOnlyAttributes($userType, $operationType) + { + return $this->getConfig()->getResourceEntityOnlyAttributes($this->getResourceType(), $userType, $operationType); + } + + /** + * Get available attributes of API resource from configuration file + * + * @return array + */ + public function getAvailableAttributesFromConfig() + { + return $this->getConfig()->getResourceAttributes($this->getResourceType()); + } + + /** + * Get available attributes of API resource from data base + * + * @return array + */ + public function getDbAttributes() + { + $available = array(); + $workModel = $this->getConfig()->getResourceWorkingModel($this->getResourceType()); + + if ($workModel) { + /* @var $resource Mage_Core_Model_Resource_Db_Abstract */ + $resource = Mage::getResourceModel($workModel); + + if (method_exists($resource, 'getMainTable')) { + $available = array_keys($resource->getReadConnection()->describeTable($resource->getMainTable())); + } + } + return $available; + } + + /** + * Get EAV attributes of working model + * + * @param bool $onlyVisible OPTIONAL Show only the attributes which are visible on frontend + * @param bool $excludeSystem OPTIONAL Exclude attributes marked as system + * @return array + */ + public function getEavAttributes($onlyVisible = false, $excludeSystem = false) + { + $attributes = array(); + $model = $this->getConfig()->getResourceWorkingModel($this->getResourceType()); + + /** @var $entityType Mage_Eav_Model_Entity_Type */ + $entityType = Mage::getModel('eav/entity_type')->load($model, 'entity_model'); + + /** @var $attribute Mage_Eav_Model_Entity_Attribute */ + foreach ($entityType->getAttributeCollection() as $attribute) { + if ($onlyVisible && !$attribute->getIsVisible()) { + continue; + } + if ($excludeSystem && $attribute->getIsSystem()) { + continue; + } + $attributes[$attribute->getAttributeCode()] = $attribute->getFrontendLabel(); + } + + return $attributes; + } + + /** + * Retrieve current store according to request and API user type + * + * @return Mage_Core_Model_Store + */ + protected function _getStore() + { + $store = $this->getRequest()->getParam('store'); + try { + if ($this->getUserType() != Mage_Api2_Model_Auth_User_Admin::USER_TYPE) { + // customer or guest role + if (!$store) { + $store = Mage::app()->getDefaultStoreView(); + } else { + $store = Mage::app()->getStore($store); + } + } else { + // admin role + if (is_null($store)) { + $store = Mage_Catalog_Model_Abstract::DEFAULT_STORE_ID; + } + $store = Mage::app()->getStore($store); + } + } catch (Mage_Core_Model_Store_Exception $e) { + // store does not exist + $this->_critical('Requested store is invalid', Mage_Api2_Model_Server::HTTP_BAD_REQUEST); + } + return $store; + } +} diff --git a/app/code/core/Mage/Api2/Model/Resource/Acl/Filter/Attribute.php b/app/code/core/Mage/Api2/Model/Resource/Acl/Filter/Attribute.php new file mode 100644 index 0000000000..cff1aa8067 --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Resource/Acl/Filter/Attribute.php @@ -0,0 +1,87 @@ + + */ +class Mage_Api2_Model_Resource_Acl_Filter_Attribute extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Attribute Filter resource ID "all" + */ + const FILTER_RESOURCE_ALL = 'all'; + + /** + * Initialize resource model + * + * @return void + */ + protected function _construct() + { + $this->_init('api2/acl_attribute', 'entity_id'); + } + + /** + * Get allowed attributes + * + * @param string $userType + * @param string $resourceId + * @param string $operation One of Mage_Api2_Model_Resource::OPERATION_ATTRIBUTE_... constant + * @return string|bool|null + */ + public function getAllowedAttributes($userType, $resourceId, $operation) + { + $select = $this->_getReadAdapter()->select() + ->from($this->getMainTable(), 'allowed_attributes') + ->where('user_type = ?', $userType) + ->where('resource_id = ?', $resourceId) + ->where('operation = ?', $operation); + + return $this->getReadConnection()->fetchOne($select); + } + + /** + * Check if ALL attributes allowed + * + * @param string $userType + * @return bool + */ + public function isAllAttributesAllowed($userType) + { + $resourceId = self::FILTER_RESOURCE_ALL; + + $select = $this->getReadConnection()->select() + ->from($this->getMainTable(), new Zend_Db_Expr('COUNT(*)')) + ->where('user_type = ?', $userType) + ->where('resource_id = ?', $resourceId); + + return ($this->getReadConnection()->fetchOne($select) == 1); + } +} diff --git a/app/code/core/Mage/Api2/Model/Resource/Acl/Filter/Attribute/Collection.php b/app/code/core/Mage/Api2/Model/Resource/Acl/Filter/Attribute/Collection.php new file mode 100644 index 0000000000..2f4c410a3e --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Resource/Acl/Filter/Attribute/Collection.php @@ -0,0 +1,57 @@ + + */ +class Mage_Api2_Model_Resource_Acl_Filter_Attribute_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Initialize collection model + * + * @return void + */ + protected function _construct() + { + $this->_init('api2/acl_filter_attribute'); + } + + /** + * Add filtering by user type + * + * @param string $userType + * @return Mage_Api2_Model_Resource_Acl_Filter_Attribute_Collection + */ + public function addFilterByUserType($userType) + { + $this->addFilter('user_type', $userType, 'public'); + return $this; + } +} diff --git a/app/code/core/Mage/Api2/Model/Resource/Acl/Global/Role.php b/app/code/core/Mage/Api2/Model/Resource/Acl/Global/Role.php new file mode 100644 index 0000000000..fff891c383 --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Resource/Acl/Global/Role.php @@ -0,0 +1,121 @@ + + * @method int getId + * @method string getRoleName + */ +class Mage_Api2_Model_Resource_Acl_Global_Role extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Initialize resource model + * + * @return void + */ + protected function _construct() + { + $this->_init('api2/acl_role', 'entity_id'); + } + + /** + * Create/update relation row of admin user to API2 role + * + * @param int $adminId Admin user id + * @param int $roleId API2 role id + * @return Mage_Api2_Model_Resource_Acl_Global_Role + */ + public function saveAdminToRoleRelation($adminId, $roleId) + { + if (Mage_Api2_Model_Acl_Global_Role::ROLE_GUEST_ID == $roleId + || Mage_Api2_Model_Acl_Global_Role::ROLE_CUSTOMER_ID == $roleId + ) { + Mage::throwException( + Mage::helper('api2')->__('The role is a special one and not for assigning it to admin users.') + ); + } + + $read = $this->_getReadAdapter(); + $select = $read->select() + ->from($this->getTable('api2/acl_user'), 'admin_id') + ->where('admin_id = ?', $adminId, Zend_Db::INT_TYPE); + + $write = $this->_getWriteAdapter(); + $table = $this->getTable('api2/acl_user'); + + if (false === $read->fetchOne($select)) { + $write->insert($table, array('admin_id' => $adminId, 'role_id' => $roleId)); + } else { + $write->update($table, array('role_id' => $roleId), array('admin_id = ?' => $adminId)); + } + + return $this; + } + + /** + * delete relation row of admin user to API2 role + * + * @param int $adminId Admin user id + * @param int $roleId API2 role id + * @return Mage_Api2_Model_Resource_Acl_Global_Role + */ + public function deleteAdminToRoleRelation($adminId, $roleId) + { + $write = $this->_getWriteAdapter(); + $table = $this->getTable('api2/acl_user'); + + $where = array( + 'role_id = ?' => $roleId, + 'admin_id = ?' => $adminId + ); + + $write->delete($table, $where); + + return $this; + } + + /** + * Get users + * + * @param Mage_Api2_Model_Acl_Global_Role $role + * @return array + */ + public function getRoleUsers(Mage_Api2_Model_Acl_Global_Role $role) + { + $adapter = $this->_getReadAdapter(); + $select = $adapter->select() + ->from($this->getTable('api2/acl_user')) + ->where('role_id=?', $role->getId()); + + $users = $adapter->fetchCol($select); + + return $users; + } +} diff --git a/app/code/core/Mage/Api2/Model/Resource/Acl/Global/Role/Collection.php b/app/code/core/Mage/Api2/Model/Resource/Acl/Global/Role/Collection.php new file mode 100644 index 0000000000..9efb28dd53 --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Resource/Acl/Global/Role/Collection.php @@ -0,0 +1,63 @@ + + */ +class Mage_Api2_Model_Resource_Acl_Global_Role_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Initialize collection model + * + * @return void + */ + protected function _construct() + { + $this->_init('api2/acl_global_role'); + } + + /** + * Add filter by admin user id and join table with appropriate information + * + * @param int $id Admin user id + * @return Mage_Api2_Model_Resource_Acl_Global_Role_Collection + */ + public function addFilterByAdminId($id) + { + $this->getSelect() + ->joinInner( + array('user' => $this->getTable('api2/acl_user')), + 'main_table.entity_id = user.role_id', + array('admin_id' => 'user.admin_id')) + ->where('user.admin_id = ?', $id, Zend_Db::INT_TYPE); + + return $this; + } +} diff --git a/app/code/core/Mage/Api2/Model/Resource/Acl/Global/Rule.php b/app/code/core/Mage/Api2/Model/Resource/Acl/Global/Rule.php new file mode 100644 index 0000000000..997100961d --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Resource/Acl/Global/Rule.php @@ -0,0 +1,45 @@ + + */ +class Mage_Api2_Model_Resource_Acl_Global_Rule extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Initialize resource model + * + * @return void + */ + protected function _construct() + { + $this->_init('api2/acl_rule', 'entity_id'); + } +} diff --git a/app/code/core/Mage/Api2/Model/Resource/Acl/Global/Rule/Collection.php b/app/code/core/Mage/Api2/Model/Resource/Acl/Global/Rule/Collection.php new file mode 100644 index 0000000000..41aa8ac7e3 --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Resource/Acl/Global/Rule/Collection.php @@ -0,0 +1,57 @@ + + */ +class Mage_Api2_Model_Resource_Acl_Global_Rule_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Initialize collection model + * + * @return void + */ + protected function _construct() + { + $this->_init('api2/acl_global_rule'); + } + + /** + * Add filtering by role ID + * + * @param int $roleId + * @return Mage_Api2_Model_Resource_Acl_Global_Rule_Collection + */ + public function addFilterByRoleId($roleId) + { + $this->addFilter('role_id', $roleId, 'public'); + return $this; + } +} diff --git a/app/code/core/Mage/Api2/Model/Resource/Setup.php b/app/code/core/Mage/Api2/Model/Resource/Setup.php new file mode 100644 index 0000000000..2494c610db --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Resource/Setup.php @@ -0,0 +1,36 @@ + + */ +class Mage_Api2_Model_Resource_Setup extends Mage_Core_Model_Resource_Setup +{ +} diff --git a/app/code/core/Mage/Api2/Model/Resource/Validator.php b/app/code/core/Mage/Api2/Model/Resource/Validator.php new file mode 100644 index 0000000000..9e274f31df --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Resource/Validator.php @@ -0,0 +1,102 @@ + + */ +abstract class Mage_Api2_Model_Resource_Validator +{ + /** + * Array of validation failure errors. + * + * @var array + */ + protected $_errors = array(); + + /** + * Set an array of errors + * + * @param array $data + * @return Mage_Api2_Model_Resource_Validator + */ + protected function _setErrors(array $data) + { + $this->_errors = array_values($data); + return $this; + } + + /** + * Add errors + * + * @param array $errors + * @return Mage_Api2_Model_Resource_Validator + */ + protected function _addErrors($errors) + { + foreach ($errors as $error) { + $this->_addError($error); + } + return $this; + } + + /** + * Add error + * + * @param string $error + * @return Mage_Api2_Model_Resource_Validator + */ + protected function _addError($error) + { + $this->_errors[] = $error; + return $this; + } + + /** + * Returns an array of errors that explain why the most recent isValidData() + * call returned false. The array keys are validation failure error identifiers, + * and the array values are the corresponding human-readable error strings. + * + * If isValidData() was never called or if the most recent isValidData() call + * returned true, then this method returns an empty array. + * + * @return array + */ + public function getErrors() + { + return $this->_errors; + } +} diff --git a/app/code/core/Mage/Api2/Model/Resource/Validator/Eav.php b/app/code/core/Mage/Api2/Model/Resource/Validator/Eav.php new file mode 100644 index 0000000000..91fac69963 --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Resource/Validator/Eav.php @@ -0,0 +1,172 @@ + + */ +class Mage_Api2_Model_Resource_Validator_Eav extends Mage_Api2_Model_Resource_Validator +{ + /** + * Config node key of current validator + */ + const CONFIG_NODE_KEY = 'eav'; + + /** + * Form path + * + * @var string + */ + protected $_formPath; + + /** + * Entity model + * + * @var Mage_Core_Model_Abstract + */ + protected $_entity; + + /** + * Form code + * + * @var string + */ + protected $_formCode; + + /** + * Eav form model + * + * @var Mage_Eav_Model_Form + */ + protected $_eavForm; + + /** + * Construct. Set all depends. + * + * Required parameteres for options: + * - resource + * + * @param array $options + * @throws Exception If passed parameter 'resource' is wrong + * @throws Exception If config parameter 'formPath' is empty + * @throws Exception If config parameter 'formCode' is empty + * @throws Exception If config parameter 'entity' is wrong + * @throws Exception If entity is not model + * @throws Exception If eav form is not found + */ + public function __construct($options) + { + if (!isset($options['resource']) || !$options['resource'] instanceof Mage_Api2_Model_Resource) { + throw new Exception("Passed parameter 'resource' is wrong."); + } + $resource = $options['resource']; + $userType = $resource->getUserType(); + + $validationConfig = $resource->getConfig()->getValidationConfig( + $resource->getResourceType(), self::CONFIG_NODE_KEY); + + if (empty($validationConfig[$userType]['form_model'])) { + throw new Exception("Config parameter 'formPath' is empty."); + } + $this->_formPath = $validationConfig[$userType]['form_model']; + + if (empty($validationConfig[$userType]['form_code'])) { + throw new Exception("Config parameter 'formCode' is empty."); + } + $this->_formCode = $validationConfig[$userType]['form_code']; + + if (empty($validationConfig[$userType]['entity_model'])) { + throw new Exception("Config parameter 'entity' is wrong."); + } + $this->_entity = Mage::getModel($validationConfig[$userType]['entity_model']); + if (empty($this->_entity) || !$this->_entity instanceof Mage_Core_Model_Abstract) { + throw new Exception("Entity is not model."); + } + + $this->_eavForm = Mage::getModel($this->_formPath); + if (empty($this->_eavForm) || !$this->_eavForm instanceof Mage_Eav_Model_Form) { + throw new Exception("Eav form '{$this->_formPath}' is not found."); + } + $this->_eavForm->setEntity($this->_entity) + ->setFormCode($this->_formCode) + ->ignoreInvisible(false); + } + + /** + * Filter request data. + * + * @param array $data + * @return array Filtered data + */ + public function filter(array $data) + { + return array_intersect_key($this->_eavForm->extractData($this->_eavForm->prepareRequest($data)), $data); + } + + /** + * Validate entity. + * If the $partial parameter is TRUE, then we validate only those parameters that were passed. + * + * If fails validation, then this method returns false, and + * getErrors() will return an array of errors that explain why the + * validation failed. + * + * @param array $data + * @param bool $partial + * @return bool + */ + public function isValidData(array $data, $partial = false) + { + $errors = array(); + foreach ($this->_eavForm->getAttributes() as $attribute) { + if ($partial && !array_key_exists($attribute->getAttributeCode(), $data)) { + continue; + } + if ($this->_eavForm->ignoreInvisible() && !$attribute->getIsVisible()) { + continue; + } + if (!isset($data[$attribute->getAttributeCode()])) { + $data[$attribute->getAttributeCode()] = null; + } + + $result = Mage_Eav_Model_Attribute_Data::factory($attribute, $this->_eavForm->getEntity()) + ->setExtractedData($data)->validateValue($data[$attribute->getAttributeCode()]); + if ($result !== true) { + $errors = array_merge($errors, $result); + } + } + + if (count($errors)) { + $this->_setErrors($errors); + return false; + } + + return true; + } +} diff --git a/app/code/core/Mage/Api2/Model/Resource/Validator/Fields.php b/app/code/core/Mage/Api2/Model/Resource/Validator/Fields.php new file mode 100644 index 0000000000..6d97e9a828 --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Resource/Validator/Fields.php @@ -0,0 +1,186 @@ + + */ +class Mage_Api2_Model_Resource_Validator_Fields extends Mage_Api2_Model_Resource_Validator +{ + /** + * Config node key of current validator + */ + const CONFIG_NODE_KEY = 'fields'; + + /** + * Resource + * + * @var Mage_Api2_Model_Resource + */ + protected $_resource; + + /** + * List of Validators (Zend_Validate_Interface) + * The key is a field name, a value is validator for this field + * + * @var array + */ + protected $_validators; + + /** + * List of required fields + * + * @var array + */ + protected $_requiredFields; + + /** + * Construct. Set all depends. + * + * Required parameteres for options: + * - resource + * + * @param array $options + * @throws Exception If passed parameter 'resource' is wrong + */ + public function __construct($options) + { + if (!isset($options['resource']) || !$options['resource'] instanceof Mage_Api2_Model_Resource) { + throw new Exception("Passed parameter 'resource' is wrong."); + } + $this->_resource = $options['resource']; + + $validationConfig = $this->_resource->getConfig()->getValidationConfig( + $this->_resource->getResourceType(), self::CONFIG_NODE_KEY); + if (!is_array($validationConfig)) { + $validationConfig = array(); + } + $this->_buildValidatorsChain($validationConfig); + } + + /** + * Build validator chain with config data + * + * @param array $validationConfig + * @throws Exception If validator type is not set + * @throws Exception If validator is not exist + */ + protected function _buildValidatorsChain(array $validationConfig) + { + foreach ($validationConfig as $field => $validatorsConfig) { + if (count($validatorsConfig)) { + $chainForOneField = new Zend_Validate(); + foreach ($validatorsConfig as $validatorName => $validatorConfig) { + // it is required field + if ('required' == $validatorName && 1 == $validatorConfig) { + $this->_requiredFields[] = $field; + continue; + } + // instantiation of the validator class + if (!isset($validatorConfig['type'])) { + throw new Exception("Validator type is not set for {$validatorName}"); + } + $validator = $this->_getValidatorInstance( + $validatorConfig['type'], + !empty($validatorConfig['options']) ? $validatorConfig['options'] : array() + ); + // set custom message + if (isset($validatorConfig['message'])) { + $validator->setMessage($validatorConfig['message']); + } + // add to list of validators + $chainForOneField->addValidator($validator); + } + $this->_validators[$field] = $chainForOneField; + } + } + } + + /** + * Get validator object instance + * Override the method if we need to use not only Zend validators! + * + * @param string $type + * @param array $options + * @return Zend_Validate_Interface + * @throws Exception If validator is not exist + */ + protected function _getValidatorInstance($type, $options) + { + $validatorClass = 'Zend_Validate_' . $type; + if (!class_exists($validatorClass)) { + throw new Exception("Validator {$type} is not exist"); + } + return new $validatorClass($options); + } + + /** + * Validate data. + * If fails validation, then this method returns false, and + * getErrors() will return an array of errors that explain why the + * validation failed. + * + * @param array $data + * @param bool $isPartial + * @return bool + */ + public function isValidData(array $data, $isPartial = false) + { + $isValid = true; + + // required fields + if (!$isPartial && count($this->_requiredFields) > 0) { + $notEmptyValidator = new Zend_Validate_NotEmpty(); + foreach ($this->_requiredFields as $requiredField) { + if (!$notEmptyValidator->isValid(isset($data[$requiredField]) ? $data[$requiredField] : null)) { + $isValid = false; + foreach ($notEmptyValidator->getMessages() as $message) { + $this->_addError(sprintf('%s: %s', $requiredField, $message)); + } + } + } + } + + // fields rules + foreach ($data as $field => $value) { + if (isset($this->_validators[$field])) { + /* @var $validator Zend_Validate_Interface */ + $validator = $this->_validators[$field]; + if (!$validator->isValid($value)) { + $isValid = false; + foreach ($validator->getMessages() as $message) { + $this->_addError(sprintf('%s: %s', $field, $message)); + } + } + } + } + + return $isValid; + } +} diff --git a/app/code/core/Mage/Api2/Model/Response.php b/app/code/core/Mage/Api2/Model/Response.php new file mode 100644 index 0000000000..3a889c69b7 --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Response.php @@ -0,0 +1,114 @@ + + */ +class Mage_Api2_Model_Response extends Zend_Controller_Response_Http +{ + /** + * Character set which must be used in response + */ + const RESPONSE_CHARSET = 'utf-8'; + + /**#@+ + * Default message types + */ + const MESSAGE_TYPE_SUCCESS = 'success'; + const MESSAGE_TYPE_ERROR = 'error'; + const MESSAGE_TYPE_WARNING = 'warning'; + /**#@- */ + + /** + * Messages + * + * @var array + */ + protected $_messages = array(); + + /** + * Set header appropriate to specified MIME type + * + * @param string $mimeType MIME type + * @return Mage_Api2_Model_Response + */ + public function setMimeType($mimeType) + { + return $this->setHeader('Content-Type', "{$mimeType}; charset=" . self::RESPONSE_CHARSET, true); + } + + /** + * Add message to responce + * + * @param string $message + * @param string $code + * @param array $params + * @param string $type + * return Mage_Api2_Model_Response + */ + public function addMessage($message, $code, $params = array(), $type = self::MESSAGE_TYPE_ERROR) + { + $params['message'] = $message; + $params['code'] = $code; + $this->_messages[$type][] = $params; + return $this; + } + + /** + * Has messages + * + * @return bool + */ + public function hasMessages() + { + return (bool)count($this->_messages) > 0; + } + + /** + * Return messages + * + * @return array + */ + public function getMessages() + { + return $this->_messages; + } + + /** + * Clear messages + * + * return Mage_Api2_Model_Response + */ + public function clearMessages() + { + $this->_messages = array(); + return $this; + } +} diff --git a/app/code/core/Mage/Api2/Model/Route/Abstract.php b/app/code/core/Mage/Api2/Model/Route/Abstract.php new file mode 100644 index 0000000000..1f8aef028b --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Route/Abstract.php @@ -0,0 +1,99 @@ + + */ +abstract class Mage_Api2_Model_Route_Abstract extends Zend_Controller_Router_Route +{ + /**#@+ + * Names for Zend_Controller_Router_Route::__construct params + */ + const PARAM_ROUTE = 'route'; + const PARAM_DEFAULTS = 'defaults'; + const PARAM_REQS = 'reqs'; + const PARAM_TRANSLATOR = 'translator'; + const PARAM_LOCALE = 'locale'; + /**#@- */ + + /* + * Default values of parent::__construct() params + * + * @var array + */ + protected $_paramsDefaultValues = array( + self::PARAM_ROUTE => null, + self::PARAM_DEFAULTS => array(), + self::PARAM_REQS => array(), + self::PARAM_TRANSLATOR => null, + self::PARAM_LOCALE => null + ); + + /** + * Process construct param and call parent::__construct() with params + * + * @param array $arguments + */ + public function __construct(array $arguments) + { + parent::__construct( + $this->_getArgumentValue(self::PARAM_ROUTE, $arguments), + $this->_getArgumentValue(self::PARAM_DEFAULTS, $arguments), + $this->_getArgumentValue(self::PARAM_REQS, $arguments), + $this->_getArgumentValue(self::PARAM_TRANSLATOR, $arguments), + $this->_getArgumentValue(self::PARAM_LOCALE, $arguments) + ); + } + + /** + * Retrieve argument value + * + * @param string $name argument name + * @param array $arguments + * @return mixed + */ + protected function _getArgumentValue($name, array $arguments) + { + return isset($arguments[$name]) ? $arguments[$name] : $this->_paramsDefaultValues[$name]; + } + + /** + * Matches a Request with parts defined by a map. Assigns and + * returns an array of variables on a successful match. + * + * @param Mage_Api2_Model_Request $request + * @param boolean $partial Partial path matching + * @return array|bool An array of assigned values or a boolean false on a mismatch + */ + public function match($request, $partial = false) + { + return parent::match(ltrim($request->getPathInfo(), $this->_urlDelimiter), $partial); + } +} diff --git a/app/code/core/Mage/Api2/Model/Route/ApiType.php b/app/code/core/Mage/Api2/Model/Route/ApiType.php new file mode 100644 index 0000000000..7aa20140e6 --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Route/ApiType.php @@ -0,0 +1,57 @@ + + */ +class Mage_Api2_Model_Route_ApiType extends Mage_Api2_Model_Route_Abstract implements Mage_Api2_Model_Route_Interface +{ + /** + * API url template with API type variable + */ + const API_ROUTE = 'api/:api_type'; + + /** + * Prepares the route for mapping by splitting (exploding) it + * to a corresponding atomic parts. These parts are assigned + * a position which is later used for matching and preparing values. + * + * @param string $route Map used to match with later submitted URL path + * @param array $defaults Defaults for map variables with keys as variable names + * @param array $reqs Regular expression requirements for variables (keys as variable names) + * @param Zend_Translate $translator Translator to use for this instance + * @param mixed $locale + */ + public function __construct($route, $defaults = array(), $reqs = array(), Zend_Translate $translator = null, + $locale = null + ) { + parent::__construct(array(Mage_Api2_Model_Route_Abstract::PARAM_ROUTE => self::API_ROUTE)); + } +} diff --git a/app/code/core/Mage/Api2/Model/Route/Interface.php b/app/code/core/Mage/Api2/Model/Route/Interface.php new file mode 100644 index 0000000000..617c6c6f4d --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Route/Interface.php @@ -0,0 +1,45 @@ + + */ +interface Mage_Api2_Model_Route_Interface +{ + /** + * Matches a Request with parts defined by a map. Assigns and + * returns an array of variables on a successful match. + * + * @param Mage_Api2_Model_Request $request + * @param boolean $partial Partial path matching + * @return array|false An array of assigned values or a false on a mismatch + */ + public function match($request, $partial = false); +} diff --git a/app/code/core/Mage/Api2/Model/Route/Rest.php b/app/code/core/Mage/Api2/Model/Route/Rest.php new file mode 100644 index 0000000000..ae8808dc71 --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Route/Rest.php @@ -0,0 +1,36 @@ + + */ +class Mage_Api2_Model_Route_Rest extends Mage_Api2_Model_Route_Abstract implements Mage_Api2_Model_Route_Interface +{ +} diff --git a/app/code/core/Mage/Api2/Model/Router.php b/app/code/core/Mage/Api2/Model/Router.php new file mode 100644 index 0000000000..361ddd2b55 --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Router.php @@ -0,0 +1,122 @@ + + */ +class Mage_Api2_Model_Router +{ + /** + * Routes which are stored in module config files api2.xml + * + * @var array + */ + protected $_routes = array(); + + /** + * Set routes + * + * @param array $routes + * @return Mage_Api2_Model_Router + */ + public function setRoutes(array $routes) + { + $this->_routes = $routes; + + return $this; + } + + /** + * Get routes + * + * @return array + */ + public function getRoutes() + { + return $this->_routes; + } + + /** + * Route the Request, the only responsibility of the class + * Find route that match current URL, set parameters of the route to Request object + * + * @param Mage_Api2_Model_Request $request + * @return Mage_Api2_Model_Request + * @throws Mage_Api2_Exception + */ + public function route(Mage_Api2_Model_Request $request) + { + $isMatched = false; + + /** @var $route Mage_Api2_Model_Route_Interface */ + foreach ($this->getRoutes() as $route) { + if ($params = $route->match($request)) { + $request->setParams($params); + $isMatched = true; + break; + } + } + if (!$isMatched) { + throw new Mage_Api2_Exception('Request does not match any route.', Mage_Api2_Model_Server::HTTP_NOT_FOUND); + } + if (!$request->getResourceType() || !$request->getModel()) { + throw new Mage_Api2_Exception('Matched resource is not properly set.', + Mage_Api2_Model_Server::HTTP_INTERNAL_ERROR); + } + return $request; + } + + /** + * Set API type to request as a result of one pass route + * + * @param Mage_Api2_Model_Request $request + * @param boolean $trimApiTypePath OPTIONAL If TRUE - /api/:api_type part of request path info will be trimmed + * @return Mage_Api2_Model_Router + * @throws Mage_Api2_Exception + */ + public function routeApiType(Mage_Api2_Model_Request $request, $trimApiTypePath = true) + { + /** @var $apiTypeRoute Mage_Api2_Model_Route_ApiType */ + $apiTypeRoute = Mage::getModel('api2/route_apiType'); + + if (!($apiTypeMatch = $apiTypeRoute->match($request, true))) { + throw new Mage_Api2_Exception('Request does not match type route.', Mage_Api2_Model_Server::HTTP_NOT_FOUND); + } + // Trim matched URI path for next routes + if ($trimApiTypePath) { + $matchedPathLength = strlen('/' . ltrim($apiTypeRoute->getMatchedPath(), '/')); + + $request->setPathInfo(substr($request->getPathInfo(), $matchedPathLength)); + } + $request->setParam('api_type', $apiTypeMatch['api_type']); + + return $this; + } +} diff --git a/app/code/core/Mage/Api2/Model/Server.php b/app/code/core/Mage/Api2/Model/Server.php new file mode 100644 index 0000000000..1b33ce2aa8 --- /dev/null +++ b/app/code/core/Mage/Api2/Model/Server.php @@ -0,0 +1,315 @@ + + */ +class Mage_Api2_Model_Server +{ + /** + * Api2 REST type + */ + const API_TYPE_REST = 'rest'; + + /**#@+ + * HTTP Response Codes + */ + const HTTP_OK = 200; + const HTTP_CREATED = 201; + const HTTP_MULTI_STATUS = 207; + const HTTP_BAD_REQUEST = 400; + const HTTP_UNAUTHORIZED = 401; + const HTTP_FORBIDDEN = 403; + const HTTP_NOT_FOUND = 404; + const HTTP_METHOD_NOT_ALLOWED = 405; + const HTTP_NOT_ACCEPTABLE = 406; + const HTTP_INTERNAL_ERROR = 500; + /**#@- */ + + /** + * List of api types + * + * @var array + */ + protected static $_apiTypes = array(self::API_TYPE_REST); + + /** + * @var Mage_Api2_Model_Auth_User_Abstract + */ + protected $_authUser; + + /** + * Run server + */ + public function run() + { + // can not use response object case + try { + /** @var $response Mage_Api2_Model_Response */ + $response = Mage::getSingleton('api2/response'); + } catch (Exception $e) { + Mage::logException($e); + + if (!headers_sent()) { + header('HTTP/1.1 ' . self::HTTP_INTERNAL_ERROR); + } + echo 'Service temporary unavailable'; + return; + } + // can not render errors case + try { + /** @var $request Mage_Api2_Model_Request */ + $request = Mage::getSingleton('api2/request'); + /** @var $renderer Mage_Api2_Model_Renderer_Interface */ + $renderer = Mage_Api2_Model_Renderer::factory($request->getAcceptTypes()); + } catch (Exception $e) { + Mage::logException($e); + + $response->setHttpResponseCode(self::HTTP_INTERNAL_ERROR) + ->setBody('Service temporary unavailable') + ->sendResponse(); + return; + } + // default case + try { + /** @var $apiUser Mage_Api2_Model_Auth_User_Abstract */ + $apiUser = $this->_authenticate($request); + + $this->_route($request) + ->_allow($request, $apiUser) + ->_dispatch($request, $response, $apiUser); + + if ($response->getHttpResponseCode() == self::HTTP_CREATED) { + // TODO: Re-factor this after _renderException refactoring + throw new Mage_Api2_Exception('Resource was partially created', self::HTTP_CREATED); + } + //NOTE: At this moment Renderer already could have some content rendered, so we should replace it + if ($response->isException()) { + throw new Mage_Api2_Exception('Unhandled simple errors.', self::HTTP_INTERNAL_ERROR); + } + } catch (Exception $e) { + Mage::logException($e); + $this->_renderException($e, $renderer, $response); + } + + $response->sendResponse(); + } + + /** + * Make internal call to api + * + * @param Mage_Api2_Model_Request $request + * @param Mage_Api2_Model_Response $response + * @return Mage_Api2_Model_Response + */ + public function internalCall(Mage_Api2_Model_Request $request, Mage_Api2_Model_Response $response) + { + $apiUser = $this->_getAuthUser(); + $this->_route($request) + ->_allow($request, $apiUser) + ->_dispatch($request, $response, $apiUser); + } + + /** + * Authenticate user + * + * @throws Exception + * @param Mage_Api2_Model_Request $request + * @return Mage_Api2_Model_Auth_User_Abstract + */ + protected function _authenticate(Mage_Api2_Model_Request $request) + { + /** @var $authManager Mage_Api2_Model_Auth */ + $authManager = Mage::getModel('api2/auth'); + + $this->_setAuthUser($authManager->authenticate($request)); + return $this->_getAuthUser(); + } + + /** + * Set auth user + * + * @throws Exception + * @param Mage_Api2_Model_Auth_User_Abstract $authUser + * @return Mage_Api2_Model_Server + */ + protected function _setAuthUser(Mage_Api2_Model_Auth_User_Abstract $authUser) + { + $this->_authUser = $authUser; + return $this; + } + + /** + * Retrieve existing auth user + * + * @throws Exception + * @return Mage_Api2_Model_Auth_User_Abstract + */ + protected function _getAuthUser() + { + if (!$this->_authUser) { + throw new Exception("Mage_Api2_Model_Server::internalCall() seems to be executed " + . "before Mage_Api2_Model_Server::run()"); + } + return $this->_authUser; + } + + /** + * Set all routes of the given api type to Route object + * Find route that match current URL, set parameters of the route to Request object + * + * @param Mage_Api2_Model_Request $request + * @return Mage_Api2_Model_Server + */ + protected function _route(Mage_Api2_Model_Request $request) + { + /** @var $router Mage_Api2_Model_Router */ + $router = Mage::getModel('api2/router'); + + $router->routeApiType($request, true) + ->setRoutes($this->_getConfig()->getRoutes($request->getApiType())) + ->route($request); + + return $this; + } + + /** + * Global ACL processing + * + * @param Mage_Api2_Model_Request $request + * @param Mage_Api2_Model_Auth_User_Abstract $apiUser + * @return Mage_Api2_Model_Server + * @throws Mage_Api2_Exception + */ + protected function _allow(Mage_Api2_Model_Request $request, Mage_Api2_Model_Auth_User_Abstract $apiUser) + { + /** @var $globalAcl Mage_Api2_Model_Acl_Global */ + $globalAcl = Mage::getModel('api2/acl_global'); + + if (!$globalAcl->isAllowed($apiUser, $request->getResourceType(), $request->getOperation())) { + throw new Mage_Api2_Exception('Access denied', self::HTTP_FORBIDDEN); + } + return $this; + } + + /** + * Load class file, instantiate resource class, set parameters to the instance, run resource internal dispatch + * method + * + * @param Mage_Api2_Model_Request $request + * @param Mage_Api2_Model_Response $response + * @param Mage_Api2_Model_Auth_User_Abstract $apiUser + * @return Mage_Api2_Model_Server + */ + protected function _dispatch( + Mage_Api2_Model_Request $request, + Mage_Api2_Model_Response $response, + Mage_Api2_Model_Auth_User_Abstract $apiUser + ) + { + /** @var $dispatcher Mage_Api2_Model_Dispatcher */ + $dispatcher = Mage::getModel('api2/dispatcher'); + $dispatcher->setApiUser($apiUser)->dispatch($request, $response); + + return $this; + } + + /** + * Get api2 config instance + * + * @return Mage_Api2_Model_Config + */ + protected function _getConfig() + { + return Mage::getModel('api2/config'); + } + + /** + * Process thrown exception + * Generate and set HTTP response code, error message to Response object + * + * @param Exception $exception + * @param Mage_Api2_Model_Renderer_Interface $renderer + * @param Mage_Api2_Model_Response $response + * @return Mage_Api2_Model_Server + */ + protected function _renderException(Exception $exception, Mage_Api2_Model_Renderer_Interface $renderer, + Mage_Api2_Model_Response $response + ) { + if ($exception instanceof Mage_Api2_Exception && $exception->getCode()) { + $httpCode = $exception->getCode(); + } else { + $httpCode = self::HTTP_INTERNAL_ERROR; + } + try { + //add last error to stack + $response->setException($exception); + + $messages = array(); + + /** @var Exception $exception */ + foreach ($response->getException() as $exception) { + $message = array('code' => $exception->getCode(), 'message' => $exception->getMessage()); + + if (Mage::getIsDeveloperMode()) { + $message['trace'] = $exception->getTraceAsString(); + } + $messages['messages']['error'][] = $message; + } + //set HTTP Code of last error, Content-Type and Body + $response->setBody($renderer->render($messages)); + $response->setHeader('Content-Type', sprintf( + '%s; charset=%s', $renderer->getMimeType(), Mage_Api2_Model_Response::RESPONSE_CHARSET + )); + } catch (Exception $e) { + //tunnelling of 406(Not acceptable) error + $httpCode = $e->getCode() == self::HTTP_NOT_ACCEPTABLE //$e->getCode() can result in one more loop + ? self::HTTP_NOT_ACCEPTABLE // of try..catch + : self::HTTP_INTERNAL_ERROR; + + //if error appeared in "error rendering" process then show it in plain text + $response->setBody($e->getMessage()); + $response->setHeader('Content-Type', 'text/plain; charset=' . Mage_Api2_Model_Response::RESPONSE_CHARSET); + } + $response->setHttpResponseCode($httpCode); + + return $this; + } + + /** + * Retrieve api types + * + * @return array + */ + public static function getApiTypes() + { + return self::$_apiTypes; + } +} diff --git a/app/code/core/Mage/Api2/controllers/Adminhtml/Api2/AttributeController.php b/app/code/core/Mage/Api2/controllers/Adminhtml/Api2/AttributeController.php new file mode 100644 index 0000000000..ac6a4a6cf7 --- /dev/null +++ b/app/code/core/Mage/Api2/controllers/Adminhtml/Api2/AttributeController.php @@ -0,0 +1,148 @@ + + */ +class Mage_Api2_Adminhtml_Api2_AttributeController extends Mage_Adminhtml_Controller_Action +{ + /** + * Show user types grid + */ + public function indexAction() + { + $this->_title($this->__('System')) + ->_title($this->__('Web Services')) + ->_title($this->__('REST Attributes')); + + $this->loadLayout()->_setActiveMenu('system/services/attributes'); + + $this->_addBreadcrumb($this->__('Web services'), $this->__('Web services')) + ->_addBreadcrumb($this->__('REST Attributes'), $this->__('REST Attributes')) + ->_addBreadcrumb($this->__('Attributes'), $this->__('Attributes')); + + $this->renderLayout(); + } + + /** + * Edit role + */ + public function editAction() + { + $this->loadLayout() + ->_setActiveMenu('system/services/attributes'); + + $type = $this->getRequest()->getParam('type'); + + $userTypes = Mage_Api2_Model_Auth_User::getUserTypes(); + if (!isset($userTypes[$type])) { + $this->_getSession()->addError($this->__('User type "%s" not found.', $type)); + $this->_redirect('*/*/'); + return; + } + + $this->_title($this->__('System')) + ->_title($this->__('Web Services')) + ->_title($this->__('REST ACL Attributes')); + + $title = $this->__('Edit %s ACL attribute rules', $userTypes[$type]); + $this->_title($title); + $this->_addBreadcrumb($title, $title); + + $this->renderLayout(); + } + + /** + * Save role + */ + public function saveAction() + { + $request = $this->getRequest(); + + $type = $request->getParam('type'); + + if (!$type) { + $this->_getSession()->addError( + $this->__('User type "%s" no longer exists', $type)); + $this->_redirect('*/*/'); + return; + } + + /** @var $session Mage_Adminhtml_Model_Session */ + $session = $this->_getSession(); + + try { + /** @var $ruleTree Mage_Api2_Model_Acl_Global_Rule_Tree */ + $ruleTree = Mage::getSingleton( + 'api2/acl_global_rule_tree', + array('type' => Mage_Api2_Model_Acl_Global_Rule_Tree::TYPE_ATTRIBUTE) + ); + + /** @var $attribute Mage_Api2_Model_Acl_Filter_Attribute */ + $attribute = Mage::getModel('api2/acl_filter_attribute'); + + /** @var $collection Mage_Api2_Model_Resource_Acl_Filter_Attribute_Collection */ + $collection = $attribute->getCollection(); + $collection->addFilterByUserType($type); + + /** @var $model Mage_Api2_Model_Acl_Filter_Attribute */ + foreach ($collection as $model) { + $model->delete(); + } + + foreach ($ruleTree->getPostResources() as $resourceId => $operations) { + if (Mage_Api2_Model_Acl_Global_Rule::RESOURCE_ALL === $resourceId) { + $attribute->setUserType($type) + ->setResourceId($resourceId) + ->save(); + } else { + foreach ($operations as $operation => $attributes) { + $attribute->setId(null) + ->isObjectNew(true); + + $attribute->setUserType($type) + ->setResourceId($resourceId) + ->setOperation($operation) + ->setAllowedAttributes(implode(',', array_keys($attributes))) + ->save(); + } + } + } + + $session->addSuccess($this->__('The attribute rules were saved.')); + } catch (Mage_Core_Exception $e) { + $session->addError($e->getMessage()); + } catch (Exception $e) { + $session->addException($e, $this->__('An error occurred while saving attribute rules.')); + } + + $this->_redirect('*/*/edit', array('type' => $type)); + } +} diff --git a/app/code/core/Mage/Api2/controllers/Adminhtml/Api2/RoleController.php b/app/code/core/Mage/Api2/controllers/Adminhtml/Api2/RoleController.php new file mode 100644 index 0000000000..451dfe7b5f --- /dev/null +++ b/app/code/core/Mage/Api2/controllers/Adminhtml/Api2/RoleController.php @@ -0,0 +1,340 @@ + + */ +class Mage_Api2_Adminhtml_Api2_RoleController extends Mage_Adminhtml_Controller_Action +{ + /** + * Show grid + */ + public function indexAction() + { + $this->_title($this->__('System')) + ->_title($this->__('Web Services')) + ->_title($this->__('REST Roles')); + + $this->loadLayout()->_setActiveMenu('system/services/roles'); + $this->_addBreadcrumb($this->__('Web services'), $this->__('Web services')); + $this->_addBreadcrumb($this->__('REST Roles'), $this->__('REST Roles')); + $this->_addBreadcrumb($this->__('Roles'), $this->__('Roles')); + + $this->renderLayout(); + } + + /** + * Updating grid by ajax + */ + public function gridAction() + { + $this->loadLayout(); + $this->renderLayout(); + } + + /** + * Updating users grid by ajax + */ + public function usersGridAction() + { + $id = $this->getRequest()->getParam('id', false); + + $this->loadLayout(); + /** @var $grid Mage_Api2_Block_Adminhtml_Roles_Tab_Users */ + $grid = $this->getLayout()->getBlock('adminhtml.role.edit.tab.users'); + $grid->setUsers($this->_getUsers($id)); + + $this->renderLayout(); + } + + /** + * Create new role + */ + public function newAction() + { + $this->_title($this->__('System')) + ->_title($this->__('Web Services')) + ->_title($this->__('Rest Roles')); + + $this->loadLayout()->_setActiveMenu('system/services/roles'); + $this->_addBreadcrumb($this->__('Web services'), $this->__('Web services')); + $this->_addBreadcrumb($this->__('REST Roles'), $this->__('REST Roles')); + $this->_addBreadcrumb($this->__('Roles'), $this->__('Roles')); + + $breadCrumb = $this->__('Add New Role'); + $breadCrumbTitle = $this->__('Add New Role'); + $this->_title($this->__('New Role')); + + $this->_addBreadcrumb($breadCrumb, $breadCrumbTitle); + + $this->renderLayout(); + } + + /** + * Edit role + */ + public function editAction() + { + $id = (int) $this->getRequest()->getParam('id'); + /** @var $role Mage_Api2_Model_Acl_Global_Role */ + $role = Mage::getModel('api2/acl_global_role')->load($id); + + if (!$role->getId()) { + $this->_getSession()->addError($this->__('Role "%s" not found.', $id)); + $this->_redirect('*/*/'); + return; + } + + $this->loadLayout()->_setActiveMenu('system/services/roles'); + + $this->_title($this->__('System')) + ->_title($this->__('Web Services')) + ->_title($this->__('Rest Roles')); + + $breadCrumb = $this->__('Edit Role'); + $breadCrumbTitle = $this->__('Edit Role'); + $this->_title($this->__('Edit Role')); + $this->_addBreadcrumb($breadCrumb, $breadCrumbTitle); + + /** @var $tabs Mage_Api2_Block_Adminhtml_Roles_Tabs */ + $tabs = $this->getLayout()->getBlock('adminhtml.role.edit.tabs'); + $tabs->setRole($role); + /** @var $child Mage_Adminhtml_Block_Template */ + foreach ($tabs->getChild() as $child) { + $child->setData('role', $role); + } + + /** @var $buttons Mage_Api2_Block_Adminhtml_Roles_Buttons */ + $buttons = $this->getLayout()->getBlock('adminhtml.roles.buttons'); + $buttons->setRole($role); + + /** @var $users Mage_Api2_Block_Adminhtml_Roles_Tab_Users */ + $users = $this->getLayout()->getBlock('adminhtml.role.edit.tab.users'); + $users->setUsers($this->_getUsers($id)); + + //$this->getLayout()->getBlock('adminhtml.role.edit.tab.resources')->getResTreeJson(); + //exit; + + $this->renderLayout(); + } + + /** + * Save role + */ + public function saveAction() + { + $request = $this->getRequest(); + + $id = $request->getParam('id', false); + /** @var $role Mage_Api2_Model_Acl_Global_Role */ + $role = Mage::getModel('api2/acl_global_role')->load($id); + + if (!$role->getId() && $id) { + $this->_getSession()->addError( + $this->__('Role "%s" no longer exists', $role->getData('role_name'))); + $this->_redirect('*/*/'); + return; + } + + $roleUsers = $request->getParam('in_role_users', null); + parse_str($roleUsers, $roleUsers); + $roleUsers = array_keys($roleUsers); + + $oldRoleUsers = $this->getRequest()->getParam('in_role_users_old'); + parse_str($oldRoleUsers, $oldRoleUsers); + $oldRoleUsers = array_keys($oldRoleUsers); + + /** @var $session Mage_Adminhtml_Model_Session */ + $session = $this->_getSession(); + + try { + $role->setRoleName($this->getRequest()->getParam('role_name', false)) + ->save(); + + foreach($oldRoleUsers as $oUid) { + $this->_deleteUserFromRole($oUid, $role->getId()); + } + + foreach ($roleUsers as $nRuid) { + $this->_addUserToRole($nRuid, $role->getId()); + } + + /** + * Save rules with resources + */ + /** @var $rule Mage_Api2_Model_Acl_Global_Rule */ + $rule = Mage::getModel('api2/acl_global_rule'); + if ($id) { + $collection = $rule->getCollection(); + $collection->addFilterByRoleId($role->getId()); + + /** @var $model Mage_Api2_Model_Acl_Global_Rule */ + foreach ($collection as $model) { + $model->delete(); + } + } + + /** @var $ruleTree Mage_Api2_Model_Acl_Global_Rule_Tree */ + $ruleTree = Mage::getSingleton( + 'api2/acl_global_rule_tree', + array('type' => Mage_Api2_Model_Acl_Global_Rule_Tree::TYPE_PRIVILEGE) + ); + $resources = $ruleTree->getPostResources(); + $id = $role->getId(); + foreach ($resources as $resourceId => $privileges) { + foreach ($privileges as $privilege => $allow) { + if (!$allow) { + continue; + } + + $rule->setId(null) + ->isObjectNew(true); + + $rule->setRoleId($id) + ->setResourceId($resourceId) + ->setPrivilege($privilege) + ->save(); + } + } + + $session->addSuccess($this->__('The role has been saved.')); + } catch (Mage_Core_Exception $e) { + $session->addError($e->getMessage()); + } catch (Exception $e) { + $session->addException($e, $this->__('An error occurred while saving role.')); + } + + $this->_redirect('*/*/edit', array('id'=>$id)); + } + + /** + * Delete role + */ + public function deleteAction() + { + $id = $this->getRequest()->getParam('id', false); + + try { + /** @var $model Mage_Api2_Model_Acl_Global_Role */ + $model = Mage::getModel("api2/acl_global_role"); + $model->load($id)->delete(); + $this->_getSession()->addSuccess($this->__('Role has been deleted.')); + } catch (Mage_Core_Exception $e) { + $this->_getSession()->addError($e->getMessage()); + } catch (Exception $e) { + $this->_getSession()->addException($e, $this->__('An error occurred while deleting the role.')); + } + + $this->_redirect("*/*/"); + } + + /** + * Check against ACL + * + * @return bool + */ + protected function _isAllowed() + { + /** @var $session Mage_Admin_Model_Session */ + $session = Mage::getSingleton('admin/session'); + return $session->isAllowed('system/api/roles_rest'); + } + + /** + * Get API2 roles ajax grid action + */ + public function rolesGridAction() + { + /** @var $model Mage_Admin_Model_User */ + $model = Mage::getModel('admin/user'); + $model->load($this->getRequest()->getParam('user_id')); + + Mage::register('permissions_user', $model); + $this->getResponse() + ->setBody($this->getLayout()->createBlock('api2/adminhtml_permissions_user_edit_tab_roles')->toHtml()); + } + + /** + * Get users possessing the role + * + * @param int $id + * @return array|mixed + */ + protected function _getUsers($id) + { + if ( $this->getRequest()->getParam('in_role_users') != "" ) { + return $this->getRequest()->getParam('in_role_users'); + } + + /** @var $role Mage_Api2_Model_Acl_Global_Role */ + $role = Mage::getModel('api2/acl_global_role'); + $role->setId($id); + + /** @var $resource Mage_Api2_Model_Resource_Acl_Global_Role */ + $resource = $role->getResource(); + $users = $resource->getRoleUsers($role); + + if (sizeof($users) == 0) { + $users = array(); + } + + return $users; + } + + /** + * Take away user role + * + * @param int $adminId + * @param int $roleId + * @return Mage_Api2_Adminhtml_Api2_RoleController + */ + protected function _deleteUserFromRole($adminId, $roleId) + { + /** @var $resourceModel Mage_Api2_Model_Resource_Acl_Global_Role */ + $resourceModel = Mage::getResourceModel('api2/acl_global_role'); + $resourceModel->deleteAdminToRoleRelation($adminId, $roleId); + return $this; + } + + /** + * Give user a role + * + * @param int $adminId + * @param int $roleId + * @return Mage_Api2_Adminhtml_Api2_RoleController + */ + protected function _addUserToRole($adminId, $roleId) + { + /** @var $resourceModel Mage_Api2_Model_Resource_Acl_Global_Role */ + $resourceModel = Mage::getResourceModel('api2/acl_global_role'); + $resourceModel->saveAdminToRoleRelation($adminId, $roleId); + return $this; + } +} diff --git a/app/code/core/Mage/Api2/etc/adminhtml.xml b/app/code/core/Mage/Api2/etc/adminhtml.xml new file mode 100644 index 0000000000..14995e592a --- /dev/null +++ b/app/code/core/Mage/Api2/etc/adminhtml.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + REST - Roles + 30 + + + Add + 10 + + + Edit + 20 + + + Delete + 30 + + + + + REST - Attributes + 40 + + + Edit + 10 + + + + + + + + + + + + + + + + + + REST - Roles + 30 + adminhtml/api2_role + + + REST - Attributes + 40 + adminhtml/api2_attribute + + + + + + + diff --git a/app/code/core/Mage/Api2/etc/config.xml b/app/code/core/Mage/Api2/etc/config.xml new file mode 100644 index 0000000000..1238eda13f --- /dev/null +++ b/app/code/core/Mage/Api2/etc/config.xml @@ -0,0 +1,212 @@ + + + + + + 1.0.0.0 + + + + + + + + Web Services definition files (api2.xml). + CONFIG_API2 + + + + + + Mage_Api2_Model + api2_resource + + + Mage_Api2_Model_Resource + + + api2_acl_role
+
+ + api2_acl_user
+
+ + api2_acl_rule
+
+ + api2_acl_attribute
+
+
+
+
+ + + Mage_Api2_Block + + + + + Mage_Api2_Helper + + + + + + Mage_Api2 + Mage_Api2_Model_Resource_Setup + + + + + + + + api2/observer + saveAdminToRoleRelation + + + + + + + + api2/auth_adapter_oauth + + 1 + 10 + + + + + api2/auth_user_admin + 1 + + + api2/auth_user_customer + 1 + + + api2/auth_user_guest + 1 + + + + + + + application/json + api2/request_interpreter_json + + + + + text/plain + api2/request_interpreter_query + + + + + application/xml + api2/request_interpreter_xml + + + application/xhtml+xml + api2/request_interpreter_xml + + + text/xml + api2/request_interpreter_xml + + + + + + + + */* + api2/renderer_json + + + application/json + api2/renderer_json + + + + + text/plain + api2/renderer_query + + + + + text/xml + api2/renderer_xml + + + application/xml + api2/renderer_xml + + + application/xhtml+xml + api2/renderer_xml + + + + +
+ + + + + + Mage_Api2_Adminhtml + + + + + + + + + + api2.xml + + + + + + + + api2/observer + catalogAttributeSaveAfter + + + + + +
diff --git a/app/code/core/Mage/Api2/sql/api2_setup/install-1.0.0.0.php b/app/code/core/Mage/Api2/sql/api2_setup/install-1.0.0.0.php new file mode 100644 index 0000000000..904c45c5e8 --- /dev/null +++ b/app/code/core/Mage/Api2/sql/api2_setup/install-1.0.0.0.php @@ -0,0 +1,183 @@ +startSetup(); + +/** @var $adapter Varien_Db_Adapter_Pdo_Mysql */ +$adapter = $installer->getConnection(); + +/** + * Create table 'api2/acl_role' + */ +$table = $adapter->newTable($installer->getTable('api2/acl_role')) + ->addColumn('entity_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Entity ID') + ->addColumn('created_at', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, + array( + 'nullable' => false, + 'default' => Varien_Db_Ddl_Table::TIMESTAMP_INIT + ), 'Created At') + ->addColumn('updated_at', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, + array( + 'nullable' => true + ), 'Updated At') + ->addColumn('role_name', Varien_Db_Ddl_Table::TYPE_VARCHAR, + 255, array('nullable' => false), 'Name of role') + ->addIndex($installer->getIdxName('api2/acl_role', array('created_at')), array('created_at')) + ->addIndex($installer->getIdxName('api2/acl_role', array('updated_at')), array('updated_at')) + ->setComment('Api2 Global ACL Roles'); +$adapter->createTable($table); + +// Create Guest and Customer User Roles +$adapter->insertMultiple( + $installer->getTable('api2/acl_role'), + array( + array('role_name' => 'Guest'), + array('role_name' => 'Customer') + ) +); + +/** + * Create table 'api2/acl_user' + */ +$table = $adapter->newTable($installer->getTable('api2/acl_user')) + ->addColumn('admin_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'unsigned' => true, + 'nullable' => false, + ), 'Admin ID') + ->addColumn('role_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'unsigned' => true, + 'nullable' => false, + ), 'Role ID') + ->addIndex( + $installer->getIdxName( + $installer->getTable('api2/acl_user'), + array('admin_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('admin_id'), + array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE)) + ->addForeignKey( + $installer->getFkName('api2/acl_user', 'admin_id', 'admin/user', 'user_id'), + 'admin_id', + $installer->getTable('admin/user'), + 'user_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, + Varien_Db_Ddl_Table::ACTION_CASCADE) + ->addForeignKey( + $installer->getFkName('api2/acl_user', 'role_id', 'api2/acl_role', 'entity_id'), + 'role_id', + $installer->getTable('api2/acl_role'), + 'entity_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, + Varien_Db_Ddl_Table::ACTION_CASCADE) + ->setComment('Api2 Global ACL Users'); +$adapter->createTable($table); + +/** + * Create table 'api2/acl_rule' + */ +$table = $adapter->newTable($installer->getTable('api2/acl_rule')) + ->addColumn('entity_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'primary' => true, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + ), 'Entity ID') + ->addColumn('role_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'unsigned' => true, + 'nullable' => false, + ), 'Role ID') + ->addColumn('resource_id', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + 'nullable' => false + ), 'Resource ID') + ->addColumn('privilege', Varien_Db_Ddl_Table::TYPE_TEXT, 20, array( + 'nullable' => true + ), 'ACL Privilege') + ->addIndex( + $installer->getIdxName( + $installer->getTable('api2/acl_rule'), + array('role_id', 'resource_id', 'privilege'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('role_id', 'resource_id', 'privilege'), + array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE)) + ->addForeignKey( + $installer->getFkName('api2/acl_rule', 'role_id', 'api2/acl_role', 'entity_id'), + 'role_id', + $installer->getTable('api2/acl_role'), + 'entity_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, + Varien_Db_Ddl_Table::ACTION_CASCADE) + ->setComment('Api2 Global ACL Rules'); +$adapter->createTable($table); + +/** +* Create table 'api2/acl_attribute' +*/ +$table = $adapter->newTable($installer->getTable('api2/acl_attribute')) + ->addColumn('entity_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Entity ID') + ->addColumn('user_type', Varien_Db_Ddl_Table::TYPE_VARCHAR, 20, array( + 'nullable' => false + ), 'Type of user') + ->addColumn('resource_id', Varien_Db_Ddl_Table::TYPE_VARCHAR, 255, array( + 'nullable' => false + ), 'Resource ID') + ->addColumn('operation', Varien_Db_Ddl_Table::TYPE_VARCHAR, 20, array( + 'nullable' => false + ), 'Operation') + ->addColumn('allowed_attributes', Varien_Db_Ddl_Table::TYPE_TEXT, null, array( + 'nullable' => true + ), 'Allowed attributes') + ->addIndex( + $installer->getIdxName('api2/acl_attribute', array('user_type')), + array('user_type')) + ->addIndex( + $installer->getIdxName( + $installer->getTable('api2/acl_attribute'), + array('user_type', 'resource_id', 'operation'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('user_type', 'resource_id', 'operation'), + array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE)) + ->setComment('Api2 Filter ACL Attributes'); +$adapter->createTable($table); + +$installer->endSetup(); diff --git a/app/code/core/Mage/Authorizenet/etc/system.xml b/app/code/core/Mage/Authorizenet/etc/system.xml index e487f8a589..b56b9565bd 100755 --- a/app/code/core/Mage/Authorizenet/etc/system.xml +++ b/app/code/core/Mage/Authorizenet/etc/system.xml @@ -146,6 +146,7 @@ text + validate-email 130 1 1 diff --git a/app/code/core/Mage/Backup/Helper/Data.php b/app/code/core/Mage/Backup/Helper/Data.php index f08d8f23be..41c96cae53 100644 --- a/app/code/core/Mage/Backup/Helper/Data.php +++ b/app/code/core/Mage/Backup/Helper/Data.php @@ -224,7 +224,7 @@ public function getCreateSuccessMessageByType($type) { $messagesMap = array( self::TYPE_SYSTEM_SNAPSHOT => $this->__('The system backup has been created.'), - self::TYPE_SNAPSHOT_WITHOUT_MEDIA => $this->__('The system backup has been created.'), + self::TYPE_SNAPSHOT_WITHOUT_MEDIA => $this->__('The system (excluding Media) backup has been created.'), self::TYPE_MEDIA => $this->__('The database and media backup has been created.'), self::TYPE_DB => $this->__('The database backup has been created.') ); diff --git a/app/code/core/Mage/Bundle/Block/Adminhtml/Catalog/Product/Edit/Tab/Bundle/Option.php b/app/code/core/Mage/Bundle/Block/Adminhtml/Catalog/Product/Edit/Tab/Bundle/Option.php index 9970e7dff5..d7f1d76639 100644 --- a/app/code/core/Mage/Bundle/Block/Adminhtml/Catalog/Product/Edit/Tab/Bundle/Option.php +++ b/app/code/core/Mage/Bundle/Block/Adminhtml/Catalog/Product/Edit/Tab/Bundle/Option.php @@ -33,11 +33,35 @@ */ class Mage_Bundle_Block_Adminhtml_Catalog_Product_Edit_Tab_Bundle_Option extends Mage_Adminhtml_Block_Widget { + /** + * Form element + * + * @var Varien_Data_Form_Element_Abstract|null + */ protected $_element = null; + + /** + * List of customer groups + * + * @deprecated since 1.7.0.0 + * @var array|null + */ protected $_customerGroups = null; + + /** + * List of websites + * + * @deprecated since 1.7.0.0 + * @var array|null + */ protected $_websites = null; - protected $_oprions = null; + /** + * List of bundle product options + * + * @var array|null + */ + protected $_options = null; /** * Bundle option renderer class constructor diff --git a/app/code/core/Mage/Bundle/Model/Sales/Order/Pdf/Items/Abstract.php b/app/code/core/Mage/Bundle/Model/Sales/Order/Pdf/Items/Abstract.php index a2810309cd..0b790fbfd3 100644 --- a/app/code/core/Mage/Bundle/Model/Sales/Order/Pdf/Items/Abstract.php +++ b/app/code/core/Mage/Bundle/Model/Sales/Order/Pdf/Items/Abstract.php @@ -24,7 +24,6 @@ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ - /** * Sales Order Pdf Items renderer * @@ -134,7 +133,9 @@ public function isChildCalculated($item = null) if ($parentItem) { $options = $parentItem->getProductOptions(); if ($options) { - if (isset($options['product_calculations']) && $options['product_calculations'] == Mage_Catalog_Model_Product_Type_Abstract::CALCULATE_CHILD) { + if (isset($options['product_calculations']) && + $options['product_calculations'] == Mage_Catalog_Model_Product_Type_Abstract::CALCULATE_CHILD + ) { return true; } else { return false; @@ -143,7 +144,9 @@ public function isChildCalculated($item = null) } else { $options = $item->getProductOptions(); if ($options) { - if (isset($options['product_calculations']) && $options['product_calculations'] == Mage_Catalog_Model_Product_Type_Abstract::CALCULATE_CHILD) { + if (isset($options['product_calculations']) && + $options['product_calculations'] == Mage_Catalog_Model_Product_Type_Abstract::CALCULATE_CHILD + ) { return false; } else { return true; diff --git a/app/code/core/Mage/Bundle/Model/Sales/Order/Pdf/Items/Creditmemo.php b/app/code/core/Mage/Bundle/Model/Sales/Order/Pdf/Items/Creditmemo.php index 4021fc9274..5d2ec976ce 100644 --- a/app/code/core/Mage/Bundle/Model/Sales/Order/Pdf/Items/Creditmemo.php +++ b/app/code/core/Mage/Bundle/Model/Sales/Order/Pdf/Items/Creditmemo.php @@ -24,7 +24,6 @@ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ - /** * Sales Order Creditmemo Pdf default items renderer * @@ -47,8 +46,8 @@ public function draw() $items = $this->getChilds($item); $_prevOptionId = ''; - $drawItems = array(); - $leftBound = 35; + $drawItems = array(); + $leftBound = 35; $rightBound = 565; foreach ($items as $_item) { @@ -66,7 +65,7 @@ public function draw() if (!isset($drawItems[$optionId])) { $drawItems[$optionId] = array( 'lines' => array(), - 'height' => 10 + 'height' => 15 ); } @@ -75,13 +74,13 @@ public function draw() if ($_prevOptionId != $attributes['option_id']) { $line[0] = array( 'font' => 'italic', - 'text' => Mage::helper('core/string')->str_split($attributes['option_label'],60, true, true), + 'text' => Mage::helper('core/string')->str_split($attributes['option_label'], 38, true, true), 'feed' => $x ); $drawItems[$optionId] = array( 'lines' => array($line), - 'height' => 10 + 'height' => 15 ); $line = array(); @@ -99,7 +98,7 @@ public function draw() } $line[] = array( - 'text' => Mage::helper('core/string')->str_split($name, 60, true, true), + 'text' => Mage::helper('core/string')->str_split($name, 35, true, true), 'feed' => $feed ); @@ -108,7 +107,7 @@ public function draw() // draw SKUs if (!$_item->getOrderItem()->getParentItem()) { $text = array(); - foreach (Mage::helper('core/string')->str_split($item->getSku(), 30) as $part) { + foreach (Mage::helper('core/string')->str_split($item->getSku(), 17) as $part) { $text[] = $part; } $line[] = array( @@ -166,7 +165,9 @@ public function draw() $x += 45; // draw Total(inc) - $text = $order->formatPriceTxt($_item->getRowTotal()+$_item->getTaxAmount()-$_item->getDiscountAmount()); + $text = $order->formatPriceTxt( + $_item->getRowTotal() + $_item->getTaxAmount() - $_item->getDiscountAmount() + ); $line[] = array( 'text' => $text, 'feed' => $rightBound, @@ -186,17 +187,19 @@ public function draw() foreach ($options['options'] as $option) { $lines = array(); $lines[][] = array( - 'text' => Mage::helper('core/string')->str_split(strip_tags($option['label']), 70, true, true), + 'text' => Mage::helper('core/string')->str_split(strip_tags($option['label']), 40, true, true), 'font' => 'italic', 'feed' => $leftBound ); if ($option['value']) { $text = array(); - $_printValue = isset($option['print_value']) ? $option['print_value'] : strip_tags($option['value']); + $_printValue = isset($option['print_value']) + ? $option['print_value'] + : strip_tags($option['value']); $values = explode(', ', $_printValue); foreach ($values as $value) { - foreach (Mage::helper('core/string')->str_split($value, 50, true, true) as $_value) { + foreach (Mage::helper('core/string')->str_split($value, 30, true, true) as $_value) { $text[] = $_value; } } @@ -209,7 +212,7 @@ public function draw() $drawItems[] = array( 'lines' => $lines, - 'height' => 10 + 'height' => 15 ); } } diff --git a/app/code/core/Mage/Bundle/Model/Sales/Order/Pdf/Items/Invoice.php b/app/code/core/Mage/Bundle/Model/Sales/Order/Pdf/Items/Invoice.php index 58747a7f30..0439f1a769 100644 --- a/app/code/core/Mage/Bundle/Model/Sales/Order/Pdf/Items/Invoice.php +++ b/app/code/core/Mage/Bundle/Model/Sales/Order/Pdf/Items/Invoice.php @@ -24,7 +24,6 @@ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ - /** * Sales Order Invoice Pdf default items renderer * @@ -65,7 +64,7 @@ public function draw() if (!isset($drawItems[$optionId])) { $drawItems[$optionId] = array( 'lines' => array(), - 'height' => 10 + 'height' => 15 ); } @@ -73,13 +72,13 @@ public function draw() if ($_prevOptionId != $attributes['option_id']) { $line[0] = array( 'font' => 'italic', - 'text' => Mage::helper('core/string')->str_split($attributes['option_label'], 70, true, true), + 'text' => Mage::helper('core/string')->str_split($attributes['option_label'], 45, true, true), 'feed' => 35 ); $drawItems[$optionId] = array( 'lines' => array($line), - 'height' => 10 + 'height' => 15 ); $line = array(); @@ -97,14 +96,14 @@ public function draw() $name = $_item->getName(); } $line[] = array( - 'text' => Mage::helper('core/string')->str_split($name, 55, true, true), + 'text' => Mage::helper('core/string')->str_split($name, 35, true, true), 'feed' => $feed ); // draw SKUs if (!$_item->getOrderItem()->getParentItem()) { $text = array(); - foreach (Mage::helper('core/string')->str_split($item->getSku(), 30) as $part) { + foreach (Mage::helper('core/string')->str_split($item->getSku(), 17) as $part) { $text[] = $part; } $line[] = array( @@ -155,17 +154,19 @@ public function draw() foreach ($options['options'] as $option) { $lines = array(); $lines[][] = array( - 'text' => Mage::helper('core/string')->str_split(strip_tags($option['label']), 70, true, true), + 'text' => Mage::helper('core/string')->str_split(strip_tags($option['label']), 40, true, true), 'font' => 'italic', 'feed' => 35 ); if ($option['value']) { $text = array(); - $_printValue = isset($option['print_value']) ? $option['print_value'] : strip_tags($option['value']); + $_printValue = isset($option['print_value']) + ? $option['print_value'] + : strip_tags($option['value']); $values = explode(', ', $_printValue); foreach ($values as $value) { - foreach (Mage::helper('core/string')->str_split($value, 50, true, true) as $_value) { + foreach (Mage::helper('core/string')->str_split($value, 30, true, true) as $_value) { $text[] = $_value; } } @@ -178,7 +179,7 @@ public function draw() $drawItems[] = array( 'lines' => $lines, - 'height' => 10 + 'height' => 15 ); } } diff --git a/app/code/core/Mage/Bundle/Model/Sales/Order/Pdf/Items/Shipment.php b/app/code/core/Mage/Bundle/Model/Sales/Order/Pdf/Items/Shipment.php index 93d15661f0..af96b79518 100644 --- a/app/code/core/Mage/Bundle/Model/Sales/Order/Pdf/Items/Shipment.php +++ b/app/code/core/Mage/Bundle/Model/Sales/Order/Pdf/Items/Shipment.php @@ -24,7 +24,6 @@ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ - /** * Sales Order Shipment Pdf items renderer * @@ -66,7 +65,7 @@ public function draw() if (!isset($drawItems[$optionId])) { $drawItems[$optionId] = array( 'lines' => array(), - 'height' => 10 + 'height' => 15 ); } @@ -74,13 +73,13 @@ public function draw() if ($_prevOptionId != $attributes['option_id']) { $line[0] = array( 'font' => 'italic', - 'text' => Mage::helper('core/string')->str_split($attributes['option_label'],60, true, true), + 'text' => Mage::helper('core/string')->str_split($attributes['option_label'], 60, true, true), 'feed' => 60 ); $drawItems[$optionId] = array( 'lines' => array($line), - 'height' => 10 + 'height' => 15 ); $line = array(); @@ -89,7 +88,9 @@ public function draw() } } - if (($this->isShipmentSeparately() && $_item->getParentItem()) || (!$this->isShipmentSeparately() && !$_item->getParentItem())) { + if (($this->isShipmentSeparately() && $_item->getParentItem()) + || (!$this->isShipmentSeparately() && !$_item->getParentItem()) + ) { if (isset($shipItems[$_item->getId()])) { $qty = $shipItems[$_item->getId()]->getQty()*1; } else if ($_item->getIsVirtual()) { @@ -125,7 +126,7 @@ public function draw() // draw SKUs $text = array(); - foreach (Mage::helper('core/string')->str_split($_item->getSku(), 30) as $part) { + foreach (Mage::helper('core/string')->str_split($_item->getSku(), 25) as $part) { $text[] = $part; } $line[] = array( @@ -150,7 +151,9 @@ public function draw() if ($option['value']) { $text = array(); - $_printValue = isset($option['print_value']) ? $option['print_value'] : strip_tags($option['value']); + $_printValue = isset($option['print_value']) + ? $option['print_value'] + : strip_tags($option['value']); $values = explode(', ', $_printValue); foreach ($values as $value) { foreach (Mage::helper('core/string')->str_split($value, 50, true, true) as $_value) { @@ -166,7 +169,7 @@ public function draw() $drawItems[] = array( 'lines' => $lines, - 'height' => 10 + 'height' => 15 ); } } diff --git a/app/code/core/Mage/Captcha/Block/Captcha/Zend.php b/app/code/core/Mage/Captcha/Block/Captcha/Zend.php index ff9bec2da9..cb17ea6d9b 100755 --- a/app/code/core/Mage/Captcha/Block/Captcha/Zend.php +++ b/app/code/core/Mage/Captcha/Block/Captcha/Zend.php @@ -57,8 +57,13 @@ public function getTemplate() */ public function getRefreshUrl() { - $url = Mage::app()->getStore()->isAdmin() ? "adminhtml/refresh/refresh" : "captcha/refresh"; - return Mage::getUrl($url, array('_secure' => Mage::app()->getRequest()->isSecure())); + $isSecure = Mage::app()->getStore()->isAdmin() + ? Mage::app()->getStore()->isAdminUrlSecure() + : Mage::getConfig()->shouldUrlBeSecure(Mage::app()->getRequest()->getPathInfo()); + return Mage::getUrl( + Mage::app()->getStore()->isAdmin() ? 'adminhtml/refresh/refresh' : 'captcha/refresh', + array('_secure' => $isSecure) + ); } /** diff --git a/app/code/core/Mage/Captcha/etc/config.xml b/app/code/core/Mage/Captcha/etc/config.xml index ee3251ded8..0377c43f0e 100755 --- a/app/code/core/Mage/Captcha/etc/config.xml +++ b/app/code/core/Mage/Captcha/etc/config.xml @@ -163,6 +163,13 @@ + + + + captcha + + + zend diff --git a/app/code/core/Mage/Catalog/Block/Product/Abstract.php b/app/code/core/Mage/Catalog/Block/Product/Abstract.php index f2dce13bc7..a4728a51bd 100644 --- a/app/code/core/Mage/Catalog/Block/Product/Abstract.php +++ b/app/code/core/Mage/Catalog/Block/Product/Abstract.php @@ -262,6 +262,7 @@ public function getReviewsSummaryHtml(Mage_Catalog_Model_Product $product, $temp * * @param string $type * @param string $template + * @return string */ public function addReviewSummaryTemplate($type, $template) { @@ -312,9 +313,10 @@ public function getTierPriceTemplate() return $this->getData('tier_price_template'); } /** - * Returns product tierprice block html + * Returns product tier price block html * * @param Mage_Catalog_Model_Product $product + * @return string */ public function getTierPriceHtml($product = null) { @@ -396,8 +398,8 @@ public function getTierPrices($product = null) * to get correct values in different products lists. * E.g. crosssells, upsells, new products, recently viewed * - * @param Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Collection $collection - * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Collection + * @param Mage_Catalog_Model_Resource_Product_Collection $collection + * @return Mage_Catalog_Model_Resource_Product_Collection */ protected function _addProductAttributesAndPrices(Mage_Catalog_Model_Resource_Product_Collection $collection) { @@ -496,7 +498,7 @@ public function getColumnCount() * Add row size depends on page layout * * @param string $pageLayout - * @param int $rowSize + * @param int $columnCount * @return Mage_Catalog_Block_Product_List */ public function addColumnCountLayoutDepend($pageLayout, $columnCount) diff --git a/app/code/core/Mage/Catalog/Model/Api2/Product.php b/app/code/core/Mage/Catalog/Model/Api2/Product.php new file mode 100644 index 0000000000..e79bd133c2 --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Api2/Product.php @@ -0,0 +1,94 @@ + + */ +class Mage_Catalog_Model_Api2_Product extends Mage_Api2_Model_Resource +{ + /** + * Get available attributes of API resource + * + * @param string $userType + * @param string $operation + * @return array + */ + public function getAvailableAttributes($userType, $operation) + { + $attributes = $this->getAvailableAttributesFromConfig(); + /** @var $entityType Mage_Eav_Model_Entity_Type */ + $entityType = Mage::getModel('eav/entity_type')->loadByCode('catalog_product'); + $entityOnlyAttrs = $this->getEntityOnlyAttributes($userType, $operation); + /** @var $attribute Mage_Catalog_Model_Resource_Eav_Attribute */ + foreach ($entityType->getAttributeCollection() as $attribute) { + if ($this->_isAttributeVisible($attribute, $userType)) { + $attributes[$attribute->getAttributeCode()] = $attribute->getFrontendLabel(); + } + } + $excludedAttrs = $this->getExcludedAttributes($userType, $operation); + $includedAttrs = $this->getIncludedAttributes($userType, $operation); + foreach ($attributes as $code => $label) { + if (in_array($code, $excludedAttrs) || ($includedAttrs && !in_array($code, $includedAttrs))) { + unset($attributes[$code]); + } + if (in_array($code, $entityOnlyAttrs)) { + $attributes[$code] .= ' *'; + } + } + + return $attributes; + } + + /** + * Define if attribute should be visible for passed user type + * + * @param Mage_Catalog_Model_Resource_Eav_Attribute $attribute + * @param string $userType + * @return bool + */ + protected function _isAttributeVisible(Mage_Catalog_Model_Resource_Eav_Attribute $attribute, $userType) + { + $isAttributeVisible = false; + if ($userType == Mage_Api2_Model_Auth_User_Admin::USER_TYPE) { + $isAttributeVisible = $attribute->getIsVisible(); + } else { + $systemAttributesForNonAdmin = array( + 'sku', 'name', 'short_description', 'description', 'tier_price', 'meta_title', 'meta_description', + 'meta_keyword', + ); + if ($attribute->getIsUserDefined()) { + $isAttributeVisible = $attribute->getIsVisibleOnFront(); + } else if (in_array($attribute->getAttributeCode(), $systemAttributesForNonAdmin)) { + $isAttributeVisible = true; + } + } + return (bool)$isAttributeVisible; + } +} diff --git a/app/code/core/Mage/Catalog/Model/Api2/Product/Category.php b/app/code/core/Mage/Catalog/Model/Api2/Product/Category.php new file mode 100644 index 0000000000..94bb871d1b --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Api2/Product/Category.php @@ -0,0 +1,36 @@ + + */ +class Mage_Catalog_Model_Api2_Product_Category extends Mage_Api2_Model_Resource +{ +} diff --git a/app/code/core/Mage/Catalog/Model/Api2/Product/Category/Rest.php b/app/code/core/Mage/Catalog/Model/Api2/Product/Category/Rest.php new file mode 100644 index 0000000000..673b9c6332 --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Api2/Product/Category/Rest.php @@ -0,0 +1,105 @@ + + */ +abstract class Mage_Catalog_Model_Api2_Product_Category_Rest extends Mage_Catalog_Model_Api2_Product_Rest +{ + /** + * Product category assign is not available + * + * @param array $data + */ + protected function _create(array $data) + { + $this->_critical(self::RESOURCE_METHOD_NOT_ALLOWED); + } + + /** + * Product category update is not available + * + * @param array $data + */ + protected function _update(array $data) + { + $this->_critical(self::RESOURCE_METHOD_NOT_ALLOWED); + } + + /** + * Retrieve product data + * + * @return array + */ + protected function _retrieveCollection() + { + $return = array(); + + foreach ($this->_getCategoryIds() as $categoryId) { + $return[] = array('category_id' => $categoryId); + } + return $return; + } + + /** + * Only admin have permissions for product category unassign + */ + protected function _delete() + { + $this->_critical(self::RESOURCE_METHOD_NOT_ALLOWED); + } + + /** + * Load category by id + * + * @param int $categoryId + * @return Mage_Catalog_Model_Category + */ + protected function _getCategoryById($categoryId) + { + /** @var $category Mage_Catalog_Model_Category */ + $category = Mage::getModel('catalog/category')->setStoreId(0)->load($categoryId); + if (!$category->getId()) { + $this->_critical('Category not found', Mage_Api2_Model_Server::HTTP_NOT_FOUND); + } + + return $category; + } + + /** + * Get assigned categories ids + * + * @return array + */ + protected function _getCategoryIds() + { + return $this->_getProduct()->getCategoryCollection()->addIsActiveFilter()->getAllIds(); + } +} diff --git a/app/code/core/Mage/Catalog/Model/Api2/Product/Category/Rest/Admin/V1.php b/app/code/core/Mage/Catalog/Model/Api2/Product/Category/Rest/Admin/V1.php new file mode 100644 index 0000000000..a7ca806113 --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Api2/Product/Category/Rest/Admin/V1.php @@ -0,0 +1,146 @@ + + */ +class Mage_Catalog_Model_Api2_Product_Category_Rest_Admin_V1 extends Mage_Catalog_Model_Api2_Product_Category_Rest +{ + /** + * Product category assign + * + * @param array $data + * @return string + */ + protected function _create(array $data) + { + /* @var $validator Mage_Api2_Model_Resource_Validator_Fields */ + $validator = Mage::getResourceModel('api2/validator_fields', array('resource' => $this)); + if (!$validator->isValidData($data)) { + foreach ($validator->getErrors() as $error) { + $this->_error($error, Mage_Api2_Model_Server::HTTP_BAD_REQUEST); + } + $this->_critical(self::RESOURCE_DATA_PRE_VALIDATION_ERROR); + } + + $product = $this->_getProduct(); + $category = $this->_getCategoryById($data['category_id']); + + $categoryIds = $product->getCategoryIds(); + if (!is_array($categoryIds)) { + $categoryIds = array(); + } + if (in_array($category->getId(), $categoryIds)) { + $this->_critical(sprintf('Product #%d is already assigned to category #%d', + $product->getId(), $category->getId()), Mage_Api2_Model_Server::HTTP_BAD_REQUEST); + } + if ($category->getId() == Mage_Catalog_Model_Category::TREE_ROOT_ID) { + $this->_critical('Cannot assign product to tree root category.', Mage_Api2_Model_Server::HTTP_BAD_REQUEST); + } + $categoryIds[] = $category->getId(); + $product->setCategoryIds(implode(',', $categoryIds)); + + try{ + $product->save(); + } catch (Mage_Core_Exception $e) { + $this->_critical($e->getMessage(), Mage_Api2_Model_Server::HTTP_INTERNAL_ERROR); + } catch (Exception $e) { + $this->_critical(self::RESOURCE_INTERNAL_ERROR); + } + + return $this->_getLocation($category); + } + + /** + * Product category unassign + * + * @return bool + */ + protected function _delete() + { + $product = $this->_getProduct(); + $category = $this->_getCategoryById($this->getRequest()->getParam('category_id')); + + $categoryIds = $product->getCategoryIds(); + $categoryToBeDeletedId = array_search($category->getId(), $categoryIds); + if (false === $categoryToBeDeletedId) { + $this->_critical(sprintf('Product #%d isn\'t assigned to category #%d', + $product->getId(), $category->getId()), Mage_Api2_Model_Server::HTTP_BAD_REQUEST); + } + + // delete category + unset($categoryIds[$categoryToBeDeletedId]); + $product->setCategoryIds(implode(',', $categoryIds)); + + try{ + $product->save(); + } catch (Mage_Core_Exception $e) { + $this->_critical($e->getMessage(), Mage_Api2_Model_Server::HTTP_INTERNAL_ERROR); + } catch (Exception $e) { + $this->_critical(self::RESOURCE_INTERNAL_ERROR); + } + + return true; + } + + /** + * Return all assigned categories + * + * @return array + */ + protected function _getCategoryIds() + { + return $this->_getProduct()->getCategoryIds(); + } + + /** + * Get resource location + * + * @param Mage_Core_Model_Abstract $resource + * @return string URL + */ + protected function _getLocation($resource) + { + /** @var $apiTypeRoute Mage_Api2_Model_Route_ApiType */ + $apiTypeRoute = Mage::getModel('api2/route_apiType'); + + $chain = $apiTypeRoute->chain(new Zend_Controller_Router_Route( + $this->getConfig()->getRouteWithEntityTypeAction($this->getResourceType()) + )); + $params = array( + 'api_type' => $this->getRequest()->getApiType(), + 'id' => $this->getRequest()->getParam('id'), + 'category_id' => $resource->getId() + ); + $uri = $chain->assemble($params); + + return '/' . $uri; + } +} diff --git a/app/code/core/Mage/Catalog/Model/Api2/Product/Category/Rest/Customer/V1.php b/app/code/core/Mage/Catalog/Model/Api2/Product/Category/Rest/Customer/V1.php new file mode 100644 index 0000000000..5164eb3867 --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Api2/Product/Category/Rest/Customer/V1.php @@ -0,0 +1,36 @@ + + */ +class Mage_Catalog_Model_Api2_Product_Category_Rest_Customer_V1 extends Mage_Catalog_Model_Api2_Product_Category_Rest +{ +} diff --git a/app/code/core/Mage/Catalog/Model/Api2/Product/Category/Rest/Guest/V1.php b/app/code/core/Mage/Catalog/Model/Api2/Product/Category/Rest/Guest/V1.php new file mode 100644 index 0000000000..9261cb50b2 --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Api2/Product/Category/Rest/Guest/V1.php @@ -0,0 +1,36 @@ + + */ +class Mage_Catalog_Model_Api2_Product_Category_Rest_Guest_V1 extends Mage_Catalog_Model_Api2_Product_Category_Rest +{ +} diff --git a/app/code/core/Mage/Catalog/Model/Api2/Product/Image.php b/app/code/core/Mage/Catalog/Model/Api2/Product/Image.php new file mode 100644 index 0000000000..33e9f12a9a --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Api2/Product/Image.php @@ -0,0 +1,36 @@ + + */ +class Mage_Catalog_Model_Api2_Product_Image extends Mage_Api2_Model_Resource +{ +} diff --git a/app/code/core/Mage/Catalog/Model/Api2/Product/Image/Rest.php b/app/code/core/Mage/Catalog/Model/Api2/Product/Image/Rest.php new file mode 100644 index 0000000000..badc3d7b75 --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Api2/Product/Image/Rest.php @@ -0,0 +1,221 @@ + + */ +abstract class Mage_Catalog_Model_Api2_Product_Image_Rest extends Mage_Catalog_Model_Api2_Product_Rest +{ + /** + * Attribute code for media gallery + */ + const GALLERY_ATTRIBUTE_CODE = 'media_gallery'; + + /** + * Allowed MIME types for image + * + * @var array + */ + protected $_mimeTypes = array( + 'image/jpeg' => 'jpg', + 'image/gif' => 'gif', + 'image/png' => 'png' + ); + + /** + * Retrieve product image data for customer and guest roles + * + * @throws Mage_Api2_Exception + * @return array + */ + protected function _retrieve() + { + $imageData = array(); + $imageId = (int)$this->getRequest()->getParam('image'); + $galleryData = $this->_getProduct()->getData(self::GALLERY_ATTRIBUTE_CODE); + + if (!isset($galleryData['images']) || !is_array($galleryData['images'])) { + $this->_critical(self::RESOURCE_NOT_FOUND); + } + foreach ($galleryData['images'] as $image) { + if ($image['value_id'] == $imageId && !$image['disabled']) { + $imageData = $this->_formatImageData($image); + break; + } + } + if (empty($imageData)) { + $this->_critical(self::RESOURCE_NOT_FOUND); + } + return $imageData; + } + + /** + * Retrieve product images data for customer and guest + * + * @return array + */ + protected function _retrieveCollection() + { + $images = array(); + $galleryData = $this->_getProduct()->getData(self::GALLERY_ATTRIBUTE_CODE); + if (isset($galleryData['images']) && is_array($galleryData['images'])) { + foreach ($galleryData['images'] as $image) { + if (!$image['disabled']) { + $images[] = $this->_formatImageData($image); + } + } + } + return $images; + } + + /** + * Retrieve media gallery + * + * @throws Mage_Api2_Exception + * @return Mage_Catalog_Model_Product_Attribute_Backend_Media + */ + protected function _getMediaGallery() + { + $attributes = $this->_getProduct()->getTypeInstance(true)->getSetAttributes($this->_getProduct()); + + if (!isset($attributes[self::GALLERY_ATTRIBUTE_CODE]) + || !$attributes[self::GALLERY_ATTRIBUTE_CODE] instanceof Mage_Eav_Model_Entity_Attribute_Abstract + ) { + $this->_critical('Requested product does not support images', Mage_Api2_Model_Server::HTTP_BAD_REQUEST); + } + $galleryAttribute = $attributes[self::GALLERY_ATTRIBUTE_CODE]; + /** @var $mediaGallery Mage_Catalog_Model_Product_Attribute_Backend_Media */ + $mediaGallery = $galleryAttribute->getBackend(); + return $mediaGallery; + } + + /** + * Create image data representation for API + * + * @param array $image + * @return array + */ + protected function _formatImageData($image) + { + $result = array( + 'id' => $image['value_id'], + 'label' => $image['label'], + 'position' => $image['position'], + 'exclude' => $image['disabled'], + 'url' => $this->_getMediaConfig()->getMediaUrl($image['file']), + 'types' => $this->_getImageTypesAssignedToProduct($image['file']) + ); + return $result; + } + + /** + * Retrieve image types assigned to product (base, small, thumbnail) + * + * @param string $imageFile + * @return array + */ + protected function _getImageTypesAssignedToProduct($imageFile) + { + $types = array(); + foreach ($this->_getProduct()->getMediaAttributes() as $attribute) { + if ($this->_getProduct()->getData($attribute->getAttributeCode()) == $imageFile) { + $types[] = $attribute->getAttributeCode(); + } + } + return $types; + } + + /** + * Retrieve media config + * + * @return Mage_Catalog_Model_Product_Media_Config + */ + protected function _getMediaConfig() + { + return Mage::getSingleton('catalog/product_media_config'); + } + + /** + * Create file name from received data + * + * @param array $data + * @return string + */ + protected function _getFileName($data) + { + $fileName = 'image'; + if (isset($data['file_name']) && $data['file_name']) { + $fileName = $data['file_name']; + } + $fileName .= '.' . $this->_getExtensionByMimeType($data['file_mime_type']); + return $fileName; + } + + /** + * Retrieve file extension using MIME type + * + * @throws Mage_Api2_Exception + * @param string $mimeType + * @return string + */ + protected function _getExtensionByMimeType($mimeType) + { + if (!array_key_exists($mimeType, $this->_mimeTypes)) { + $this->_critical('Unsuppoted image MIME type', Mage_Api2_Model_Server::HTTP_BAD_REQUEST); + } + return $this->_mimeTypes[$mimeType]; + } + + /** + * Get file URI by its id. File URI is used by media backend to identify image + * + * @throws Mage_Api2_Exception + * @param int $imageId + * @return string + */ + protected function _getImageFileById($imageId) + { + $file = null; + $mediaGalleryData = $this->_getProduct()->getData('media_gallery'); + if (!isset($mediaGalleryData['images'])) { + $this->_critical(self::RESOURCE_NOT_FOUND); + } + foreach ($mediaGalleryData['images'] as $image) { + if ($image['value_id'] == $imageId) { + $file = $image['file']; + break; + } + } + if (!($file && $this->_getMediaGallery()->getImage($this->_getProduct(), $file))) { + $this->_critical(self::RESOURCE_NOT_FOUND); + } + return $file; + } +} diff --git a/app/code/core/Mage/Catalog/Model/Api2/Product/Image/Rest/Admin/V1.php b/app/code/core/Mage/Catalog/Model/Api2/Product/Image/Rest/Admin/V1.php new file mode 100644 index 0000000000..e0c3a82762 --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Api2/Product/Image/Rest/Admin/V1.php @@ -0,0 +1,236 @@ + + */ +class Mage_Catalog_Model_Api2_Product_Image_Rest_Admin_V1 extends Mage_Catalog_Model_Api2_Product_Image_Rest +{ + /** + * Product image add + * + * @throws Mage_Api2_Exception + * @param array $data + * @return string + */ + protected function _create(array $data) + { + /* @var $validator Mage_Catalog_Model_Api2_Product_Image_Validator_Image */ + $validator = Mage::getModel('catalog/api2_product_image_validator_image'); + if (!$validator->isValidData($data)) { + foreach ($validator->getErrors() as $error) { + $this->_error($error, Mage_Api2_Model_Server::HTTP_BAD_REQUEST); + } + $this->_critical(self::RESOURCE_DATA_PRE_VALIDATION_ERROR); + } + $imageFileContent = @base64_decode($data['file_content'], true); + if (!$imageFileContent) { + $this->_critical('The image content must be valid base64 encoded data', + Mage_Api2_Model_Server::HTTP_BAD_REQUEST); + } + unset($data['file_content']); + + $apiTempDir = Mage::getBaseDir('var') . DS . 'api' . DS . Mage::getSingleton('api/session')->getSessionId(); + $imageFileName = $this->_getFileName($data); + + try { + $ioAdapter = new Varien_Io_File(); + $ioAdapter->checkAndCreateFolder($apiTempDir); + $ioAdapter->open(array('path' => $apiTempDir)); + $ioAdapter->write($imageFileName, $imageFileContent, 0666); + unset($imageFileContent); + + // try to create Image object to check if image data is valid + try { + new Varien_Image($apiTempDir . DS . $imageFileName); + } catch (Exception $e) { + $ioAdapter->rmdir($apiTempDir, true); + $this->_critical($e->getMessage(), Mage_Api2_Model_Server::HTTP_INTERNAL_ERROR); + } + $product = $this->_getProduct(); + $imageFileUri = $this->_getMediaGallery()->addImage($product, $apiTempDir . DS . $imageFileName); + $ioAdapter->rmdir($apiTempDir, true); + // updateImage() must be called to add image data that is missing after addImage() call + $this->_getMediaGallery()->updateImage($product, $imageFileUri, $data); + + if (isset($data['types'])) { + $this->_getMediaGallery()->setMediaAttribute($product, $data['types'], $imageFileUri); + } + $product->save(); + return $this->_getImageLocation($this->_getCreatedImageId($imageFileUri)); + } catch (Mage_Core_Exception $e) { + $this->_critical($e->getMessage(), Mage_Api2_Model_Server::HTTP_INTERNAL_ERROR); + } catch (Exception $e) { + $this->_critical(self::RESOURCE_UNKNOWN_ERROR); + } + } + + /** + * Get added image ID + * + * @throws Mage_Api2_Exception + * @param string $imageFileUri + * @return int + */ + protected function _getCreatedImageId($imageFileUri) + { + $imageId = null; + + $imageData = Mage::getResourceModel('catalog/product_attribute_backend_media') + ->loadGallery($this->_getProduct(), $this->_getMediaGallery()); + foreach ($imageData as $image) { + if ($image['file'] == $imageFileUri) { + $imageId = $image['value_id']; + break; + } + } + if (!$imageId) { + $this->_critical('Unknown error during image save', Mage_Api2_Model_Server::HTTP_INTERNAL_ERROR); + } + return $imageId; + } + + /** + * Retrieve product images data + * + * @throws Mage_Api2_Exception + * @return array + */ + protected function _retrieve() + { + $result = array(); + $imageId = (int) $this->getRequest()->getParam('image'); + $galleryData = $this->_getProduct()->getData(self::GALLERY_ATTRIBUTE_CODE); + if (!isset($galleryData['images']) || !is_array($galleryData['images'])) { + $this->_critical('Product image not found', Mage_Api2_Model_Server::HTTP_NOT_FOUND); + } + foreach ($galleryData['images'] as &$image) { + if ($image['value_id'] == $imageId) { + $result = $this->_formatImageData($image); + break; + } + } + if (empty($result)) { + $this->_critical('Product image not found', Mage_Api2_Model_Server::HTTP_NOT_FOUND); + } + return $result; + } + + /** + * Update product image + * + * @throws Mage_Api2_Exception + * @param array $data + * @return bool + */ + protected function _update(array $data) + { + $imageId = (int)$this->getRequest()->getParam('image'); + $imageFileUri = $this->_getImageFileById($imageId); + $product = $this->_getProduct(); + $this->_getMediaGallery()->updateImage($product, $imageFileUri, $data); + if (isset($data['types']) && is_array($data['types'])) { + $assignedTypes = $this->_getImageTypesAssignedToProduct($imageFileUri); + $typesToBeCleared = array_diff($assignedTypes, $data['types']); + if (count($typesToBeCleared) > 0) { + $this->_getMediaGallery()->clearMediaAttribute($product, $typesToBeCleared); + } + $this->_getMediaGallery()->setMediaAttribute($product, $data['types'], $imageFileUri); + } + try { + $product->save(); + } catch (Mage_Core_Exception $e) { + $this->_critical($e->getMessage(), Mage_Api2_Model_Server::HTTP_INTERNAL_ERROR); + } catch (Exception $e) { + $this->_critical(self::RESOURCE_INTERNAL_ERROR); + } + } + + /** + * Product image delete + * + * @throws Mage_Api2_Exception + */ + protected function _delete() + { + $imageId = (int)$this->getRequest()->getParam('image'); + $product = $this->_getProduct(); + $imageFileUri = $this->_getImageFileById($imageId); + $this->_getMediaGallery()->removeImage($product, $imageFileUri); + try { + $product->save(); + } catch (Mage_Core_Exception $e) { + $this->_critical($e->getMessage(), Mage_Api2_Model_Server::HTTP_INTERNAL_ERROR); + } catch (Exception $e) { + $this->_critical(self::RESOURCE_INTERNAL_ERROR); + } + } + + /** + * Retrieve product images data + * + * @throws Mage_Api2_Exception + * @return array + */ + protected function _retrieveCollection() + { + $images = array(); + $galleryData = $this->_getProduct()->getData(self::GALLERY_ATTRIBUTE_CODE); + if (isset($galleryData['images']) && is_array($galleryData['images'])) { + foreach ($galleryData['images'] as $image) { + $images[] = $this->_formatImageData($image); + } + } + return $images; + } + + /** + * Get image resource location + * + * @param int $imageId + * @return string URL + */ + protected function _getImageLocation($imageId) + { + /* @var $apiTypeRoute Mage_Api2_Model_Route_ApiType */ + $apiTypeRoute = Mage::getModel('api2/route_apiType'); + + $chain = $apiTypeRoute->chain( + new Zend_Controller_Router_Route($this->getConfig()->getRouteWithEntityTypeAction($this->getResourceType())) + ); + $params = array( + 'api_type' => $this->getRequest()->getApiType(), + 'id' => $this->getRequest()->getParam('id'), + 'image' => $imageId + ); + $uri = $chain->assemble($params); + return '/' . $uri; + } +} diff --git a/app/code/core/Mage/Catalog/Model/Api2/Product/Image/Rest/Customer/V1.php b/app/code/core/Mage/Catalog/Model/Api2/Product/Image/Rest/Customer/V1.php new file mode 100644 index 0000000000..ce8d358139 --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Api2/Product/Image/Rest/Customer/V1.php @@ -0,0 +1,36 @@ + + */ +class Mage_Catalog_Model_Api2_Product_Image_Rest_Customer_V1 extends Mage_Catalog_Model_Api2_Product_Image_Rest +{ +} diff --git a/app/code/core/Mage/Catalog/Model/Api2/Product/Image/Rest/Guest/V1.php b/app/code/core/Mage/Catalog/Model/Api2/Product/Image/Rest/Guest/V1.php new file mode 100644 index 0000000000..c3e04f1ce6 --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Api2/Product/Image/Rest/Guest/V1.php @@ -0,0 +1,36 @@ + + */ +class Mage_Catalog_Model_Api2_Product_Image_Rest_Guest_V1 extends Mage_Catalog_Model_Api2_Product_Image_Rest +{ +} diff --git a/app/code/core/Mage/Catalog/Model/Api2/Product/Image/Validator/Image.php b/app/code/core/Mage/Catalog/Model/Api2/Product/Image/Validator/Image.php new file mode 100644 index 0000000000..50888dfd36 --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Api2/Product/Image/Validator/Image.php @@ -0,0 +1,53 @@ + + */ +class Mage_Catalog_Model_Api2_Product_Image_Validator_Image extends Mage_Api2_Model_Resource_Validator +{ + /** + * Validate data. In case of validation failure return false, + * getErrors() could be used to retrieve list of validation error messages + * + * @param array $data + * @return bool + */ + public function isValidData(array $data) + { + if (!isset($data['file_content']) || !isset($data['file_mime_type']) || empty($data['file_content']) || + empty($data['file_mime_type']) + ) { + $this->_addError('The image is not specified'); + } + + return !count($this->getErrors()); + } +} diff --git a/app/code/core/Mage/Catalog/Model/Api2/Product/Rest.php b/app/code/core/Mage/Catalog/Model/Api2/Product/Rest.php new file mode 100644 index 0000000000..3f7168d8f4 --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Api2/Product/Rest.php @@ -0,0 +1,400 @@ + + */ +abstract class Mage_Catalog_Model_Api2_Product_Rest extends Mage_Catalog_Model_Api2_Product +{ + /** + * Current loaded product + * + * @var Mage_Catalog_Model_Product + */ + protected $_product; + + /** + * Retrieve product data + * + * @return array + */ + protected function _retrieve() + { + $product = $this->_getProduct(); + + $this->_prepareProductForResponse($product); + return $product->getData(); + } + + /** + * Retrieve list of products + * + * @return array + */ + protected function _retrieveCollection() + { + /** @var $collection Mage_Catalog_Model_Resource_Product_Collection */ + $collection = Mage::getResourceModel('catalog/product_collection'); + $store = $this->_getStore(); + $entityOnlyAttributes = $this->getEntityOnlyAttributes($this->getUserType(), + Mage_Api2_Model_Resource::OPERATION_ATTRIBUTE_READ); + $availableAttributes = array_keys($this->getAvailableAttributes($this->getUserType(), + Mage_Api2_Model_Resource::OPERATION_ATTRIBUTE_READ)); + // available attributes not contain image attribute, but it needed for get image_url + $availableAttributes[] = 'image'; + $collection->addStoreFilter($store->getId()) + ->addPriceData($this->_getCustomerGroupId(), $store->getWebsiteId()) + ->addAttributeToSelect(array_diff($availableAttributes, $entityOnlyAttributes)) + ->addAttributeToFilter('visibility', array( + 'neq' => Mage_Catalog_Model_Product_Visibility::VISIBILITY_NOT_VISIBLE)) + ->addAttributeToFilter('status', array('eq' => Mage_Catalog_Model_Product_Status::STATUS_ENABLED)); + $this->_applyCategoryFilter($collection); + $this->_applyCollectionModifiers($collection); + $products = $collection->load(); + + /** @var Mage_Catalog_Model_Product $product */ + foreach ($products as $product) { + $this->_setProduct($product); + $this->_prepareProductForResponse($product); + } + return $products->toArray(); + } + + /** + * Apply filter by category id + * + * @param Mage_Catalog_Model_Resource_Product_Collection $collection + */ + protected function _applyCategoryFilter(Mage_Catalog_Model_Resource_Product_Collection $collection) + { + $categoryId = $this->getRequest()->getParam('category_id'); + if ($categoryId) { + $category = $this->_getCategoryById($categoryId); + if (!$category->getId()) { + $this->_critical('Category not found.', Mage_Api2_Model_Server::HTTP_BAD_REQUEST); + } + $collection->addCategoryFilter($category); + } + } + + /** + * Add special fields to product get response + * + * @param Mage_Catalog_Model_Product $product + */ + protected function _prepareProductForResponse(Mage_Catalog_Model_Product $product) + { + /** @var $productHelper Mage_Catalog_Helper_Product */ + $productHelper = Mage::helper('catalog/product'); + $productData = $product->getData(); + $product->setWebsiteId($this->_getStore()->getWebsiteId()); + // customer group is required in product for correct prices calculation + $product->setCustomerGroupId($this->_getCustomerGroupId()); + // calculate prices + $finalPrice = $product->getFinalPrice(); + $productData['regular_price_with_tax'] = $this->_applyTaxToPrice($product->getPrice(), true); + $productData['regular_price_without_tax'] = $this->_applyTaxToPrice($product->getPrice(), false); + $productData['final_price_with_tax'] = $this->_applyTaxToPrice($finalPrice, true); + $productData['final_price_without_tax'] = $this->_applyTaxToPrice($finalPrice, false); + + $productData['is_saleable'] = $product->getIsSalable(); + $productData['image_url'] = (string) Mage::helper('catalog/image')->init($product, 'image'); + + if ($this->getActionType() == self::ACTION_TYPE_ENTITY) { + // define URLs + $productData['url'] = $productHelper->getProductUrl($product->getId()); + /** @var $cartHelper Mage_Checkout_Helper_Cart */ + $cartHelper = Mage::helper('checkout/cart'); + $productData['buy_now_url'] = $cartHelper->getAddUrl($product); + + /** @var $stockItem Mage_CatalogInventory_Model_Stock_Item */ + $stockItem = $product->getStockItem(); + if (!$stockItem) { + $stockItem = Mage::getModel('cataloginventory/stock_item'); + $stockItem->loadByProduct($product); + } + $productData['is_in_stock'] = $stockItem->getIsInStock(); + + /** @var $reviewModel Mage_Review_Model_Review */ + $reviewModel = Mage::getModel('review/review'); + $productData['total_reviews_count'] = $reviewModel->getTotalReviews($product->getId(), true, + $this->_getStore()->getId()); + + $productData['tier_price'] = $this->_getTierPrices(); + $productData['has_custom_options'] = count($product->getOptions()) > 0; + } else { + // remove tier price from response + $product->unsetData('tier_price'); + unset($productData['tier_price']); + } + $product->addData($productData); + } + + /** + * Product create only available for admin + * + * @param array $data + */ + protected function _create(array $data) + { + $this->_critical(self::RESOURCE_METHOD_NOT_ALLOWED); + } + + /** + * Product update only available for admin + * + * @param array $data + */ + protected function _update(array $data) + { + $this->_critical(self::RESOURCE_METHOD_NOT_ALLOWED); + } + + /** + * Product delete only available for admin + */ + protected function _delete() + { + $this->_critical(self::RESOURCE_METHOD_NOT_ALLOWED); + } + + /** + * Load product by its SKU or ID provided in request + * + * @return Mage_Catalog_Model_Product + */ + protected function _getProduct() + { + if (is_null($this->_product)) { + $productId = $this->getRequest()->getParam('id'); + /** @var $productHelper Mage_Catalog_Helper_Product */ + $productHelper = Mage::helper('catalog/product'); + $product = $productHelper->getProduct($productId, $this->_getStore()->getId()); + if (!($product->getId())) { + $this->_critical(self::RESOURCE_NOT_FOUND); + } + // check if product belongs to website current + if ($this->getRequest()->getParam('store')) { + $isValidWebsite = in_array($this->_getStore()->getWebsiteId(), $product->getWebsiteIds()); + if (!$isValidWebsite) { + $this->_critical(self::RESOURCE_NOT_FOUND); + } + } + // Check display settings for customers & guests + if ($this->getApiUser()->getType() != Mage_Api2_Model_Auth_User_Admin::USER_TYPE) { + // check if product assigned to any website and can be shown + if ((!Mage::app()->isSingleStoreMode() && !count($product->getWebsiteIds())) + || !$productHelper->canShow($product)) { + $this->_critical(self::RESOURCE_NOT_FOUND); + } + } + $this->_product = $product; + } + return $this->_product; + } + + /** + * Set product + * + * @param Mage_Catalog_Model_Product $product + */ + protected function _setProduct(Mage_Catalog_Model_Product $product) + { + $this->_product = $product; + } + + /** + * Load category by id + * + * @param int $categoryId + * @return Mage_Catalog_Model_Category + */ + protected function _getCategoryById($categoryId) + { + return Mage::getModel('catalog/category')->load($categoryId); + } + + /** + * Get product price with all tax settings processing + * + * @param float $price inputed product price + * @param bool $includingTax return price include tax flag + * @param null|Mage_Customer_Model_Address $shippingAddress + * @param null|Mage_Customer_Model_Address $billingAddress + * @param null|int $ctc customer tax class + * @param bool $priceIncludesTax flag that price parameter contain tax + * @return float + * @see Mage_Tax_Helper_Data::getPrice() + */ + protected function _getPrice($price, $includingTax = null, $shippingAddress = null, + $billingAddress = null, $ctc = null, $priceIncludesTax = null + ) { + $product = $this->_getProduct(); + $store = $this->_getStore(); + + if (is_null($priceIncludesTax)) { + /** @var $config Mage_Tax_Model_Config */ + $config = Mage::getSingleton('tax/config'); + $priceIncludesTax = $config->priceIncludesTax($store) || $config->getNeedUseShippingExcludeTax(); + } + + $percent = $product->getTaxPercent(); + $includingPercent = null; + + $taxClassId = $product->getTaxClassId(); + if (is_null($percent)) { + if ($taxClassId) { + $request = Mage::getSingleton('tax/calculation') + ->getRateRequest($shippingAddress, $billingAddress, $ctc, $store); + $percent = Mage::getSingleton('tax/calculation')->getRate($request->setProductClassId($taxClassId)); + } + } + if ($taxClassId && $priceIncludesTax) { + $request = Mage::getSingleton('tax/calculation')->getRateRequest(false, false, false, $store); + $includingPercent = Mage::getSingleton('tax/calculation') + ->getRate($request->setProductClassId($taxClassId)); + } + + if ($percent === false || is_null($percent)) { + if ($priceIncludesTax && !$includingPercent) { + return $price; + } + } + $product->setTaxPercent($percent); + + if (!is_null($includingTax)) { + if ($priceIncludesTax) { + if ($includingTax) { + /** + * Recalculate price include tax in case of different rates + */ + if ($includingPercent != $percent) { + $price = $this->_calculatePrice($price, $includingPercent, false); + /** + * Using regular rounding. Ex: + * price incl tax = 52.76 + * store tax rate = 19.6% + * customer tax rate= 19% + * + * price excl tax = 52.76 / 1.196 = 44.11371237 ~ 44.11 + * tax = 44.11371237 * 0.19 = 8.381605351 ~ 8.38 + * price incl tax = 52.49531773 ~ 52.50 != 52.49 + * + * that why we need round prices excluding tax before applying tax + * this calculation is used for showing prices on catalog pages + */ + if ($percent != 0) { + $price = Mage::getSingleton('tax/calculation')->round($price); + $price = $this->_calculatePrice($price, $percent, true); + } + } + } else { + $price = $this->_calculatePrice($price, $includingPercent, false); + } + } else { + if ($includingTax) { + $price = $this->_calculatePrice($price, $percent, true); + } + } + } else { + if ($priceIncludesTax) { + if ($includingTax) { + $price = $this->_calculatePrice($price, $includingPercent, false); + $price = $this->_calculatePrice($price, $percent, true); + } else { + $price = $this->_calculatePrice($price, $includingPercent, false); + } + } else { + if ($includingTax) { + $price = $this->_calculatePrice($price, $percent, true); + } + } + } + + return $store->roundPrice($price); + } + + /** + * Calculate price imcluding/excluding tax base on tax rate percent + * + * @param float $price + * @param float $percent + * @param bool $includeTax true - for calculate price including tax and false if price excluding tax + * @return float + */ + protected function _calculatePrice($price, $percent, $includeTax) + { + /** @var $calculator Mage_Tax_Model_Calculation */ + $calculator = Mage::getSingleton('tax/calculation'); + $taxAmount = $calculator->calcTaxAmount($price, $percent, !$includeTax, false); + + return $includeTax ? $price + $taxAmount : $price - $taxAmount; + } + + /** + * Retrive tier prices in special format + * + * @return array + */ + protected function _getTierPrices() + { + $tierPrices = array(); + foreach ($this->_getProduct()->getTierPrice() as $tierPrice) { + $tierPrices[] = array( + 'qty' => $tierPrice['price_qty'], + 'price_with_tax' => $this->_applyTaxToPrice($tierPrice['price']), + 'price_without_tax' => $this->_applyTaxToPrice($tierPrice['price'], false) + ); + } + return $tierPrices; + } + + /** + * Default implementation. May be different for customer/guest/admin role. + * + * @return null + */ + protected function _getCustomerGroupId() + { + return null; + } + + /** + * Default implementation. May be different for customer/guest/admin role. + * + * @param float $price + * @param bool $withTax + * @return float + */ + protected function _applyTaxToPrice($price, $withTax = true) + { + return $price; + } +} diff --git a/app/code/core/Mage/Catalog/Model/Api2/Product/Rest/Admin/V1.php b/app/code/core/Mage/Catalog/Model/Api2/Product/Rest/Admin/V1.php new file mode 100644 index 0000000000..8d2dffa3b4 --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Api2/Product/Rest/Admin/V1.php @@ -0,0 +1,383 @@ + + */ +class Mage_Catalog_Model_Api2_Product_Rest_Admin_V1 extends Mage_Catalog_Model_Api2_Product_Rest +{ + /** + * The greatest decimal value which could be stored. Corresponds to DECIMAL (12,4) SQL type + */ + const MAX_DECIMAL_VALUE = 99999999.9999; + + /** + * Add special fields to product get response + * + * @param Mage_Catalog_Model_Product $product + */ + protected function _prepareProductForResponse(Mage_Catalog_Model_Product $product) + { + $pricesFilterKeys = array('price_id', 'all_groups', 'website_price'); + $groupPrice = $product->getData('group_price'); + $product->setData('group_price', $this->_filterOutArrayKeys($groupPrice, $pricesFilterKeys, true)); + $tierPrice = $product->getData('tier_price'); + $product->setData('tier_price', $this->_filterOutArrayKeys($tierPrice, $pricesFilterKeys, true)); + + $stockData = $product->getStockItem()->getData(); + $stockDataFilterKeys = array('item_id', 'product_id', 'stock_id', 'low_stock_date', 'type_id', + 'stock_status_changed_auto', 'stock_status_changed_automatically', 'product_name', 'store_id', + 'product_type_id', 'product_status_changed', 'product_changed_websites', + 'use_config_enable_qty_increments'); + $product->setData('stock_data', $this->_filterOutArrayKeys($stockData, $stockDataFilterKeys)); + $product->setData('product_type_name', $product->getTypeId()); + } + + /** + * Remove specified keys from associative or indexed array + * + * @param array $array + * @param array $keys + * @param bool $dropOrigKeys if true - return array as indexed array + * @return array + */ + protected function _filterOutArrayKeys(array $array, array $keys, $dropOrigKeys = false) + { + $isIndexedArray = is_array(reset($array)); + if ($isIndexedArray) { + foreach ($array as &$value) { + if (is_array($value)) { + $value = array_diff_key($value, array_flip($keys)); + } + } + if ($dropOrigKeys) { + $array = array_values($array); + } + unset($value); + } else { + $array = array_diff_key($array, array_flip($keys)); + } + + return $array; + } + + /** + * Retrieve list of products + * + * @return array + */ + protected function _retrieveCollection() + { + /** @var $collection Mage_Catalog_Model_Resource_Product_Collection */ + $collection = Mage::getResourceModel('catalog/product_collection'); + $store = $this->_getStore(); + $collection->setStoreId($store->getId()); + $collection->addAttributeToSelect(array_keys( + $this->getAvailableAttributes($this->getUserType(), Mage_Api2_Model_Resource::OPERATION_ATTRIBUTE_READ) + )); + $this->_applyCategoryFilter($collection); + $this->_applyCollectionModifiers($collection); + $products = $collection->load()->toArray(); + return $products; + } + + /** + * Delete product by its ID + * + * @throws Mage_Api2_Exception + */ + protected function _delete() + { + $product = $this->_getProduct(); + try { + $product->delete(); + } catch (Mage_Core_Exception $e) { + $this->_critical($e->getMessage(), Mage_Api2_Model_Server::HTTP_INTERNAL_ERROR); + } catch (Exception $e) { + $this->_critical(self::RESOURCE_INTERNAL_ERROR); + } + } + + /** + * Create product + * + * @param array $data + * @return string + */ + protected function _create(array $data) + { + /* @var $validator Mage_Catalog_Model_Api2_Product_Validator_Product */ + $validator = Mage::getModel('catalog/api2_product_validator_product', array( + 'operation' => self::OPERATION_CREATE + )); + + if (!$validator->isValidData($data)) { + foreach ($validator->getErrors() as $error) { + $this->_error($error, Mage_Api2_Model_Server::HTTP_BAD_REQUEST); + } + $this->_critical(self::RESOURCE_DATA_PRE_VALIDATION_ERROR); + } + + $type = $data['type_id']; + if ($type !== 'simple') { + $this->_critical("Creation of products with type '$type' is not implemented", + Mage_Api2_Model_Server::HTTP_METHOD_NOT_ALLOWED); + } + $set = $data['attribute_set_id']; + $sku = $data['sku']; + + /** @var $product Mage_Catalog_Model_Product */ + $product = Mage::getModel('catalog/product') + ->setStoreId(Mage_Catalog_Model_Abstract::DEFAULT_STORE_ID) + ->setAttributeSetId($set) + ->setTypeId($type) + ->setSku($sku); + + $this->_prepareDataForSave($product, $data); + try { + $product->validate(); + $product->save(); + $this->_multicall($product->getId()); + } catch (Mage_Eav_Model_Entity_Attribute_Exception $e) { + $this->_critical(sprintf('Invalid attribute "%s": %s', $e->getAttributeCode(), $e->getMessage()), + Mage_Api2_Model_Server::HTTP_BAD_REQUEST); + } catch (Mage_Core_Exception $e) { + $this->_critical($e->getMessage(), Mage_Api2_Model_Server::HTTP_INTERNAL_ERROR); + } catch (Exception $e) { + $this->_critical(self::RESOURCE_UNKNOWN_ERROR); + } + + return $this->_getLocation($product); + } + + /** + * Update product by its ID + * + * @param array $data + */ + protected function _update(array $data) + { + /** @var $product Mage_Catalog_Model_Product */ + $product = $this->_getProduct(); + /* @var $validator Mage_Catalog_Model_Api2_Product_Validator_Product */ + $validator = Mage::getModel('catalog/api2_product_validator_product', array( + 'operation' => self::OPERATION_UPDATE, + 'product' => $product + )); + + if (!$validator->isValidData($data)) { + foreach ($validator->getErrors() as $error) { + $this->_error($error, Mage_Api2_Model_Server::HTTP_BAD_REQUEST); + } + $this->_critical(self::RESOURCE_DATA_PRE_VALIDATION_ERROR); + } + if (isset($data['sku'])) { + $product->setSku($data['sku']); + } + $this->_prepareDataForSave($product, $data); + try { + $product->validate(); + $product->save(); + } catch (Mage_Eav_Model_Entity_Attribute_Exception $e) { + $this->_critical(sprintf('Invalid attribute "%s": %s', $e->getAttributeCode(), $e->getMessage()), + Mage_Api2_Model_Server::HTTP_BAD_REQUEST); + } catch (Mage_Core_Exception $e) { + $this->_critical($e->getMessage(), Mage_Api2_Model_Server::HTTP_INTERNAL_ERROR); + } catch (Exception $e) { + $this->_critical(self::RESOURCE_UNKNOWN_ERROR); + } + } + + /** + * Determine if stock management is enabled + * + * @param array $stockData + * @return bool + */ + protected function _isManageStockEnabled($stockData) + { + if (!(isset($stockData['use_config_manage_stock']) && $stockData['use_config_manage_stock'])) { + $manageStock = isset($stockData['manage_stock']) && $stockData['manage_stock']; + } else { + $manageStock = Mage::getStoreConfig( + Mage_CatalogInventory_Model_Stock_Item::XML_PATH_ITEM . 'manage_stock'); + } + return (bool) $manageStock; + } + + /** + * Check if value from config is used + * + * @param array $data + * @param string $field + * @return bool + */ + protected function _isConfigValueUsed($data, $field) + { + return isset($data["use_config_$field"]) && $data["use_config_$field"]; + } + + /** + * Set additional data before product save + * + * @param Mage_Catalog_Model_Product $product + * @param array $productData + */ + protected function _prepareDataForSave($product, $productData) + { + if (isset($productData['stock_data'])) { + if (!$product->isObjectNew() && !isset($productData['stock_data']['manage_stock'])) { + $productData['stock_data']['manage_stock'] = $product->getStockItem()->getManageStock(); + } + $this->_filterStockData($productData['stock_data']); + } else { + $productData['stock_data'] = array( + 'use_config_manage_stock' => 1, + 'use_config_min_sale_qty' => 1, + 'use_config_max_sale_qty' => 1, + ); + } + $product->setStockData($productData['stock_data']); + // save gift options + $this->_filterConfigValueUsed($productData, array('gift_message_available', 'gift_wrapping_available')); + if (isset($productData['use_config_gift_message_available'])) { + $product->setData('use_config_gift_message_available', $productData['use_config_gift_message_available']); + if (!$productData['use_config_gift_message_available'] + && ($product->getData('gift_message_available') === null)) { + $product->setData('gift_message_available', (int) Mage::getStoreConfig( + Mage_GiftMessage_Helper_Message::XPATH_CONFIG_GIFT_MESSAGE_ALLOW_ITEMS, $product->getStoreId())); + } + } + if (isset($productData['use_config_gift_wrapping_available'])) { + $product->setData('use_config_gift_wrapping_available', + $productData['use_config_gift_wrapping_available']); + if (!$productData['use_config_gift_wrapping_available'] + && ($product->getData('gift_wrapping_available') === null) + ) { + $product->setData('gift_wrapping_available', (int) Mage::getStoreConfig( + Enterprise_GiftWrapping_Helper_Data::XML_PATH_ALLOWED_FOR_ITEMS, $product->getStoreId())); + } + } + + if (isset($productData['website_ids']) && is_array($productData['website_ids'])) { + $product->setWebsiteIds($productData['website_ids']); + } + // Create Permanent Redirect for old URL key + if (!$product->isObjectNew() && isset($productData['url_key']) + && isset($productData['url_key_create_redirect']) + ) { + $product->setData('save_rewrites_history', (bool)$productData['url_key_create_redirect']); + } + /** @var $attribute Mage_Catalog_Model_Resource_Eav_Attribute */ + foreach ($product->getTypeInstance(true)->getEditableAttributes($product) as $attribute) { + //Unset data if object attribute has no value in current store + if (Mage_Catalog_Model_Abstract::DEFAULT_STORE_ID !== (int)$product->getStoreId() + && !$product->getExistsStoreValueFlag($attribute->getAttributeCode()) + && !$attribute->isScopeGlobal() + ) { + $product->setData($attribute->getAttributeCode(), false); + } + + if ($this->_isAllowedAttribute($attribute)) { + if (isset($productData[$attribute->getAttributeCode()])) { + $product->setData( + $attribute->getAttributeCode(), + $productData[$attribute->getAttributeCode()] + ); + } + } + } + } + + /** + * Filter stock data values + * + * @param array $stockData + */ + protected function _filterStockData(&$stockData) + { + $fieldsWithPossibleDefautlValuesInConfig = array('manage_stock', 'min_sale_qty', 'max_sale_qty', 'backorders', + 'qty_increments', 'notify_stock_qty', 'min_qty', 'enable_qty_increments'); + $this->_filterConfigValueUsed($stockData, $fieldsWithPossibleDefautlValuesInConfig); + + if ($this->_isManageStockEnabled($stockData)) { + if (isset($stockData['qty']) && (float)$stockData['qty'] > self::MAX_DECIMAL_VALUE) { + $stockData['qty'] = self::MAX_DECIMAL_VALUE; + } + if (isset($stockData['min_qty']) && (int)$stockData['min_qty'] < 0) { + $stockData['min_qty'] = 0; + } + if (!isset($stockData['is_decimal_divided']) || $stockData['is_qty_decimal'] == 0) { + $stockData['is_decimal_divided'] = 0; + } + } else { + $nonManageStockFields = array('manage_stock', 'use_config_manage_stock', 'min_sale_qty', + 'use_config_min_sale_qty', 'max_sale_qty', 'use_config_max_sale_qty'); + foreach ($stockData as $field => $value) { + if (!in_array($field, $nonManageStockFields)) { + unset($stockData[$field]); + } + } + } + } + + /** + * Filter out fields if Use Config Settings option used + * + * @param array $data + * @param string $fields + */ + protected function _filterConfigValueUsed(&$data, $fields) { + foreach($fields as $field) { + if ($this->_isConfigValueUsed($data, $field)) { + unset($data[$field]); + } + } + } + + /** + * Check if attribute is allowed + * + * @param Mage_Eav_Model_Entity_Attribute_Abstract $attribute + * @param array $attributes + * @return boolean + */ + protected function _isAllowedAttribute($attribute, $attributes = null) + { + $isAllowed = true; + if (is_array($attributes) + && !(in_array($attribute->getAttributeCode(), $attributes) + || in_array($attribute->getAttributeId(), $attributes)) + ) { + $isAllowed = false; + } + return $isAllowed; + } +} diff --git a/app/code/core/Mage/Catalog/Model/Api2/Product/Rest/Customer/V1.php b/app/code/core/Mage/Catalog/Model/Api2/Product/Rest/Customer/V1.php new file mode 100644 index 0000000000..aa9eebaaa4 --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Api2/Product/Rest/Customer/V1.php @@ -0,0 +1,90 @@ + + */ +class Mage_Catalog_Model_Api2_Product_Rest_Customer_V1 extends Mage_Catalog_Model_Api2_Product_Rest +{ + /** + * Current logged in customer + * + * @var Mage_Customer_Model_Customer + */ + protected $_customer; + + /** + * Get customer group + * + * @return int + */ + protected function _getCustomerGroupId() + { + return $this->_getCustomer()->getGroupId(); + } + + /** + * Define product price with or without taxes + * + * @param float $price + * @param bool $withTax + * @return float + */ + protected function _applyTaxToPrice($price, $withTax = true) + { + $customer = $this->_getCustomer(); + /** @var $session Mage_Customer_Model_Session */ + $session = Mage::getSingleton('customer/session'); + $session->setCustomerId($customer->getId()); + $price = $this->_getPrice($price, $withTax, $customer->getPrimaryShippingAddress(), + $customer->getPrimaryBillingAddress(), $customer->getTaxClassId()); + $session->setCustomerId(null); + + return $price; + } + + /** + * Retrieve current customer + * + * @return Mage_Customer_Model_Customer + */ + protected function _getCustomer() + { + if (is_null($this->_customer)) { + /** @var $customer Mage_Customer_Model_Customer */ + $customer = Mage::getModel('customer/customer')->load($this->getApiUser()->getUserId()); + if (!$customer->getId()) { + $this->_critical('Customer not found.', Mage_Api2_Model_Server::HTTP_INTERNAL_ERROR); + } + $this->_customer = $customer; + } + return $this->_customer; + } +} diff --git a/app/code/core/Mage/Catalog/Model/Api2/Product/Rest/Guest/V1.php b/app/code/core/Mage/Catalog/Model/Api2/Product/Rest/Guest/V1.php new file mode 100644 index 0000000000..91fae6e035 --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Api2/Product/Rest/Guest/V1.php @@ -0,0 +1,57 @@ + + */ +class Mage_Catalog_Model_Api2_Product_Rest_Guest_V1 extends Mage_Catalog_Model_Api2_Product_Rest +{ + /** + * Get customer group + * + * @return int + */ + protected function _getCustomerGroupId() + { + return Mage_Customer_Model_Group::NOT_LOGGED_IN_ID; + } + + /** + * Define product price with or without taxes + * + * @param float $price + * @param bool $withTax + * @return float + */ + protected function _applyTaxToPrice($price, $withTax = true) + { + return $this->_getPrice($price, $withTax); + } +} diff --git a/app/code/core/Mage/Catalog/Model/Api2/Product/Validator/Product.php b/app/code/core/Mage/Catalog/Model/Api2/Product/Validator/Product.php new file mode 100644 index 0000000000..3dbbcd67ba --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Api2/Product/Validator/Product.php @@ -0,0 +1,600 @@ + + */ +class Mage_Catalog_Model_Api2_Product_Validator_Product extends Mage_Api2_Model_Resource_Validator +{ + /** + * The greatest decimal value which could be stored. Corresponds to DECIMAL (12,4) SQL type + */ + const MAX_DECIMAL_VALUE = 99999999.9999; + + /** + * Validator product + * + * @var Mage_Catalog_Model_Product + */ + protected $_product = null; + + /** + * Validation operation + * + * @var string + */ + protected $_operation = null; + + public function __construct($options) + { + if (isset($options['product'])) { + if ($options['product'] instanceof Mage_Catalog_Model_Product) { + $this->_product = $options['product']; + } else { + throw new Exception("Passed parameter 'product' is wrong."); + } + } + + if (!isset($options['operation']) || empty($options['operation'])) { + throw new Exception("Passed parameter 'operation' is empty."); + } + $this->_operation = $options['operation']; + } + + /** + * Get validator product + * + * @return Mage_Catalog_Model_Product|null + */ + protected function _getProduct() + { + return $this->_product; + } + + /** + * Is update mode + * + * @return bool + */ + protected function _isUpdate() + { + return $this->_operation == Mage_Api2_Model_Resource::OPERATION_UPDATE; + } + + /** + * Validate product data + * + * @param array $data + * @return bool + */ + public function isValidData(array $data) + { + if ($this->_isUpdate()) { + $product = $this->_getProduct(); + if (!is_null($product) && $product->getId()) { + $data['attribute_set_id'] = $product->getAttributeSetId(); + $data['type_id'] = $product->getTypeId(); + } + } + + try { + $this->_validateProductType($data); + /** @var $productEntity Mage_Eav_Model_Entity_Type */ + $productEntity = Mage::getModel('eav/entity_type')->loadByCode(Mage_Catalog_Model_Product::ENTITY); + $this->_validateAttributeSet($data, $productEntity); + $this->_validateSku($data); + $this->_validateGiftOptions($data); + $this->_validateGroupPrice($data); + $this->_validateTierPrice($data); + $this->_validateStockData($data); + $this->_validateAttributes($data, $productEntity); + $isSatisfied = count($this->getErrors()) == 0; + } catch (Mage_Api2_Exception $e) { + $this->_addError($e->getMessage()); + $isSatisfied = false; + } + + + return $isSatisfied; + } + + /** + * Collect required EAV attributes, validate applicable attributes and validate source attributes values + * + * @param array $data + * @param Mage_Eav_Model_Entity_Type $productEntity + * @return array + */ + protected function _validateAttributes($data, $productEntity) + { + if (!isset($data['attribute_set_id']) || empty($data['attribute_set_id'])) { + $this->_critical('Missing "attribute_set_id" in request.', Mage_Api2_Model_Server::HTTP_BAD_REQUEST); + } + if (!isset($data['type_id']) || empty($data['type_id'])) { + $this->_critical('Missing "type_id" in request.', Mage_Api2_Model_Server::HTTP_BAD_REQUEST); + } + // Validate weight + if (isset($data['weight']) && !empty($data['weight']) && $data['weight'] > 0 + && !Zend_Validate::is($data['weight'], 'Between', array(0, self::MAX_DECIMAL_VALUE))) { + $this->_addError('The "weight" value is not within the specified range.'); + } + // msrp_display_actual_price_type attribute values needs to be a string to pass validation + // see Mage_Catalog_Model_Product_Attribute_Source_Msrp_Type_Price::getAllOptions() + if (isset($data['msrp_display_actual_price_type'])) { + $data['msrp_display_actual_price_type'] = (string) $data['msrp_display_actual_price_type']; + } + $requiredAttributes = array('attribute_set_id'); + $positiveNumberAttributes = array('weight', 'price', 'special_price', 'msrp'); + /** @var $attribute Mage_Catalog_Model_Resource_Eav_Attribute */ + foreach ($productEntity->getAttributeCollection($data['attribute_set_id']) as $attribute) { + $attributeCode = $attribute->getAttributeCode(); + $value = false; + $isSet = false; + if (isset($data[$attribute->getAttributeCode()])) { + $value = $data[$attribute->getAttributeCode()]; + $isSet = true; + } + $applicable = false; + if (!$attribute->getApplyTo() || in_array($data['type_id'], $attribute->getApplyTo())) { + $applicable = true; + } + + if (!$applicable && !$attribute->isStatic() && $isSet) { + $productTypes = Mage_Catalog_Model_Product_Type::getTypes(); + $this->_addError(sprintf('Attribute "%s" is not applicable for product type "%s"', $attributeCode, + $productTypes[$data['type_id']]['label'])); + } + + if ($applicable && $isSet) { + // Validate dropdown attributes + if ($attribute->usesSource() + // skip check when field will be validated later as a required one + && !(empty($value) && $attribute->getIsRequired())) { + $allowedValues = $this->_getAttributeAllowedValues($attribute->getSource()->getAllOptions()); + $useStrictMode = !is_numeric($value); + if (!in_array($value, $allowedValues, $useStrictMode) + && !$this->_isConfigValueUsed($data, $attributeCode)) { + $this->_addError(sprintf('Invalid value for attribute "%s".', $attributeCode)); + } + } + // Validate datetime attributes + if ($attribute->getBackendType() == 'datetime') { + try { + $attribute->getBackend()->formatDate($value); + } catch (Zend_Date_Exception $e) { + $this->_addError(sprintf('Invalid date in the "%s" field.', $attributeCode)); + } + } + // Validate positive number required attributes + if (in_array($attributeCode, $positiveNumberAttributes) && (!empty($value) && $value !== 0) + && (!is_numeric($value) || $value < 0) + ) { + $this->_addError(sprintf('Please enter a number 0 or greater in the "%s" field.', $attributeCode)); + } + } + + if ($applicable && $attribute->getIsRequired() && $attribute->getIsVisible()) { + if (!in_array($attributeCode, $positiveNumberAttributes) || $value !== 0) { + $requiredAttributes[] = $attribute->getAttributeCode(); + } + } + } + + foreach ($requiredAttributes as $key) { + if (!array_key_exists($key, $data)) { + if (!$this->_isUpdate()) { + $this->_addError(sprintf('Missing "%s" in request.', $key)); + continue; + } + } else if (!is_numeric($data[$key]) && empty($data[$key])) { + $this->_addError(sprintf('Empty value for "%s" in request.', $key)); + } + } + } + + /** + * Validate product type + * + * @param array $data + * @return bool + */ + protected function _validateProductType($data) + { + if ($this->_isUpdate()) { + return true; + } + if (!isset($data['type_id']) || empty($data['type_id'])) { + $this->_critical('Missing "type_id" in request.', Mage_Api2_Model_Server::HTTP_BAD_REQUEST); + } + if (!array_key_exists($data['type_id'], Mage_Catalog_Model_Product_Type::getTypes())) { + $this->_critical('Invalid product type.', Mage_Api2_Model_Server::HTTP_BAD_REQUEST); + } + } + + /** + * Validate attribute set + * + * @param array $data + * @param Mage_Eav_Model_Entity_Type $productEntity + * @return bool + */ + protected function _validateAttributeSet($data, $productEntity) + { + if ($this->_isUpdate()) { + return true; + } + if (!isset($data['attribute_set_id']) || empty($data['attribute_set_id'])) { + $this->_critical('Missing "attribute_set_id" in request.', Mage_Api2_Model_Server::HTTP_BAD_REQUEST); + } + /** @var $attributeSet Mage_Eav_Model_Entity_Attribute_Set */ + $attributeSet = Mage::getModel('eav/entity_attribute_set')->load($data['attribute_set_id']); + if (!$attributeSet->getId() || $productEntity->getEntityTypeId() != $attributeSet->getEntityTypeId()) { + $this->_critical('Invalid attribute set.', Mage_Api2_Model_Server::HTTP_BAD_REQUEST); + } + } + + /** + * Validate SKU + * + * @param array $data + * @return bool + */ + protected function _validateSku($data) + { + if ($this->_isUpdate() && !isset($data['sku'])) { + return true; + } + if (!Zend_Validate::is((string)$data['sku'], 'StringLength', array('min' => 0, 'max' => 64))) { + $this->_addError('SKU length should be 64 characters maximum.'); + } + } + + /** + * Validate product gift options data + * + * @param array $data + */ + protected function _validateGiftOptions($data) + { + if (isset($data['gift_wrapping_price'])) { + if (!(is_numeric($data['gift_wrapping_price']) && $data['gift_wrapping_price'] >= 0)) { + $this->_addError('Please enter a number 0 or greater in the "gift_wrapping_price" field.'); + } + } + } + + /** + * Validate Group Price complex attribute + * + * @param array $data + */ + protected function _validateGroupPrice($data) + { + if (isset($data['group_price']) && is_array($data['group_price'])) { + $groupPrices = $data['group_price']; + foreach ($groupPrices as $index => $groupPrice) { + $fieldSet = 'group_price:' . $index; + $this->_validateWebsiteIdForGroupPrice($groupPrice, $fieldSet); + $this->_validateCustomerGroup($groupPrice, $fieldSet); + $this->_validatePositiveNumber($groupPrice, $fieldSet, 'price', true, true); + } + } + } + + /** + * Validate Tier Price complex attribute + * + * @param array $data + */ + protected function _validateTierPrice($data) + { + if (isset($data['tier_price']) && is_array($data['tier_price'])) { + $tierPrices = $data['tier_price']; + foreach ($tierPrices as $index => $tierPrice) { + $fieldSet = 'tier_price:' . $index; + $this->_validateWebsiteIdForGroupPrice($tierPrice, $fieldSet); + $this->_validateCustomerGroup($tierPrice, $fieldSet); + $this->_validatePositiveNumber($tierPrice, $fieldSet, 'price_qty'); + $this->_validatePositiveNumber($tierPrice, $fieldSet, 'price'); + } + } + } + + /** + * Check if website id is appropriate according to price scope settings + * + * @param array $data + * @param string $fieldSet + */ + protected function _validateWebsiteIdForGroupPrice($data, $fieldSet) + { + if (!isset($data['website_id'])) { + $this->_addError(sprintf('The "website_id" value in the "%s" set is a required field.', $fieldSet)); + } else { + /** @var $catalogHelper Mage_Catalog_Helper_Data */ + $catalogHelper = Mage::helper('catalog'); + $website = Mage::getModel('core/website')->load($data['website_id']); + if (is_null($website->getId()) || ($data['website_id'] !== 0 + && $catalogHelper->getPriceScope() == Mage_Catalog_Helper_Data::PRICE_SCOPE_GLOBAL)) { + $this->_addError(sprintf('Invalid "website_id" value in the "%s" set.', $fieldSet)); + } + } + } + + /** + * Validate product inventory data + * + * @param array $data + */ + protected function _validateStockData($data) + { + if (isset($data['stock_data']) && is_array($data['stock_data'])) { + $stockData = $data['stock_data']; + $fieldSet = 'stock_data'; + if (!(isset($stockData['use_config_manage_stock']) && $stockData['use_config_manage_stock'])) { + $this->_validateBoolean($stockData, $fieldSet, 'manage_stock'); + } + if ($this->_isManageStockEnabled($stockData)) { + $this->_validateNumeric($stockData, $fieldSet, 'qty'); + $this->_validatePositiveNumber($stockData, $fieldSet, 'min_qty', false, true, true); + $this->_validateNumeric($stockData, $fieldSet, 'notify_stock_qty', false, true); + $this->_validateBoolean($stockData, $fieldSet, 'is_qty_decimal'); + if (isset($stockData['is_qty_decimal']) && (bool) $stockData['is_qty_decimal'] == true) { + $this->_validateBoolean($stockData, $fieldSet, 'is_decimal_divided'); + } + $this->_validateBoolean($stockData, $fieldSet, 'enable_qty_increments', true); + if (isset($stockData['enable_qty_increments']) && (bool) $stockData['enable_qty_increments'] == true) { + $this->_validatePositiveInteger($stockData, $fieldSet, 'qty_increments', false, true); + } + if (Mage::helper('catalog')->isModuleEnabled('Mage_CatalogInventory')) { + $this->_validateSource($stockData, $fieldSet, 'backorders', + 'cataloginventory/source_backorders', true); + $this->_validateSource($stockData, $fieldSet, 'is_in_stock', 'cataloginventory/source_stock'); + } + } + + $this->_validatePositiveInteger($stockData, $fieldSet, 'min_sale_qty', false, true); + $this->_validatePositiveInteger($stockData, $fieldSet, 'max_sale_qty', false, true); + } + } + + /** + * Determine if stock management is enabled + * + * @param array $stockData + * @return bool + */ + protected function _isManageStockEnabled($stockData) + { + if (!(isset($stockData['use_config_manage_stock']) && $stockData['use_config_manage_stock'])) { + $manageStock = isset($stockData['manage_stock']) && $stockData['manage_stock']; + } else { + $manageStock = Mage::getStoreConfig( + Mage_CatalogInventory_Model_Stock_Item::XML_PATH_ITEM . 'manage_stock'); + } + return (bool) $manageStock; + } + + /** + * Validate Customer Group field + * + * @param string $fieldSet + * @param array $data + */ + protected function _validateCustomerGroup($data, $fieldSet) + { + if (!isset($data['cust_group'])) { + $this->_addError(sprintf('The "cust_group" value in the "%s" set is a required field.', $fieldSet)); + } else { + if (!is_numeric($data['cust_group'])) { + $this->_addError(sprintf('Invalid "cust_group" value in the "%s" set', $fieldSet)); + } else { + $customerGroup = Mage::getModel('customer/group')->load($data['cust_group']); + if (is_null($customerGroup->getId())) { + $this->_addError(sprintf('Invalid "cust_group" value in the "%s" set', $fieldSet)); + } + } + } + } + + /** + * Validate field to be positive number + * + * @param array $data + * @param string $fieldSet + * @param string $field + * @param bool $required + * @param bool $equalsZero + * @param bool $skipIfConfigValueUsed + */ + protected function _validatePositiveNumber($data, $fieldSet, $field, $required = true, $equalsZero = false, + $skipIfConfigValueUsed = false) + { + // in case when 'Use Config Settings' is selected no validation needed + if (!($skipIfConfigValueUsed && $this->_isConfigValueUsed($data, $field))) { + if (!isset($data[$field]) && $required) { + $this->_addError(sprintf('The "%s" value in the "%s" set is a required field.', $field, $fieldSet)); + } + + if (isset($data[$field])) { + $isValid = $equalsZero ? $data[$field] >= 0 : $data[$field] > 0; + if (!(is_numeric($data[$field]) && $isValid)) { + $message = $equalsZero + ? 'Please enter a number 0 or greater in the "%s" field in the "%s" set.' + : 'Please enter a number greater than 0 in the "%s" field in the "%s" set.'; + $this->_addError(sprintf($message, $field, $fieldSet)); + } + } + } + } + + /** + * Validate field to be a positive integer + * + * @param array $data + * @param string $fieldSet + * @param string $field + * @param bool $required + * @param bool $skipIfConfigValueUsed + */ + protected function _validatePositiveInteger($data, $fieldSet, $field, $required = false, + $skipIfConfigValueUsed = false) + { + // in case when 'Use Config Settings' is selected no validation needed + if (!($skipIfConfigValueUsed && $this->_isConfigValueUsed($data, $field))) { + if (!isset($data[$field]) && $required) { + $this->_addError(sprintf('The "%s" value in the "%s" set is a required field.',$field, $fieldSet)); + } + + if (isset($data[$field]) && (!is_int($data[$field]) || $data[$field] < 0)) { + $this->_addError(sprintf('Please use numbers only in the "%s" field in the "%s" set. ' . + 'Please avoid spaces or other characters such as dots or commas.', $field, $fieldSet)); + } + } + } + + /** + * Validate field to be a number + * + * @param array $data + * @param string $fieldSet + * @param string $field + * @param bool $required + * @param bool $skipIfConfigValueUsed + */ + protected function _validateNumeric($data, $fieldSet, $field, $required = false, $skipIfConfigValueUsed = false) + { + // in case when 'Use Config Settings' is selected no validation needed + if (!($skipIfConfigValueUsed && $this->_isConfigValueUsed($data, $field))) { + if (!isset($data[$field]) && $required) { + $this->_addError(sprintf('The "%s" value in the "%s" set is a required field.',$field, $fieldSet)); + } + + if (isset($data[$field]) && !is_numeric($data[$field])) { + $this->_addError(sprintf('Please enter a valid number in the "%s" field in the "%s" set.', + $field, $fieldSet)); + } + } + } + + /** + * Validate dropdown fields value + * + * @param array $data + * @param string $fieldSet + * @param string $field + * @param string $sourceModelName + * @param bool $skipIfConfigValueUsed + */ + protected function _validateSource($data, $fieldSet, $field, $sourceModelName, $skipIfConfigValueUsed = false) + { + // in case when 'Use Config Settings' is selected no validation needed + if (!($skipIfConfigValueUsed && $this->_isConfigValueUsed($data, $field))) { + if (isset($data[$field])) { + $sourceModel = Mage::getSingleton($sourceModelName); + if ($sourceModel) { + $allowedValues = $this->_getAttributeAllowedValues($sourceModel->toOptionArray()); + if (!in_array($data[$field], $allowedValues, true)) { + $this->_addError(sprintf('Invalid "%s" value in the "%s" set.', $field, $fieldSet)); + } + } + } + } + } + + /** + * Validate bolean fields value + * + * @param array $data + * @param string $fieldSet + * @param string $field + * @param bool $skipIfConfigValueUsed + */ + protected function _validateBoolean($data, $fieldSet, $field, $skipIfConfigValueUsed = false) + { + // in case when 'Use Config Settings' is selected no validation needed + if (!($skipIfConfigValueUsed && $this->_isConfigValueUsed($data, $field))) { + if (isset($data[$field])) { + $allowedValues = $this->_getAttributeAllowedValues( + Mage::getSingleton('eav/entity_attribute_source_boolean')->getAllOptions()); + if (!in_array($data[$field], $allowedValues, true)) { + $this->_addError(sprintf('Invalid "%s" value in the "%s" set.', $field, $fieldSet)); + } + } + } + } + + /** + * Retrieve all attribute allowed values from source model in plain array format + * + * @param array $options + * @return array + */ + protected function _getAttributeAllowedValues(array $options) + { + $values = array(); + foreach ($options as $option) { + if (isset($option['value'])) { + $value = $option['value']; + if (is_array($value)) { + $values = array_merge($values, $this->_getAttributeAllowedValues($value)); + } else { + $values[] = $value; + } + } + } + + return $values; + } + + /** + * Check if value from config is used + * + * @param array $data + * @param string $field + * @return bool + */ + protected function _isConfigValueUsed($data, $field) + { + return isset($data["use_config_$field"]) && $data["use_config_$field"]; + } + + /** + * Throw API2 exception + * + * @param string $message + * @param int $code + * @throws Mage_Api2_Exception + */ + protected function _critical($message, $code) + { + throw new Mage_Api2_Exception($message, $code); + } +} diff --git a/app/code/core/Mage/Catalog/Model/Api2/Product/Website.php b/app/code/core/Mage/Catalog/Model/Api2/Product/Website.php new file mode 100644 index 0000000000..a925de9a78 --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Api2/Product/Website.php @@ -0,0 +1,69 @@ + + */ +class Mage_Catalog_Model_Api2_Product_Website extends Mage_Api2_Model_Resource +{ + /** + * Load product by id + * + * @param int $id + * @throws Mage_Api2_Exception + * @return Mage_Catalog_Model_Product + */ + protected function _loadProductById($id) + { + /* @var $product Mage_Catalog_Model_Product */ + $product = Mage::getModel('catalog/product')->load($id); + if (!$product->getId()) { + $this->_critical(sprintf('Product #%s not found.', $id), Mage_Api2_Model_Server::HTTP_NOT_FOUND); + } + return $product; + } + + /** + * Load website by id + * + * @param int $id + * @throws Mage_Api2_Exception + * @return Mage_Core_Model_Website + */ + protected function _loadWebsiteById($id) + { + /* @var $website Mage_Core_Model_Website */ + $website = Mage::getModel('core/website')->load($id); + if (!$website->getId()) { + $this->_critical(sprintf('Website #%s not found.', $id), Mage_Api2_Model_Server::HTTP_NOT_FOUND); + } + return $website; + } +} diff --git a/app/code/core/Mage/Catalog/Model/Api2/Product/Website/Rest.php b/app/code/core/Mage/Catalog/Model/Api2/Product/Website/Rest.php new file mode 100644 index 0000000000..0f6d0d16fb --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Api2/Product/Website/Rest.php @@ -0,0 +1,258 @@ + + */ +abstract class Mage_Catalog_Model_Api2_Product_Website_Rest extends Mage_Catalog_Model_Api2_Product_Website +{ + /** + * Product website retrieve is not available + */ + protected function _retrieve() + { + $this->_critical(self::RESOURCE_METHOD_NOT_ALLOWED); + } + + /** + * Get product websites list + * + * @return array + */ + protected function _retrieveCollection() + { + $return = array(); + foreach ($this->_loadProductById($this->getRequest()->getParam('product_id'))->getWebsiteIds() as $websiteId) { + $return[] = array('website_id' => $websiteId); + } + return $return; + } + + /** + * Product website assign + * + * @param array $data + * @return string + */ + protected function _create(array $data) + { + /* @var $product Mage_Catalog_Model_Product */ + $product = $this->_loadProductById($this->getRequest()->getParam('product_id')); + + /* @var $validator Mage_Catalog_Model_Api2_Product_Website_Validator_Admin_Website */ + $validator = Mage::getModel('catalog/api2_product_website_validator_admin_website'); + if (!$validator->isValidDataForWebsiteAssignmentToProduct($product, $data)) { + foreach ($validator->getErrors() as $error) { + $this->_error($error, Mage_Api2_Model_Server::HTTP_BAD_REQUEST); + } + $this->_critical(self::RESOURCE_DATA_PRE_VALIDATION_ERROR); + } + + $websiteIds = $product->getWebsiteIds(); + /* @var $website Mage_Core_Model_Website */ + $website = Mage::getModel('core/website')->load($data['website_id']); + $websiteIds[] = $website->getId(); // Existence of a website is checked in the validator + $product->setWebsiteIds($websiteIds); + + try{ + $product->save(); + + /** + * Do copying data to stores + */ + if (isset($data['copy_to_stores'])) { + foreach ($data['copy_to_stores'] as $storeData) { + Mage::getModel('catalog/product') + ->setStoreId($storeData['store_from']) + ->load($product->getId()) + ->setStoreId($storeData['store_to']) + ->save(); + } + } + + } catch (Mage_Core_Exception $e) { + $this->_critical($e->getMessage(), Mage_Api2_Model_Server::HTTP_INTERNAL_ERROR); + } catch (Exception $e) { + $this->_critical(self::RESOURCE_INTERNAL_ERROR); + } + + return $this->_getLocation($website, $product); + } + + /** + * Product website assign + * + * @param array $data + * @return string + */ + protected function _multiCreate(array $data) + { + /* @var $product Mage_Catalog_Model_Product */ + $product = $this->_loadProductById($this->getRequest()->getParam('product_id')); + $websiteIds = $product->getWebsiteIds(); + foreach ($data as $singleData) { + try { + if (!is_array($singleData)) { + $this->_errorMessage(self::RESOURCE_DATA_INVALID, Mage_Api2_Model_Server::HTTP_BAD_REQUEST); + $this->_critical(self::RESOURCE_DATA_PRE_VALIDATION_ERROR); + } + /* @var $validator Mage_Catalog_Model_Api2_Product_Website_Validator_Admin_Website */ + $validator = Mage::getModel('catalog/api2_product_website_validator_admin_website'); + if (!$validator->isValidDataForWebsiteAssignmentToProduct($product, $singleData)) { + foreach ($validator->getErrors() as $error) { + $this->_errorMessage($error, Mage_Api2_Model_Server::HTTP_BAD_REQUEST, array( + 'website_id' => isset($singleData['website_id']) ? $singleData['website_id'] : null, + 'product_id' => $product->getId(), + )); + } + $this->_critical(self::RESOURCE_DATA_PRE_VALIDATION_ERROR); + } + + /* @var $website Mage_Core_Model_Website */ + $website = Mage::getModel('core/website')->load($singleData['website_id']); + $websiteIds[] = $website->getId(); // Existence of a website is checked in the validator + $product->setWebsiteIds($websiteIds); + + $product->save(); + + /** + * Do copying data to stores + */ + if (isset($singleData['copy_to_stores'])) { + foreach ($singleData['copy_to_stores'] as $storeData) { + Mage::getModel('catalog/product') + ->setStoreId($storeData['store_from']) + ->load($product->getId()) + ->setStoreId($storeData['store_to']) + ->save(); + } + } + + $this->_successMessage( + Mage_Api2_Model_Resource::RESOURCE_UPDATED_SUCCESSFUL, + Mage_Api2_Model_Server::HTTP_OK, + array( + 'website_id' => $website->getId(), + 'product_id' => $product->getId(), + ) + ); + } catch (Mage_Api2_Exception $e) { + // pre-validation errors are already added + if ($e->getMessage() != self::RESOURCE_DATA_PRE_VALIDATION_ERROR) { + $this->_errorMessage( + $e->getMessage(), + $e->getCode(), + array( + 'website_id' => isset($singleData['website_id']) ? $singleData['website_id'] : null, + 'product_id' => $product->getId(), + ) + ); + } + } catch (Exception $e) { + $this->_errorMessage( + Mage_Api2_Model_Resource::RESOURCE_INTERNAL_ERROR, + Mage_Api2_Model_Server::HTTP_INTERNAL_ERROR, + array( + 'website_id' => isset($singleData['website_id']) ? $singleData['website_id'] : null, + 'product_id' => $product->getId(), + ) + ); + } + } + } + + /** + * Product websites update is not available + * + * @param array $data + */ + protected function _update(array $data) + { + $this->_critical(self::RESOURCE_METHOD_NOT_ALLOWED); + } + + /** + * Product website unassign + */ + protected function _delete() + { + /* @var $product Mage_Catalog_Model_Product */ + $product = $this->_loadProductById($this->getRequest()->getParam('product_id')); + + /* @var $website Mage_Core_Model_Website */ + $website = $this->_loadWebsiteById($this->getRequest()->getParam('website_id')); + + /* @var $validator Mage_Catalog_Model_Api2_Product_Website_Validator_Admin_Website */ + $validator = Mage::getModel('catalog/api2_product_website_validator_admin_website'); + if (!$validator->isWebsiteAssignedToProduct($website, $product)) { + foreach ($validator->getErrors() as $error) { + $this->_error($error, Mage_Api2_Model_Server::HTTP_BAD_REQUEST); + } + $this->_critical(self::RESOURCE_DATA_PRE_VALIDATION_ERROR); + } + + $websiteIds = $product->getWebsiteIds(); + // Existence of a key is checked in the validator + unset($websiteIds[array_search($website->getId(), $websiteIds)]); + $product->setWebsiteIds($websiteIds); + + try { + $product->save(); + } catch (Mage_Core_Exception $e) { + $this->_critical($e->getMessage(), Mage_Api2_Model_Server::HTTP_INTERNAL_ERROR); + } catch (Exception $e) { + $this->_critical(self::RESOURCE_INTERNAL_ERROR); + } + } + + /** + * Get resource location + * + * @param Mage_Core_Model_Website $website + * @return string URL + */ + protected function _getLocation($website) + { + /* @var $apiTypeRoute Mage_Api2_Model_Route_ApiType */ + $apiTypeRoute = Mage::getModel('api2/route_apiType'); + + $chain = $apiTypeRoute->chain( + new Zend_Controller_Router_Route($this->getConfig()->getRouteWithEntityTypeAction($this->getResourceType())) + ); + $params = array( + 'api_type' => $this->getRequest()->getApiType(), + 'product_id' => $this->getRequest()->getParam('product_id'), + 'website_id' => $website->getId() + ); + $uri = $chain->assemble($params); + + return '/' . $uri; + } +} diff --git a/app/code/core/Mage/Catalog/Model/Api2/Product/Website/Rest/Admin/V1.php b/app/code/core/Mage/Catalog/Model/Api2/Product/Website/Rest/Admin/V1.php new file mode 100644 index 0000000000..6c65e04c94 --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Api2/Product/Website/Rest/Admin/V1.php @@ -0,0 +1,36 @@ + + */ +class Mage_Catalog_Model_Api2_Product_Website_Rest_Admin_V1 extends Mage_Catalog_Model_Api2_Product_Website_Rest +{ +} diff --git a/app/code/core/Mage/Catalog/Model/Api2/Product/Website/Validator/Admin/Website.php b/app/code/core/Mage/Catalog/Model/Api2/Product/Website/Validator/Admin/Website.php new file mode 100644 index 0000000000..b2230fcbd3 --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Api2/Product/Website/Validator/Admin/Website.php @@ -0,0 +1,177 @@ + + */ +class Mage_Catalog_Model_Api2_Product_Website_Validator_Admin_Website extends Mage_Api2_Model_Resource_Validator +{ + /** + * Validate data for website assignment to product. + * If fails validation, then this method returns false, and + * getErrors() will return an array of errors that explain why the + * validation failed. + * + * @param Mage_Catalog_Model_Product $product + * @param array $data + * @return bool + */ + public function isValidDataForWebsiteAssignmentToProduct(Mage_Catalog_Model_Product $product, array $data) + { + // Validate website id + if (!isset($data['website_id']) || !is_numeric($data['website_id'])) { + $this->_addError('Invalid value for "website_id" in request.'); + return false; + } + + // Validate website + /* @var $website Mage_Core_Model_Website */ + $website = Mage::getModel('core/website')->load($data['website_id']); + if (!$website->getId()) { + $this->_addError(sprintf('Website #%d not found.', $data['website_id'])); + return false; + } + + // Validate product to website association + if (in_array($website->getId(), $product->getWebsiteIds())) { + $this->_addError(sprintf('Product #%d is already assigned to website #%d', $product->getId(), + $website->getId())); + return false; + } + + // Validate "Copy To Stores" data and associations + $this->_addErrorsIfCopyToStoresDataIsNotValid($product, $website, $data); + + return !count($this->getErrors()); + } + + /** + * Validate "Copy To Stores" data and associations. + * + * @param Mage_Catalog_Model_Product $product + * @param Mage_Core_Model_Website $website + * @param array $data + * @return \Mage_Catalog_Model_Api2_Product_Website_Validator_Admin_Website + */ + protected function _addErrorsIfCopyToStoresDataIsNotValid($product, $website, $data) + { + if (isset($data['copy_to_stores'])) { + foreach ($data['copy_to_stores'] as $storeData) { + $this->_checkStoreFrom($product, $website, $storeData); + $this->_checkStoreTo($website, $storeData); + } + } + return $this; + } + + /** + * Check if it possible to copy from store "store_from" + * + * @param Mage_Catalog_Model_Product $product + * @param Mage_Core_Model_Website $website + * @param array $storeData + * @return \Mage_Catalog_Model_Api2_Product_Website_Validator_Admin_Website + */ + protected function _checkStoreFrom($product, $website, $storeData) + { + if (!isset($storeData['store_from']) || !is_numeric($storeData['store_from'])) { + $this->_addError(sprintf('Invalid value for "store_from" for the website with ID #%d.', + $website->getId())); + return $this; + } + + // Check if the store with the specified ID (from which we will copy the information) exists + // and if it belongs to the product being edited + $storeFrom = Mage::getModel('core/store')->load($storeData['store_from']); + if (!$storeFrom->getId()) { + $this->_addError(sprintf('Store not found #%d for website #%d.', $storeData['store_from'], + $website->getId())); + return $this; + } + + if (!in_array($storeFrom->getId(), $product->getStoreIds())) { + $this->_addError(sprintf('Store #%d from which we will copy the information does not belong' + . ' to the product #%d being edited.', $storeFrom->getId(), $product->getId())); + } + + return $this; + } + + /** + * Check if it possible to copy into store "store_to" + * + * @param Mage_Core_Model_Website $website + * @param array $storeData + * @return \Mage_Catalog_Model_Api2_Product_Website_Validator_Admin_Website + */ + protected function _checkStoreTo($website, $storeData) + { + if (!isset($storeData['store_to']) || !is_numeric($storeData['store_to'])) { + $this->_addError(sprintf('Invalid value for "store_to" for the website with ID #%d.', + $website->getId())); + return $this; + } + + // Check if the store with the specified ID (to which we will copy the information) exists + // and if it belongs to the website being added + $storeTo = Mage::getModel('core/store')->load($storeData['store_to']); + if (!$storeTo->getId()) { + $this->_addError(sprintf('Store not found #%d for website #%d.', $storeData['store_to'], + $website->getId())); + return $this; + } + + if (!in_array($storeTo->getId(), $website->getStoreIds())) { + $this->_addError(sprintf('Store #%d to which we will copy the information does not belong' + . ' to the website #%d being added.', $storeTo->getId(), $website->getId())); + } + + return $this; + } + + /** + * Validate is valid association for website unassignment from product. + * If fails validation, then this method returns false, and + * getErrors() will return an array of errors that explain why the + * validation failed. + * + * @param Mage_Core_Model_Website $website + * @param Mage_Catalog_Model_Product $product + * @return bool + */ + public function isWebsiteAssignedToProduct(Mage_Core_Model_Website $website, Mage_Catalog_Model_Product $product) + { + if (false === array_search($website->getId(), $product->getWebsiteIds())) { + $this->_addError(sprintf('Product #%d isn\'t assigned to website #%d', $product->getId(), + $website->getId())); + } + return !count($this->getErrors()); + } +} diff --git a/app/code/core/Mage/Catalog/Model/Category/Api.php b/app/code/core/Mage/Catalog/Model/Category/Api.php index cd5c60b1f9..1ca5775e5e 100644 --- a/app/code/core/Mage/Catalog/Model/Category/Api.php +++ b/app/code/core/Mage/Catalog/Model/Category/Api.php @@ -345,8 +345,9 @@ public function update($categoryId, $categoryData, $store = null) } $category->save(); - } - catch (Mage_Core_Exception $e) { + } catch (Mage_Core_Exception $e) { + $this->_fault('data_invalid', $e->getMessage()); + } catch (Mage_Eav_Model_Entity_Attribute_Exception $e) { $this->_fault('data_invalid', $e->getMessage()); } diff --git a/app/code/core/Mage/Catalog/Model/Category/Api/V2.php b/app/code/core/Mage/Catalog/Model/Category/Api/V2.php index 540d0b9719..b05c952521 100644 --- a/app/code/core/Mage/Catalog/Model/Category/Api/V2.php +++ b/app/code/core/Mage/Catalog/Model/Category/Api/V2.php @@ -76,13 +76,14 @@ public function create($parentId, $categoryData, $store = null) { $parent_category = $this->_initCategory($parentId, $store); + /* @var $category Mage_Catalog_Model_Category */ $category = Mage::getModel('catalog/category') ->setStoreId($this->_getStoreId($store)); $category->addData(array('path'=>implode('/',$parent_category->getPathIds()))); $category ->setAttributeSetId($category->getDefaultAttributeSetId()); - /* @var $category Mage_Catalog_Model_Category */ + foreach ($category->getAttributes() as $attribute) { $_attrCode = $attribute->getAttributeCode(); @@ -153,8 +154,9 @@ public function update($categoryId, $categoryData, $store = null) } } $category->save(); - } - catch (Mage_Core_Exception $e) { + } catch (Mage_Core_Exception $e) { + $this->_fault('data_invalid', $e->getMessage()); + } catch (Mage_Eav_Model_Entity_Attribute_Exception $e) { $this->_fault('data_invalid', $e->getMessage()); } diff --git a/app/code/core/Mage/Catalog/Model/Layer/Filter/Price.php b/app/code/core/Mage/Catalog/Model/Layer/Filter/Price.php index 2250423714..4c50ca50a4 100644 --- a/app/code/core/Mage/Catalog/Model/Layer/Filter/Price.php +++ b/app/code/core/Mage/Catalog/Model/Layer/Filter/Price.php @@ -41,18 +41,18 @@ class Mage_Catalog_Model_Layer_Filter_Price extends Mage_Catalog_Model_Layer_Fil /** * XML configuration paths for Price Layered Navigation */ - const XML_PATH_RANGE_CALCULATION = 'catalog/layered_navigation/price_range_calculation'; - const XML_PATH_RANGE_STEP = 'catalog/layered_navigation/price_range_step'; - const XML_PATH_RANGE_MAX_INTERVALS = 'catalog/layered_navigation/price_range_max_intervals'; - const XML_PATH_ONE_PRICE_INTERVAL = 'catalog/layered_navigation/one_price_interval'; - const XML_PATH_INTERVAL_DIVISION_LIMIT = 'catalog/layered_navigation/interval_division_limit'; + const XML_PATH_RANGE_CALCULATION = 'catalog/layered_navigation/price_range_calculation'; + const XML_PATH_RANGE_STEP = 'catalog/layered_navigation/price_range_step'; + const XML_PATH_RANGE_MAX_INTERVALS = 'catalog/layered_navigation/price_range_max_intervals'; + const XML_PATH_ONE_PRICE_INTERVAL = 'catalog/layered_navigation/one_price_interval'; + const XML_PATH_INTERVAL_DIVISION_LIMIT = 'catalog/layered_navigation/interval_division_limit'; /** - * Price layered navigation mode: Automatic, Continuous, Manual + * Price layered navigation modes: Automatic (equalize price ranges), Automatic (equalize product counts), Manual */ - const RANGE_CALCULATION_AUTO = 'auto'; - const RANGE_CALCULATION_IMPROVED = 'improved'; - const RANGE_CALCULATION_MANUAL = 'manual'; + const RANGE_CALCULATION_AUTO = 'auto'; // equalize price ranges + const RANGE_CALCULATION_IMPROVED = 'improved'; // equalize product counts + const RANGE_CALCULATION_MANUAL = 'manual'; /** * Minimal size of the range @@ -265,7 +265,10 @@ protected function _getCalculatedItemsData() /** @var $algorithmModel Mage_Catalog_Model_Layer_Filter_Price_Algorithm */ $algorithmModel = Mage::getSingleton('catalog/layer_filter_price_algorithm'); $collection = $this->getLayer()->getProductCollection(); - if ($collection->getPricesCount() <= $this->getIntervalDivisionLimit()) { + $appliedInterval = $this->getInterval(); + if ($appliedInterval + && $collection->getPricesCount() <= $this->getIntervalDivisionLimit() + ) { return array(); } $algorithmModel->setPricesModel($this)->setStatistics( @@ -274,9 +277,9 @@ protected function _getCalculatedItemsData() $collection->getPriceStandardDeviation(), $collection->getPricesCount() ); - $appliedInterval = $this->getInterval(); + if ($appliedInterval) { - if ($appliedInterval[0] == $appliedInterval[1]) { + if ($appliedInterval[0] == $appliedInterval[1] || $appliedInterval[1] === '0') { return array(); } $algorithmModel->setLimits($appliedInterval[0], $appliedInterval[1]); diff --git a/app/code/core/Mage/Catalog/Model/Product/Api.php b/app/code/core/Mage/Catalog/Model/Product/Api.php index d2a79af58e..39dcdd1c24 100644 --- a/app/code/core/Mage/Catalog/Model/Product/Api.php +++ b/app/code/core/Mage/Catalog/Model/Product/Api.php @@ -268,7 +268,7 @@ protected function _prepareDataForSave($product, $productData) foreach ($product->getTypeInstance(true)->getEditableAttributes($product) as $attribute) { //Unset data if object attribute has no value in current store - if (Mage_Catalog_Model_Abstract::DEFAULT_STORE_ID !== $product->getStoreId() + if (Mage_Catalog_Model_Abstract::DEFAULT_STORE_ID !== (int) $product->getStoreId() && !$product->getExistsStoreValueFlag($attribute->getAttributeCode()) && !$attribute->isScopeGlobal() ) { diff --git a/app/code/core/Mage/Catalog/Model/Product/Api/V2.php b/app/code/core/Mage/Catalog/Model/Product/Api/V2.php index 22d507a401..acdd0ca01b 100644 --- a/app/code/core/Mage/Catalog/Model/Product/Api/V2.php +++ b/app/code/core/Mage/Catalog/Model/Product/Api/V2.php @@ -338,15 +338,18 @@ protected function _prepareDataForSave ($product, $productData) * @param string $fromDate * @param string $toDate * @param string|int $store + * @param string $identifierType OPTIONAL If 'sku' - search product by SKU, if any except for NULL - search by ID, + * otherwise - try to determine identifier type automatically * @return boolean */ - public function setSpecialPrice($productId, $specialPrice = null, $fromDate = null, $toDate = null, $store = null) - { + public function setSpecialPrice($productId, $specialPrice = null, $fromDate = null, $toDate = null, $store = null, + $identifierType = null + ) { $obj = new stdClass(); $obj->special_price = $specialPrice; $obj->special_from_date = $fromDate; $obj->special_to_date = $toDate; - return $this->update($productId, $obj, $store); + return $this->update($productId, $obj, $store, $identifierType); } /** diff --git a/app/code/core/Mage/Catalog/Model/Product/Attribute/Media/Api.php b/app/code/core/Mage/Catalog/Model/Product/Attribute/Media/Api.php index e1ab5900d2..33a56d2199 100644 --- a/app/code/core/Mage/Catalog/Model/Product/Attribute/Media/Api.php +++ b/app/code/core/Mage/Catalog/Model/Product/Attribute/Media/Api.php @@ -153,6 +153,16 @@ public function create($productId, $data, $store = null, $identifierType = null) $ioAdapter->write($fileName, $fileContent, 0666); unset($fileContent); + // try to create Image object - it fails with Exception if image is not supported + try { + new Varien_Image($tmpDirectory . DS . $fileName); + } catch (Exception $e) { + // Remove temporary directory + $ioAdapter->rmdir($tmpDirectory, true); + + throw new Mage_Core_Exception($e->getMessage()); + } + // Adding image to gallery $file = $gallery->getBackend()->addImage( $product, diff --git a/app/code/core/Mage/Catalog/Model/Product/Attribute/Tierprice/Api.php b/app/code/core/Mage/Catalog/Model/Product/Attribute/Tierprice/Api.php index 79408964cc..bd1f2438d8 100644 --- a/app/code/core/Mage/Catalog/Model/Product/Attribute/Tierprice/Api.php +++ b/app/code/core/Mage/Catalog/Model/Product/Attribute/Tierprice/Api.php @@ -168,13 +168,12 @@ public function prepareTierPrices($product, $tierPrices = null) * Retrieve product * * @param int $productId - * @param string|int $store * @param string $identifierType * @return Mage_Catalog_Model_Product */ protected function _initProduct($productId, $identifierType = null) { - $product = Mage::helper('catalog/product')->getProduct($productId, $this->_getStoreId($store), $identifierType); + $product = Mage::helper('catalog/product')->getProduct($productId, $this->_getStoreId(), $identifierType); if (!$product->getId()) { $this->_fault('product_not_exists'); } diff --git a/app/code/core/Mage/Catalog/Model/Product/Option/Type/Date.php b/app/code/core/Mage/Catalog/Model/Product/Option/Type/Date.php index 98cbce335b..5cdbbb0371 100644 --- a/app/code/core/Mage/Catalog/Model/Product/Option/Type/Date.php +++ b/app/code/core/Mage/Catalog/Model/Product/Option/Type/Date.php @@ -50,7 +50,7 @@ public function validateUserValue($values) $dateValid = true; if ($this->_dateExists()) { if ($this->useCalendar()) { - $dateValid = isset($value['date']) && preg_match('/^[0-9]{1,4}.+[0-9]{1,4}.+[0-9]{1,4}$/', $value['date']); + $dateValid = isset($value['date']) && preg_match('/^\d{1,4}.+\d{1,4}.+\d{1,4}$/', $value['date']); } else { $dateValid = isset($value['day']) && isset($value['month']) && isset($value['year']) && $value['day'] > 0 && $value['month'] > 0 && $value['year'] > 0; @@ -164,10 +164,12 @@ public function getFormattedOptionValue($optionValue) $option = $this->getOption(); if ($this->getOption()->getType() == Mage_Catalog_Model_Product_Option::OPTION_TYPE_DATE) { $format = Mage::app()->getLocale()->getDateFormat(Mage_Core_Model_Locale::FORMAT_TYPE_MEDIUM); - $result = Mage::app()->getLocale()->date($optionValue, Zend_Date::ISO_8601, null, false)->toString($format); + $result = Mage::app()->getLocale()->date($optionValue, Zend_Date::ISO_8601, null, false) + ->toString($format); } elseif ($this->getOption()->getType() == Mage_Catalog_Model_Product_Option::OPTION_TYPE_DATE_TIME) { $format = Mage::app()->getLocale()->getDateTimeFormat(Mage_Core_Model_Locale::FORMAT_TYPE_SHORT); - $result = Mage::app()->getLocale()->date($optionValue, Varien_Date::DATETIME_INTERNAL_FORMAT, null, false)->toString($format); + $result = Mage::app()->getLocale() + ->date($optionValue, Varien_Date::DATETIME_INTERNAL_FORMAT, null, false)->toString($format); } elseif ($this->getOption()->getType() == Mage_Catalog_Model_Product_Option::OPTION_TYPE_TIME) { $date = new Zend_Date($optionValue); $result = date($this->is24hTimeFormat() ? 'H:i' : 'h:i a', $date->getTimestamp()); @@ -216,7 +218,7 @@ public function parseOptionValue($optionValue, $productOptionValues) } $date = new Zend_Date($timestamp); - return $date->toString(Varien_date::DATETIME_INTERNAL_FORMAT); + return $date->toString(Varien_Date::DATETIME_INTERNAL_FORMAT); } /** diff --git a/app/code/core/Mage/Catalog/Model/Product/Option/Value/Api.php b/app/code/core/Mage/Catalog/Model/Product/Option/Value/Api.php index f7fb75cd5e..be1a7dd0cd 100644 --- a/app/code/core/Mage/Catalog/Model/Product/Option/Value/Api.php +++ b/app/code/core/Mage/Catalog/Model/Product/Option/Value/Api.php @@ -187,7 +187,7 @@ public function remove($valueId) } try { - $optionValue->deleteValues($valueId); + $optionValue->delete(); } catch (Mage_Core_Exception $e) { $this->_fault('not_deleted', $e->getMessage()); } diff --git a/app/code/core/Mage/Catalog/Model/Product/Type/Configurable/Price.php b/app/code/core/Mage/Catalog/Model/Product/Type/Configurable/Price.php index f2cfa5ce0e..51d8c6b3fb 100644 --- a/app/code/core/Mage/Catalog/Model/Product/Type/Configurable/Price.php +++ b/app/code/core/Mage/Catalog/Model/Product/Type/Configurable/Price.php @@ -52,7 +52,7 @@ public function getFinalPrice($qty=null, $product) Mage::dispatchEvent('catalog_product_get_final_price', array('product' => $product, 'qty' => $qty)); $finalPrice = $product->getData('final_price'); - $finalPrice += $this->getTotalConfigurableItemsPrice($product, $qty); + $finalPrice += $this->getTotalConfigurableItemsPrice($product, $finalPrice); $finalPrice += $this->_applyOptionsPrice($product, $qty, $basePrice) - $basePrice; $finalPrice = max(0, $finalPrice); @@ -64,10 +64,10 @@ public function getFinalPrice($qty=null, $product) * Get Total price for configurable items * * @param Mage_Catalog_Model_Product $product - * @param null|float $qty + * @param float $finalPrice * @return float */ - public function getTotalConfigurableItemsPrice($product, $qty = null) + public function getTotalConfigurableItemsPrice($product, $finalPrice) { $price = 0.0; @@ -81,7 +81,6 @@ public function getTotalConfigurableItemsPrice($product, $qty = null) $selectedAttributes = unserialize($product->getCustomOption('attributes')->getValue()); } - $basePrice = $this->getBasePrice($product, $qty); foreach ($attributes as $attribute) { $attributeId = $attribute->getProductAttribute()->getId(); $value = $this->_getValueByIndex( @@ -91,7 +90,7 @@ public function getTotalConfigurableItemsPrice($product, $qty = null) $product->setParentId(true); if ($value) { if ($value['pricing_value'] != 0) { - $product->setConfigurablePrice($this->_calcSelectionPrice($value, $basePrice)); + $product->setConfigurablePrice($this->_calcSelectionPrice($value, $finalPrice)); Mage::dispatchEvent( 'catalog_product_type_configurable_price', array('product' => $product) diff --git a/app/code/core/Mage/Catalog/Model/Resource/Layer/Filter/Price.php b/app/code/core/Mage/Catalog/Model/Resource/Layer/Filter/Price.php index 3d1389560f..5de20a0f03 100755 --- a/app/code/core/Mage/Catalog/Model/Resource/Layer/Filter/Price.php +++ b/app/code/core/Mage/Catalog/Model/Resource/Layer/Filter/Price.php @@ -37,7 +37,7 @@ class Mage_Catalog_Model_Resource_Layer_Filter_Price extends Mage_Core_Model_Res /** * Minimal possible price */ - const MIN_POSSIBLE_PRICE = .0001; + const MIN_POSSIBLE_PRICE = .01; /** * Initialize connection and define main table name @@ -225,9 +225,9 @@ protected function _getComparingValue($price, $filter, $decrease = true) { $currencyRate = $filter->getLayer()->getProductCollection()->getCurrencyRate(); if ($decrease) { - return round(($price - (self::MIN_POSSIBLE_PRICE / 2)) / $currencyRate, 3); + return ($price - (self::MIN_POSSIBLE_PRICE / 2)) / $currencyRate; } - return round(($price + (self::MIN_POSSIBLE_PRICE / 2)) / $currencyRate, 3); + return ($price + (self::MIN_POSSIBLE_PRICE / 2)) / $currencyRate; } /** @@ -253,14 +253,12 @@ protected function _getFullPriceExpression($filter, $select) public function getCount($filter, $range) { $select = $this->_getSelect($filter); - $priceExpression = $this->_getPriceExpression($filter, $select); - $rate = $filter->getCurrencyRate(); + $priceExpression = $this->_getFullPriceExpression($filter, $select); /** * Check and set correct variable values to prevent SQL-injections */ - $rate = floatval($rate); - $range = floatval($range) / $rate; + $range = floatval($range); if ($range == 0) { $range = 1; } diff --git a/app/code/core/Mage/Catalog/Model/Resource/Product/Collection.php b/app/code/core/Mage/Catalog/Model/Resource/Product/Collection.php index f3c8b5d4a5..29ac97dbc3 100755 --- a/app/code/core/Mage/Catalog/Model/Resource/Product/Collection.php +++ b/app/code/core/Mage/Catalog/Model/Resource/Product/Collection.php @@ -229,7 +229,12 @@ protected function _preparePriceExpressionParameters($select) // prepare response object for event $response = new Varien_Object(); $response->setAdditionalCalculations(array()); - $table = self::INDEX_TABLE_ALIAS; + $tableAliases = array_keys($select->getPart(Zend_Db_Select::FROM)); + if (in_array(self::INDEX_TABLE_ALIAS, $tableAliases)) { + $table = self::INDEX_TABLE_ALIAS; + } else { + $table = reset($tableAliases); + } // prepare event arguments $eventArgs = array( diff --git a/app/code/core/Mage/Catalog/Model/Resource/Setup.php b/app/code/core/Mage/Catalog/Model/Resource/Setup.php index 89fcbfd34e..8bf8f7aa81 100755 --- a/app/code/core/Mage/Catalog/Model/Resource/Setup.php +++ b/app/code/core/Mage/Catalog/Model/Resource/Setup.php @@ -353,7 +353,7 @@ public function getDefaultEntities() 'group' => 'Custom Design', ), 'filter_price_range' => array( - 'type' => 'int', + 'type' => 'decimal', 'label' => 'Layered Navigation Price Step', 'input' => 'text', 'required' => false, diff --git a/app/code/core/Mage/Catalog/Model/Resource/Url.php b/app/code/core/Mage/Catalog/Model/Resource/Url.php index 8cedda6d83..67d1690954 100755 --- a/app/code/core/Mage/Catalog/Model/Resource/Url.php +++ b/app/code/core/Mage/Catalog/Model/Resource/Url.php @@ -296,11 +296,15 @@ public function prepareRewrites($storeId, $categoryIds = null, $productIds = nul public function saveRewrite($rewriteData, $rewrite) { $adapter = $this->_getWriteAdapter(); + try { + $adapter->insertOnDuplicate($this->getMainTable(), $rewriteData); + } catch (Exception $e) { + Mage::logException($e); + Mage::throwException(Mage::helper('catalog')->__('An error occurred while saving the URL rewrite')); + } + if ($rewrite && $rewrite->getId()) { if ($rewriteData['request_path'] != $rewrite->getRequestPath()) { - $where = array($this->getIdFieldName() . '=?' => $rewrite->getId()); - $adapter->update($this->getMainTable(), $rewriteData, $where); - // Update existing rewrites history and avoid chain redirects $where = array('target_path = ?' => $rewrite->getRequestPath()); if ($rewrite->getStoreId()) { @@ -312,13 +316,6 @@ public function saveRewrite($rewriteData, $rewrite) $where ); } - } else { - try { - $adapter->insert($this->getMainTable(), $rewriteData); - } catch (Exception $e) { - Mage::logException($e); - Mage::throwException(Mage::helper('catalog')->__('An error occurred while saving the URL rewrite')); - } } unset($rewriteData); diff --git a/app/code/core/Mage/Catalog/data/catalog_setup/data-upgrade-1.6.0.0.13-1.6.0.0.14.php b/app/code/core/Mage/Catalog/data/catalog_setup/data-upgrade-1.6.0.0.13-1.6.0.0.14.php new file mode 100644 index 0000000000..ef7f466f27 --- /dev/null +++ b/app/code/core/Mage/Catalog/data/catalog_setup/data-upgrade-1.6.0.0.13-1.6.0.0.14.php @@ -0,0 +1,68 @@ +getConnection(); + +$installer->startSetup(); + +$entityTypeId = $installer->getEntityTypeId(Mage_Catalog_Model_Category::ENTITY); +$attributeId = $installer->getAttributeId($entityTypeId, 'filter_price_range'); +$attributeTableOld = $installer->getAttributeTable($entityTypeId, $attributeId); + +$installer->updateAttribute($entityTypeId, $attributeId, 'backend_type', 'decimal'); + +$attributeTableNew = $installer->getAttributeTable($entityTypeId, $attributeId); + +if ($attributeTableOld != $attributeTableNew) { + $connection->disableTableKeys($attributeTableOld) + ->disableTableKeys($attributeTableNew); + + $select = $connection->select() + ->from($attributeTableOld, array('entity_type_id', 'attribute_id', 'store_id', 'entity_id', 'value')) + ->where('entity_type_id = ?', $entityTypeId) + ->where('attribute_id = ?', $attributeId); + + $query = $select->insertFromSelect($attributeTableNew, + array('entity_type_id', 'attribute_id', 'store_id', 'entity_id', 'value') + ); + + $connection->query($query); + + $connection->delete($attributeTableOld, + $connection->quoteInto('entity_type_id = ?', $entityTypeId) + . $connection->quoteInto(' AND attribute_id = ?', $attributeId) + ); + + $connection->enableTableKeys($attributeTableOld) + ->enableTableKeys($attributeTableNew); +} + +Mage::getModel('index/indexer') + ->getProcessByCode('catalog_category_flat') + ->changeStatus(Mage_Index_Model_Process::STATUS_REQUIRE_REINDEX); + +$installer->endSetup(); diff --git a/app/code/core/Mage/Catalog/etc/api2.xml b/app/code/core/Mage/Catalog/etc/api2.xml new file mode 100644 index 0000000000..950da09805 --- /dev/null +++ b/app/code/core/Mage/Catalog/etc/api2.xml @@ -0,0 +1,370 @@ + + + + + + + Catalog + 30 + + + Product + 50 + + + + + + + catalog_product + catalog/api2_product + catalog/product + Catalog Product + 10 + + + 1 + 1 + 1 + 1 + + + 1 + + + 1 + + + + Product ID + Product Type + Attribute Set + Inventory Data + Default Image + Salability Status + Total Reviews Count + Product URL + Buy Now URL + Has Custom Options + Stock Status + Regular Price With Tax + Regular Price Without Tax + Final Price With Tax + Final Price Without Tax + Use Config Settings for Allow Gift Message + Use Config Settings for Allow Gift Wrapping + Create Permanent Redirect for old URL + + + + + 1 + 1 + 1 + 1 + 1 + 1 + 1 + + + + + 1 + 1 + 1 + 1 + 1 + 1 + 1 + + + + + + + 1 + 1/ + 1 + 1 + 1 + + + + + 1 + 1 + 1 + 1 + 1 + + + + + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + + + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + + + + + + /products/:id + entity + + + /products/:id/store/:store + entity + + + /products + collection + + + /products/store/:store + collection + + + /products/category/:category_id + collection + + + /products/store/:store/category/:category_id + collection + + + /products/category/:category_id/store/:store + collection + + + 1 + + + catalog_product + catalog/api2_product_category + catalog/category + Product Category + 40 + + + 1 + 1 + 1 + + + 1 + + + 1 + + + + Category ID + + + + /products/:id/categories + collection + + + /products/:id/categories/:category_id + entity + + + + + + 1 + + Int + Please use numbers only in "category_id" field. + + + + + 1 + + + catalog_product + catalog/api2_product_image + Product Image + 70 + + + 1 + 1 + 1 + 1 + + + 1 + + + 1 + + + + ID + File Name + File Content + File MIME Type + + Position + Type + Exclude + URL + + + + + 1 + 1 + 1/ + 1/ + + + + + 1 + 1 + 1/ + 1/ + + + + + 1 + 1 + 1/ + + + 1 + 1 + + + + + + /products/:id/images/:image + entity + + + /products/:id/images/:image/store/:store + entity + + + /products/:id/images + collection + + + /products/:id/images/store/:store + collection + + + 1 + + + catalog_product + catalog/api2_product_website + catalog/product_website + Product Website + 100 + + + 1 + 1 + 1 + + + + + 1 + 1 + + + + + /products/:product_id/websites/:website_id + entity + + + /products/:product_id/websites + collection + + + 1 + + + + diff --git a/app/code/core/Mage/Catalog/etc/config.xml b/app/code/core/Mage/Catalog/etc/config.xml index b206ba92ac..9f49002047 100644 --- a/app/code/core/Mage/Catalog/etc/config.xml +++ b/app/code/core/Mage/Catalog/etc/config.xml @@ -28,7 +28,7 @@ - 1.6.0.0.13 + 1.6.0.0.14 diff --git a/app/code/core/Mage/Catalog/etc/wsdl.xml b/app/code/core/Mage/Catalog/etc/wsdl.xml index 21a67e0cbc..1aba78ee87 100644 --- a/app/code/core/Mage/Catalog/etc/wsdl.xml +++ b/app/code/core/Mage/Catalog/etc/wsdl.xml @@ -198,6 +198,8 @@ + diff --git a/app/code/core/Mage/Catalog/etc/wsi.xml b/app/code/core/Mage/Catalog/etc/wsi.xml index 642606cc51..d3918cf476 100644 --- a/app/code/core/Mage/Catalog/etc/wsi.xml +++ b/app/code/core/Mage/Catalog/etc/wsi.xml @@ -340,7 +340,164 @@
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -524,6 +681,214 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -538,6 +903,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1051,6 +1497,21 @@ + + + + + + + + + + + + + + + @@ -1119,12 +1580,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1311,6 +1880,12 @@ + + + + + + Set_Get current store view @@ -1442,11 +2017,101 @@ + + Add new custom option into product + + + + + Update product custom option + + + + + Get list of available custom option types + + + + + Retrieve list of product custom options + + + + + Get full information about custom option in product + + + + + Remove custom option + + + + + Retrieve custom option values list + + + + + Add new custom option values + + + + + Retrieve custom option value info + + + + + Update custom option value + + + + + Remove value from custom option + + + + + Create product attribute set based on another set + + + + + Remove product attribute set + + + Retrieve product attribute sets + + Add attribute into attribute set + + + + + Remove attribute from attribute set + + + + + Create group within existing attribute set + + + + + Rename existing group + + + + + Remove group from attribute set + + + Retrieve product types @@ -1527,6 +2192,16 @@ + + Create new attribute + + + + + Delete attribute + + + @@ -1737,6 +2412,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1746,6 +2538,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1917,6 +2754,15 @@ + + + + + + + + + diff --git a/app/code/core/Mage/CatalogInventory/Model/Api2/Stock/Item.php b/app/code/core/Mage/CatalogInventory/Model/Api2/Stock/Item.php new file mode 100644 index 0000000000..291079bf04 --- /dev/null +++ b/app/code/core/Mage/CatalogInventory/Model/Api2/Stock/Item.php @@ -0,0 +1,52 @@ + + */ +class Mage_CatalogInventory_Model_Api2_Stock_Item extends Mage_Api2_Model_Resource +{ + /** + * Load stock item by id + * + * @param int $id + * @throws Mage_Api2_Exception + * @return Mage_CatalogInventory_Model_Stock_Item + */ + protected function _loadStockItemById($id) + { + /* @var $stockItem Mage_CatalogInventory_Model_Stock_Item */ + $stockItem = Mage::getModel('cataloginventory/stock_item')->load($id); + if (!$stockItem->getId()) { + $this->_critical(self::RESOURCE_NOT_FOUND); + } + return $stockItem; + } +} diff --git a/app/code/core/Mage/CatalogInventory/Model/Api2/Stock/Item/Rest.php b/app/code/core/Mage/CatalogInventory/Model/Api2/Stock/Item/Rest.php new file mode 100644 index 0000000000..c164d96502 --- /dev/null +++ b/app/code/core/Mage/CatalogInventory/Model/Api2/Stock/Item/Rest.php @@ -0,0 +1,163 @@ + + */ +abstract class Mage_CatalogInventory_Model_Api2_Stock_Item_Rest + extends Mage_CatalogInventory_Model_Api2_Stock_Item +{ + /** + * Retrieve information about specified stock item + * + * @throws Mage_Api2_Exception + * @return array + */ + protected function _retrieve() + { + /* @var $stockItem Mage_CatalogInventory_Model_Stock_Item */ + $stockItem = $this->_loadStockItemById($this->getRequest()->getParam('id')); + return $stockItem->getData(); + } + + /** + * Get stock items list + * + * @return array + */ + protected function _retrieveCollection() + { + $data = $this->_getCollectionForRetrieve()->load()->toArray(); + return isset($data['items']) ? $data['items'] : $data; + } + + /** + * Retrieve stock items collection + * + * @return Mage_CatalogInventory_Model_Resource_Stock_Item_Collection + */ + protected function _getCollectionForRetrieve() + { + /* @var $collection Mage_CatalogInventory_Model_Resource_Stock_Item_Collection */ + $collection = Mage::getResourceModel('cataloginventory/stock_item_collection'); + $this->_applyCollectionModifiers($collection); + return $collection; + } + + /** + * Update specified stock item + * + * @param array $data + * @throws Mage_Api2_Exception + */ + protected function _update(array $data) + { + /* @var $stockItem Mage_CatalogInventory_Model_Stock_Item */ + $stockItem = $this->_loadStockItemById($this->getRequest()->getParam('id')); + + /* @var $validator Mage_CatalogInventory_Model_Api2_Stock_Item_Validator_Item */ + $validator = Mage::getModel('cataloginventory/api2_stock_item_validator_item', array( + 'resource' => $this + )); + + if (!$validator->isValidData($data)) { + foreach ($validator->getErrors() as $error) { + $this->_error($error, Mage_Api2_Model_Server::HTTP_BAD_REQUEST); + } + $this->_critical(self::RESOURCE_DATA_PRE_VALIDATION_ERROR); + } + + $stockItem->addData($data); + try { + $stockItem->save(); + } catch (Mage_Core_Exception $e) { + $this->_error($e->getMessage(), Mage_Api2_Model_Server::HTTP_INTERNAL_ERROR); + } catch (Exception $e) { + $this->_critical(self::RESOURCE_INTERNAL_ERROR); + } + } + + /** + * Update specified stock items + * + * @param array $data + */ + protected function _multiUpdate(array $data) + { + foreach ($data as $itemData) { + try { + if (!is_array($itemData)) { + $this->_errorMessage(self::RESOURCE_DATA_INVALID, Mage_Api2_Model_Server::HTTP_BAD_REQUEST); + $this->_critical(self::RESOURCE_DATA_PRE_VALIDATION_ERROR); + } + + /* @var $validator Mage_CatalogInventory_Model_Api2_Stock_Item_Validator_Item */ + $validator = Mage::getModel('cataloginventory/api2_stock_item_validator_item', array( + 'resource' => $this + )); + if (!$validator->isValidSingleItemDataForMultiUpdate($itemData)) { + foreach ($validator->getErrors() as $error) { + $this->_errorMessage($error, Mage_Api2_Model_Server::HTTP_BAD_REQUEST, array( + 'item_id' => isset($itemData['item_id']) ? $itemData['item_id'] : null + )); + } + $this->_critical(self::RESOURCE_DATA_PRE_VALIDATION_ERROR); + } + + // Existence of a item is checked in the validator + /* @var $stockItem Mage_CatalogInventory_Model_Stock_Item */ + $stockItem = $this->_loadStockItemById($itemData['item_id']); + + unset($itemData['item_id']); // item_id is not for update + $stockItem->addData($itemData); + $stockItem->save(); + + $this->_successMessage(self::RESOURCE_UPDATED_SUCCESSFUL, Mage_Api2_Model_Server::HTTP_OK, array( + 'item_id' => $stockItem->getId() + )); + } catch (Mage_Api2_Exception $e) { + // pre-validation errors are already added + if ($e->getMessage() != self::RESOURCE_DATA_PRE_VALIDATION_ERROR) { + $this->_errorMessage($e->getMessage(), $e->getCode(), array( + 'item_id' => isset($itemData['item_id']) ? $itemData['item_id'] : null + )); + } + } catch (Exception $e) { + $this->_errorMessage( + Mage_Api2_Model_Resource::RESOURCE_INTERNAL_ERROR, + Mage_Api2_Model_Server::HTTP_INTERNAL_ERROR, + array( + 'item_id' => isset($itemData['item_id']) ? $itemData['item_id'] : null + ) + ); + } + } + } +} diff --git a/app/code/core/Mage/CatalogInventory/Model/Api2/Stock/Item/Rest/Admin/V1.php b/app/code/core/Mage/CatalogInventory/Model/Api2/Stock/Item/Rest/Admin/V1.php new file mode 100644 index 0000000000..a88e924713 --- /dev/null +++ b/app/code/core/Mage/CatalogInventory/Model/Api2/Stock/Item/Rest/Admin/V1.php @@ -0,0 +1,36 @@ + + */ +class Mage_CatalogInventory_Model_Api2_Stock_Item_Rest_Admin_V1 extends Mage_CatalogInventory_Model_Api2_Stock_Item_Rest +{ +} diff --git a/app/code/core/Mage/CatalogInventory/Model/Api2/Stock/Item/Validator/Item.php b/app/code/core/Mage/CatalogInventory/Model/Api2/Stock/Item/Validator/Item.php new file mode 100644 index 0000000000..7c502a2576 --- /dev/null +++ b/app/code/core/Mage/CatalogInventory/Model/Api2/Stock/Item/Validator/Item.php @@ -0,0 +1,62 @@ + + */ +class Mage_CatalogInventory_Model_Api2_Stock_Item_Validator_Item extends Mage_Api2_Model_Resource_Validator_Fields +{ + /** + * Validate data. + * If fails validation, then this method returns false, and + * getErrors() will return an array of errors that explain why the + * validation failed. + * + * @param array $data + * @return bool + */ + public function isValidSingleItemDataForMultiUpdate(array $data) + { + // Validate item id + if (!isset($data['item_id']) || !is_numeric($data['item_id'])) { + $this->_addError('Invalid value for "item_id" in request.'); + } else { + // Validate Stock Item + /* @var $stockItem Mage_CatalogInventory_Model_Stock_Item */ + $stockItem = Mage::getModel('cataloginventory/stock_item')->load($data['item_id']); + if (!$stockItem->getId()) { + $this->_addError(sprintf('StockItem #%d not found.', $data['item_id'])); + } else { + parent::isValidData($data); + } + } + return !count($this->getErrors()); + } +} diff --git a/app/code/core/Mage/CatalogInventory/Model/Stock/Item/Api.php b/app/code/core/Mage/CatalogInventory/Model/Stock/Item/Api.php index 0951d78faa..7febf904f2 100644 --- a/app/code/core/Mage/CatalogInventory/Model/Stock/Item/Api.php +++ b/app/code/core/Mage/CatalogInventory/Model/Stock/Item/Api.php @@ -108,6 +108,14 @@ public function update($productId, $data) $stockData['use_config_manage_stock'] = $data['use_config_manage_stock']; } + if (isset($data['use_config_backorders'])) { + $stockData['use_config_backorders'] = $data['use_config_backorders']; + } + + if (isset($data['backorders'])) { + $stockData['backorders'] = $data['backorders']; + } + $product->setStockData($stockData); try { diff --git a/app/code/core/Mage/CatalogInventory/Model/Stock/Item/Api/V2.php b/app/code/core/Mage/CatalogInventory/Model/Stock/Item/Api/V2.php index a8fee164ac..c92982ef45 100644 --- a/app/code/core/Mage/CatalogInventory/Model/Stock/Item/Api/V2.php +++ b/app/code/core/Mage/CatalogInventory/Model/Stock/Item/Api/V2.php @@ -68,6 +68,14 @@ public function update($productId, $data) $stockData['use_config_manage_stock'] = $data->use_config_manage_stock; } + if (isset($data->use_config_backorders)) { + $stockData['use_config_backorders'] = $data->use_config_backorders; + } + + if (isset($data->backorders)) { + $stockData['backorders'] = $data->backorders; + } + $product->setStockData($stockData); try { diff --git a/app/code/core/Mage/CatalogInventory/etc/api2.xml b/app/code/core/Mage/CatalogInventory/etc/api2.xml new file mode 100644 index 0000000000..09b402ba1a --- /dev/null +++ b/app/code/core/Mage/CatalogInventory/etc/api2.xml @@ -0,0 +1,267 @@ + + + + + + + CatalogInventory + 120 + + + + + cataloginventory + cataloginventory/api2_stock_item + cataloginventory/stock_item + Stock Item + + + 1 + 1 + + + + Item ID + Product ID + Stock ID + Qty + Qty for Item's Status to Become Out of Stock + Use Config Settings for Qty for Item's Status to Become Out of Stock + Qty Uses Decimals + Backorders + Use Config Settings for Backorders + Minimum Qty Allowed in Shopping Cart + Use Config Settings for Minimum Qty Allowed in Shopping Cart + Maximum Qty Allowed in Shopping Cart + Use Config Settings for Maximum Qty Allowed in Shopping Cart + Stock Availability + Low Stock Date + Notify for Quantity Below + Use Config Settings for Notify for Quantity Below + Manage Stock + Use Config Settings for Manage Stock + Automatically Return Credit Memo Item to Stock + Use Config Settings for Qty Increments + Qty Increments + Use Config Settings for Enable Qty Increments + Enable Qty Increments + Can Be Divided into Multiple Boxes for Shipping + + + + + 1 + 1 + 1 + 1 + + + + + + 1 + + + + + /stockitems/:id + entity + + + /stockitems + collection + + + + + + + Regex + /^[0,1]$/ + The "manage_stock" field must be set to 0 or 1. + + + + + Regex + /^[0,1]$/ + The "use_config_manage_stock" field must be set to 0 or 1. + + + + 1 + + Between + -99999999.999999999999.9999 + + + + + Float + Please enter a valid number in "min_qty" field + + + Between + 099999999.9999 + + + + + Regex + /^[0,1]$/ + The "use_config_min_qty" field must be set to 0 or 1. + + + + + Regex + /^[0,1]$/ + The "is_qty_decimal" field must be set to 0 or 1. + + + + + Regex + /^[0,1,2]$/ + The "is_qty_decimal" field must be set to 0, 1, or 2. + + + + + Regex + /^[0,1]$/ + The "use_config_backorders" field must be set to 0 or 1. + + + + + Int + Please enter a valid number in "min_sale_qty" field + + + Between + 099999999 + + + + + Regex + /^[0,1]$/ + The "use_config_min_sale_qty" field must be set to 0 or 1. + + + + + Int + Please enter a valid number in "max_sale_qty" field + + + Between + 099999999 + + + + + Regex + /^[0,1]$/ + The "use_config_max_sale_qty" field must be set to 0 or 1. + + + + + Regex + /^[0,1]$/ + The "is_in_stock" field must be set to 0 or 1. + + + + + Float + Please enter a valid number in "notify_stock_qty" field + + + + + Regex + /^[0,1]$/ + The "use_config_notify_stock_qty" field must be set to 0 or 1. + + + + + Regex + /^[0,1]$/ + The "stock_status_changed_auto" field must be set to 0 or 1. + + + + + Regex + /^[0,1]$/ + The "use_config_qty_increments" field must be set to 0 or 1. + + + + + Int + Please use numbers only in "qty_increments" field. Please avoid spaces or other characters such as dots or commas. + + + Between + 099999999 + Please use numbers only in "qty_increments" field. Please avoid spaces or other characters such as dots or commas. + + + + + Regex + /^[0,1]$/ + The "use_config_enable_qty_inc" field must be set to 0 or 1. + + + + + Regex + /^[0,1]$/ + The "enable_qty_increments" field must be set to 0 or 1. + + + + + Regex + /^[0,1]$/ + The "is_decimal_divided" field must be set to 0 or 1. + + + + + 1 + + + + diff --git a/app/code/core/Mage/CatalogInventory/sql/cataloginventory_setup/mysql4-upgrade-0.7.4-0.7.5.php b/app/code/core/Mage/CatalogInventory/sql/cataloginventory_setup/mysql4-upgrade-0.7.4-0.7.5.php index 6af5ccc275..d5d48de851 100644 --- a/app/code/core/Mage/CatalogInventory/sql/cataloginventory_setup/mysql4-upgrade-0.7.4-0.7.5.php +++ b/app/code/core/Mage/CatalogInventory/sql/cataloginventory_setup/mysql4-upgrade-0.7.4-0.7.5.php @@ -30,9 +30,8 @@ $installer->startSetup(); $connection = $installer->getConnection(); /* @var $connection Varien_Db_Adapter_Pdo_Mysql */ -$connection->beginTransaction(); -try { - $installer->run(" + +$installer->run(" CREATE TABLE `{$installer->getTable('cataloginventory_stock_status')}` ( `product_id` int(10) unsigned NOT NULL, `website_id` smallint(5) unsigned NOT NULL, @@ -40,17 +39,14 @@ `qty` decimal(12,4) NOT NULL DEFAULT '0.0000', `stock_status` tinyint(3) unsigned NOT NULL, PRIMARY KEY (`product_id`,`website_id`,`stock_id`), - CONSTRAINT `FK_CATALOGINVENTORY_STOCK_STATUS_STOCK` FOREIGN KEY (`stock_id`) REFERENCES `{$installer->getTable('cataloginventory_stock')}` (`stock_id`) ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT `FK_CATALOGINVENTORY_STOCK_STATUS_PRODUCT` FOREIGN KEY (`product_id`) REFERENCES `{$installer->getTable('catalog_product_entity')}` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT `FK_CATALOGINVENTORY_STOCK_STATUS_WEBSITE` FOREIGN KEY (`website_id`) REFERENCES `{$installer->getTable('core_website')}` (`website_id`) ON DELETE CASCADE ON UPDATE CASCADE + CONSTRAINT `FK_CATALOGINVENTORY_STOCK_STATUS_STOCK` FOREIGN KEY (`stock_id`) + REFERENCES `{$installer->getTable('cataloginventory_stock')}` (`stock_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CATALOGINVENTORY_STOCK_STATUS_PRODUCT` FOREIGN KEY (`product_id`) + REFERENCES `{$installer->getTable('catalog_product_entity')}` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CATALOGINVENTORY_STOCK_STATUS_WEBSITE` FOREIGN KEY (`website_id`) + REFERENCES `{$installer->getTable('core_website')}` (`website_id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; - "); +"); - Mage::getModel('cataloginventory/stock_status')->rebuild(); -} -catch (Exception $e) { - $connection->rollBack(); - throw $e; -} -$connection->commit(); +Mage::getModel('cataloginventory/stock_status')->rebuild(); $installer->endSetup(); diff --git a/app/code/core/Mage/CatalogRule/Helper/Data.php b/app/code/core/Mage/CatalogRule/Helper/Data.php index 9521b57dd1..dad63f790b 100644 --- a/app/code/core/Mage/CatalogRule/Helper/Data.php +++ b/app/code/core/Mage/CatalogRule/Helper/Data.php @@ -37,20 +37,18 @@ class Mage_CatalogRule_Helper_Data extends Mage_Core_Helper_Abstract * @param float $price * @return float|int */ - public function calcPriceRule ($actionOperator, $ruleAmount, $price) + public function calcPriceRule($actionOperator, $ruleAmount, $price) { $priceRule = 0; switch ($actionOperator) { case 'to_fixed': - $priceRule = $ruleAmount; + $priceRule = min($ruleAmount, $price); break; case 'to_percent': $priceRule = $price * $ruleAmount / 100; break; case 'by_fixed': - $priceRule = $price - $ruleAmount; - // Price can not be negative - $priceRule = ($priceRule < 0) ? 0 : $priceRule; + $priceRule = max(0, $price - $ruleAmount); break; case 'by_percent': $priceRule = $price * (1 - $ruleAmount / 100); diff --git a/app/code/core/Mage/CatalogRule/Model/Observer.php b/app/code/core/Mage/CatalogRule/Model/Observer.php index 3d4252a3e3..c0919e937e 100644 --- a/app/code/core/Mage/CatalogRule/Model/Observer.php +++ b/app/code/core/Mage/CatalogRule/Model/Observer.php @@ -149,7 +149,7 @@ public function processAdminFinalPrice($observer) $key = "$date|$wId|$gId|$pId"; } - elseif ($product->getWebsiteId() != null && $product->getCustomerGroupId() != null) { + elseif (!is_null($product->getWebsiteId()) && !is_null($product->getCustomerGroupId())) { $wId = $product->getWebsiteId(); $gId = $product->getCustomerGroupId(); $pId = $product->getId(); diff --git a/app/code/core/Mage/CatalogSearch/Helper/Data.php b/app/code/core/Mage/CatalogSearch/Helper/Data.php index bb3d215b34..4c02d27083 100644 --- a/app/code/core/Mage/CatalogSearch/Helper/Data.php +++ b/app/code/core/Mage/CatalogSearch/Helper/Data.php @@ -103,10 +103,9 @@ public function getQuery() */ public function isMinQueryLength() { - if (Mage::helper('core/string')->strlen($this->getQueryText()) < $this->getMinQueryLength()) { - return true; - } - return false; + $minQueryLength = $this->getMinQueryLength(); + $thisQueryLength = Mage::helper('core/string')->strlen($this->getQueryText()); + return !$thisQueryLength || $minQueryLength !== '' && $thisQueryLength < $minQueryLength; } /** @@ -116,23 +115,19 @@ public function isMinQueryLength() */ public function getQueryText() { - if (is_null($this->_queryText)) { + if (!isset($this->_queryText)) { $this->_queryText = $this->_getRequest()->getParam($this->getQueryParamName()); if ($this->_queryText === null) { $this->_queryText = ''; } else { - if (is_array($this->_queryText)) { - $this->_queryText = null; - } - $this->_queryText = trim($this->_queryText); - $this->_queryText = Mage::helper('core/string')->cleanString($this->_queryText); - - if (Mage::helper('core/string')->strlen($this->_queryText) > $this->getMaxQueryLength()) { - $this->_queryText = Mage::helper('core/string')->substr( - $this->_queryText, - 0, - $this->getMaxQueryLength() - ); + /* @var $stringHelper Mage_Core_Helper_String */ + $stringHelper = Mage::helper('core/string'); + $this->_queryText = is_array($this->_queryText) ? '' + : $stringHelper->cleanString(trim($this->_queryText)); + + $maxQueryLength = $this->getMaxQueryLength(); + if ($maxQueryLength !== '' && $stringHelper->strlen($this->_queryText) > $maxQueryLength) { + $this->_queryText = $stringHelper->substr($this->_queryText, 0, $maxQueryLength); $this->_isMaxLength = true; } } @@ -147,7 +142,7 @@ public function getQueryText() */ public function getEscapedQueryText() { - return $this->htmlEscape($this->getQueryText()); + return $this->escapeHtml($this->getQueryText()); } /** @@ -211,7 +206,7 @@ public function getAdvancedSearchUrl() * Retrieve minimum query length * * @param mixed $store - * @return int + * @return int|string */ public function getMinQueryLength($store = null) { @@ -222,7 +217,7 @@ public function getMinQueryLength($store = null) * Retrieve maximum query length * * @param mixed $store - * @return int + * @return int|string */ public function getMaxQueryLength($store = null) { @@ -283,23 +278,20 @@ public function getNoteMessages() public function checkNotes($store = null) { if ($this->_isMaxLength) { - $this->addNoteMessage($this->__('Maximum Search query length is %s. Your query was cut.', $this->getMaxQueryLength())); + $this->addNoteMessage($this->__('Maximum Search query length is %s. Your query was cut.', $this->getMaxQueryLength())); } - $stringHelper = Mage::helper('core/string'); /* @var $stringHelper Mage_Core_Helper_String */ + $stringHelper = Mage::helper('core/string'); $searchType = Mage::getStoreConfig(Mage_CatalogSearch_Model_Fulltext::XML_PATH_CATALOG_SEARCH_TYPE); - if ($searchType == Mage_CatalogSearch_Model_Fulltext::SEARCH_TYPE_COMBINE || - $searchType == Mage_CatalogSearch_Model_Fulltext::SEARCH_TYPE_LIKE) { - + if ($searchType == Mage_CatalogSearch_Model_Fulltext::SEARCH_TYPE_COMBINE + || $searchType == Mage_CatalogSearch_Model_Fulltext::SEARCH_TYPE_LIKE + ) { $wordsFull = $stringHelper->splitWords($this->getQueryText(), true); $wordsLike = $stringHelper->splitWords($this->getQueryText(), true, $this->getMaxQueryWords()); - if (count($wordsFull) > count($wordsLike)) { - $wordsCut = array_diff($wordsFull, $wordsLike); - - $wordsCut = array_map(array($this, 'htmlEscape'), $wordsCut); + $wordsCut = array_map(array($this, 'escapeHtml'), array_diff($wordsFull, $wordsLike)); $this->addNoteMessage( $this->__('Maximum words count is %1$s. In your search query was cut next part: %2$s.', $this->getMaxQueryWords(), join(' ', $wordsCut)) ); @@ -318,7 +310,7 @@ public function checkNotes($store = null) public function prepareIndexdata($index, $separator = ' ') { $_index = array(); - foreach ($index as $key => $value) { + foreach ($index as $value) { if (!is_array($value)) { $_index[] = $value; } diff --git a/app/code/core/Mage/CatalogSearch/etc/system.xml b/app/code/core/Mage/CatalogSearch/etc/system.xml index 85622ec4ca..1080fa8c36 100644 --- a/app/code/core/Mage/CatalogSearch/etc/system.xml +++ b/app/code/core/Mage/CatalogSearch/etc/system.xml @@ -63,6 +63,7 @@ text + validate-digits 5 1 1 @@ -71,6 +72,7 @@ text + validate-digits 10 1 1 @@ -79,6 +81,7 @@ text + validate-digits 15 1 1 @@ -98,6 +101,7 @@ text + validate-digits 25 1 1 diff --git a/app/code/core/Mage/Checkout/Block/Cart/Abstract.php b/app/code/core/Mage/Checkout/Block/Cart/Abstract.php index 447ab51062..450ed289ba 100644 --- a/app/code/core/Mage/Checkout/Block/Cart/Abstract.php +++ b/app/code/core/Mage/Checkout/Block/Cart/Abstract.php @@ -129,7 +129,7 @@ public function getCustomer() /** * Get checkout session * - * @return Mage_Checkout_Model_session + * @return Mage_Checkout_Model_Session */ public function getCheckout() { diff --git a/app/code/core/Mage/Checkout/Block/Links.php b/app/code/core/Mage/Checkout/Block/Links.php index 219cca37b7..3bc2138960 100644 --- a/app/code/core/Mage/Checkout/Block/Links.php +++ b/app/code/core/Mage/Checkout/Block/Links.php @@ -72,7 +72,11 @@ public function addCheckoutLink() $parentBlock = $this->getParentBlock(); if ($parentBlock && Mage::helper('core')->isModuleOutputEnabled('Mage_Checkout')) { $text = $this->__('Checkout'); - $parentBlock->addLink($text, 'checkout', $text, true, array(), 60, null, 'class="top-link-checkout"'); + $parentBlock->addLink( + $text, 'checkout', $text, + true, array('_secure' => true), 60, null, + 'class="top-link-checkout"' + ); } return $this; } diff --git a/app/code/core/Mage/Checkout/Block/Onepage.php b/app/code/core/Mage/Checkout/Block/Onepage.php index 3a5f91406a..0bf9cab9f3 100644 --- a/app/code/core/Mage/Checkout/Block/Onepage.php +++ b/app/code/core/Mage/Checkout/Block/Onepage.php @@ -54,146 +54,13 @@ public function getSteps() return $steps; } + /** + * Get active step + * + * @return string + */ public function getActiveStep() { return $this->isCustomerLoggedIn() ? 'billing' : 'login'; } - -/* - // ADDRESSES - - public function getAddressesHtmlSelect($address, $type) - { - if ($this->isCustomerLoggedIn()) { - $options = array(); - foreach ($this->getCustomer()->getAddresses() as $a) { - $options[] = array( - 'value'=>$a->getId(), - 'label'=>$a->getStreet(-1).', '.$a->getCity().', '.$a->getRegion().' '.$a->getPostcode(), - ); - } - - $addressId = $address->getId(); - if (empty($addressId)) { - if ($type=='billing') { - $address = $this->getCustomer()->getPrimaryBillingAddress(); - } else { - $address = $this->getCustomer()->getPrimaryShippingAddress(); - } - if ($address) { - $addressId = $address->getId(); - } - } - - $select = $this->getLayout()->createBlock('core/html_select') - ->setName($type.'_address_id') - ->setId($type.'-address-select') - ->setExtraParams('onchange="'.$type.'.newAddress(!this.value)"') - ->setValue($addressId) - ->setOptions($options); - - $select->addOption('', 'New Address'); - - return $select->getHtml(); - } - return ''; - } - - public function getCountryHtmlSelect($address, $type) - { - $select = $this->getLayout()->createBlock('core/html_select') - ->setName($type.'[country_id]') - ->setId($type.':country_id') - ->setTitle(Mage::helper('checkout')->__('Country')) - ->setClass('validate-select') - ->setValue($address->getCountryId()) - ->setOptions($this->getCountryCollection()->toOptionArray()); - - if ($type==='shipping') { - $select->setExtraParams('onchange="shipping.setSameAsBilling(false);"'); - } - - return $select->getHtml(); - } - - - public function getRegionHtmlSelect($address, $type) - { - $select = $this->getLayout()->createBlock('core/html_select') - ->setName($type.'[region]') - ->setId($type.':region') - ->setTitle(Mage::helper('checkout')->__('State/Province')) - ->setClass('required-entry validate-state') - ->setValue($address->getRegionId()) - ->setOptions($this->getRegionCollection()->toOptionArray()); - - return $select->getHtml(); - } - - // LOGIN STEP - - public function getMessages() - { - return Mage::getSingleton('customer/session')->getMessages(true); - } - - public function getLoginPostAction() - { - return Mage::getUrl('customer/account/loginPost', array('_secure'=>true)); - } - - public function getSuccessUrl() - { - return $this->getUrl('* /*');//////////// - } - - public function getErrorUrl() - { - return $this->getUrl('* /*');//////////// - } - - public function getMethod() - { - return $this->getQuote()->getCheckoutMethod(); - } - - public function getMethodData() - { - return $this->getCheckout()->getMethodData(); - } - - // BILLING STEP - - public function getBillingAddress() { - if (!$this->isCustomerLoggedIn()) { - return $this->getQuote()->getBillingAddress(); - } else { - return Mage::getModel('sales/quote_address'); - } - } - - // SHIPPING STEP - - public function getShippingAddress() - { - if (!$this->isCustomerLoggedIn()) { - return $this->getQuote()->getShippingAddress(); - } else { - return Mage::getModel('sales/quote_address'); - } - } - - // PAYMENT STEP - - public function getPayment() - { - $payment = $this->getQuote()->getPayment(); - if (empty($payment)) { - $payment = Mage::getModel('sales/quote_payment'); - } else { - $payment->setCcNumber(null)->setCcCid(null); - } - return $payment; - } - */ } diff --git a/app/code/core/Mage/Checkout/Model/Cart/Product/Api.php b/app/code/core/Mage/Checkout/Model/Cart/Product/Api.php index 1288f787c0..c45a8e5845 100644 --- a/app/code/core/Mage/Checkout/Model/Cart/Product/Api.php +++ b/app/code/core/Mage/Checkout/Model/Cart/Product/Api.php @@ -34,20 +34,15 @@ class Mage_Checkout_Model_Cart_Product_Api extends Mage_Checkout_Model_Api_Resource_Product { + /** + * Base preparation of product data + * + * @param mixed $data + * @return null|array + */ protected function _prepareProductsData($data) { - if (!is_array($data)) { - return null; - } - - $_data = array(); - if (is_array($data) && is_null($data[0])) { - $_data[] = $data; - } else { - $_data = $data; - } - - return $_data; + return is_array($data) ? $data : null; } /** @@ -133,7 +128,8 @@ public function update($quoteId, $productsData, $store=null) } /** @var $quoteItem Mage_Sales_Model_Quote_Item */ - $quoteItem = $this->_getQuoteItemByProduct($quote, $productByItem, $this->_getProductRequest($productItem)); + $quoteItem = $this->_getQuoteItemByProduct($quote, $productByItem, + $this->_getProductRequest($productItem)); if (is_null($quoteItem->getId())) { $errors[] = Mage::helper('checkout')->__("One item of products is not belong any of quote item"); continue; diff --git a/app/code/core/Mage/Checkout/Model/Cart/Product/Api/V2.php b/app/code/core/Mage/Checkout/Model/Cart/Product/Api/V2.php index 691211f6d9..2ff515026b 100644 --- a/app/code/core/Mage/Checkout/Model/Cart/Product/Api/V2.php +++ b/app/code/core/Mage/Checkout/Model/Cart/Product/Api/V2.php @@ -58,18 +58,18 @@ protected function _prepareProductsData($data){ $arr[$key] = $assocArr; } } - $arr = $this->_prepareData($arr); - return parent::_prepareData($arr); + $arr = $this->_prepareProductsData($arr); + return parent::_prepareProductsData($arr); } if (is_array($data)) { foreach ($data as $key => $value) { if (is_object($value) || is_array($value)) { - $data[$key] = $this->_prepareData($value); + $data[$key] = $this->_prepareProductsData($value); } else { $data[$key] = $value; } } - return parent::_prepareData($data); + return parent::_prepareProductsData($data); } return $data; } diff --git a/app/code/core/Mage/Checkout/Model/Type/Onepage.php b/app/code/core/Mage/Checkout/Model/Type/Onepage.php index 0d4bc722a1..e16724a482 100644 --- a/app/code/core/Mage/Checkout/Model/Type/Onepage.php +++ b/app/code/core/Mage/Checkout/Model/Type/Onepage.php @@ -264,7 +264,7 @@ public function saveBilling($data, $customerAddressId) $addressData = $addressForm->extractData($addressForm->prepareRequest($data)); $addressErrors = $addressForm->validateData($addressData); if ($addressErrors !== true) { - return array('error' => 1, 'message' => $addressErrors); + return array('error' => 1, 'message' => array_values($addressErrors)); } $addressForm->compactData($addressData); //unset billing address attributes which were not shown in form diff --git a/app/code/core/Mage/Checkout/etc/config.xml b/app/code/core/Mage/Checkout/etc/config.xml index 8a9d3cb063..84d467d8fb 100644 --- a/app/code/core/Mage/Checkout/etc/config.xml +++ b/app/code/core/Mage/Checkout/etc/config.xml @@ -170,7 +170,6 @@ - /checkout/cart /checkout/onepage /checkout/multishipping diff --git a/app/code/core/Mage/Cms/data/cms_setup/data-upgrade-1.6.0.0.0-1.6.0.0.1.php b/app/code/core/Mage/Cms/data/cms_setup/data-upgrade-1.6.0.0.0-1.6.0.0.1.php index 7e105285c3..be88e876e5 100644 --- a/app/code/core/Mage/Cms/data/cms_setup/data-upgrade-1.6.0.0.0-1.6.0.0.1.php +++ b/app/code/core/Mage/Cms/data/cms_setup/data-upgrade-1.6.0.0.0-1.6.0.0.1.php @@ -148,7 +148,7 @@ CATEGORY_INFO - + Stores the category info on the page, that allows to display pages more quickly. COMPARE @@ -172,7 +172,7 @@ EXTERNAL_NO_CACHE - + A flag, which indicates whether caching is disabled or not. FRONTEND @@ -180,7 +180,7 @@ GUEST-VIEW - + Allows guests to edit their orders. LAST_CATEGORY @@ -192,11 +192,11 @@ NEWMESSAGE - + Indicates whether a new message has been received. NO_CACHE - + Indicates whether it is allowed to use cache. PERSISTENT_SHOPPING_CART diff --git a/app/code/core/Mage/Compiler/etc/compilation.xml b/app/code/core/Mage/Compiler/etc/compilation.xml index f5b2130b1d..17a5699b00 100644 --- a/app/code/core/Mage/Compiler/etc/compilation.xml +++ b/app/code/core/Mage/Compiler/etc/compilation.xml @@ -103,10 +103,6 @@ - - - - @@ -339,17 +335,16 @@ - - + - - - + + + diff --git a/app/code/core/Mage/Contacts/etc/system.xml b/app/code/core/Mage/Contacts/etc/system.xml index 6d5fdf4f8f..b74bccc557 100644 --- a/app/code/core/Mage/Contacts/etc/system.xml +++ b/app/code/core/Mage/Contacts/etc/system.xml @@ -67,6 +67,7 @@ text + validate-email 10 1 1 diff --git a/app/code/core/Mage/Core/Controller/Request/Http.php b/app/code/core/Mage/Core/Controller/Request/Http.php index efdaecb156..d7ec794a08 100644 --- a/app/code/core/Mage/Core/Controller/Request/Http.php +++ b/app/code/core/Mage/Core/Controller/Request/Http.php @@ -297,7 +297,7 @@ public function getHttpHost($trimPort = true) /** * Set a member of the $_POST superglobal * - * @param striing|array $key + * @param string|array $key * @param mixed $value * * @return Mage_Core_Controller_Request_Http diff --git a/app/code/core/Mage/Core/Controller/Varien/Action.php b/app/code/core/Mage/Core/Controller/Varien/Action.php index 648b646b14..8f16e3c5e9 100755 --- a/app/code/core/Mage/Core/Controller/Varien/Action.php +++ b/app/code/core/Mage/Core/Controller/Varien/Action.php @@ -147,7 +147,6 @@ public function __construct(Zend_Controller_Request_Abstract $request, Zend_Cont protected function _construct() { - } public function hasAction($action) @@ -242,12 +241,12 @@ public function getLayout() /** * Load layout by handles(s) * - * @param string $handles - * @param string $cacheId - * @param boolean $generateBlocks + * @param string|null|bool $handles + * @param bool $generateBlocks + * @param bool $generateXml * @return Mage_Core_Controller_Varien_Action */ - public function loadLayout($handles=null, $generateBlocks=true, $generateXml=true) + public function loadLayout($handles = null, $generateBlocks = true, $generateXml = true) { // if handles were specified in arguments load them first if (false!==$handles && ''!==$handles) { @@ -466,7 +465,7 @@ public function getActionMethodName($action) /** * Dispatch event before action * - * @return null + * @return void */ public function preDispatch() { @@ -488,13 +487,27 @@ public function preDispatch() } if (!$this->getFlag('', self::FLAG_NO_START_SESSION)) { - $checkCookie = in_array($this->getRequest()->getActionName(), $this->_cookieCheckActions); - $checkCookie = $checkCookie && !$this->getRequest()->getParam('nocookie', false); + $checkCookie = in_array($this->getRequest()->getActionName(), $this->_cookieCheckActions) + && !$this->getRequest()->getParam('nocookie', false); $cookies = Mage::getSingleton('core/cookie')->get(); - if ($checkCookie && empty($cookies)) { - $this->setFlag('', self::FLAG_NO_COOKIES_REDIRECT, true); + /** @var $session Mage_Core_Model_Session */ + $session = Mage::getSingleton('core/session', array('name' => $this->_sessionNamespace))->start(); + + if (empty($cookies)) { + if ($session->getCookieShouldBeReceived()) { + $this->setFlag('', self::FLAG_NO_COOKIES_REDIRECT, true); + $session->unsCookieShouldBeReceived(); + $session->setSkipSessionIdFlag(true); + } elseif ($checkCookie) { + if (isset($_GET[$session->getSessionIdQueryParam()]) && Mage::app()->getUseSessionInUrl() + && $this->_sessionNamespace != Mage_Adminhtml_Controller_Action::SESSION_NAMESPACE + ) { + $session->setCookieShouldBeReceived(true); + } else { + $this->setFlag('', self::FLAG_NO_COOKIES_REDIRECT, true); + } + } } - Mage::getSingleton('core/session', array('name' => $this->_sessionNamespace))->start(); } Mage::app()->loadArea($this->getLayout()->getArea()); @@ -553,7 +566,6 @@ public function norouteAction($coreRoute = null) $this->renderLayout(); } else { $status->setForwarded(true); - #$this->_forward('cmsNoRoute', 'index', 'cms'); $this->_forward( $status->getForwardAction(), $status->getForwardController(), @@ -590,7 +602,7 @@ public function noCookiesAction() * @param string $action * @param string|null $controller * @param string|null $module - * @param string|null $params + * @param array|null $params */ protected function _forward($action, $controller = null, $module = null, array $params = null) { @@ -598,15 +610,15 @@ protected function _forward($action, $controller = null, $module = null, array $ $request->initForward(); - if (!is_null($params)) { + if (isset($params)) { $request->setParams($params); } - if (!is_null($controller)) { + if (isset($controller)) { $request->setControllerName($controller); // Module should only be reset if controller has been specified - if (!is_null($module)) { + if (isset($module)) { $request->setModuleName($module); } } @@ -616,7 +628,7 @@ protected function _forward($action, $controller = null, $module = null, array $ } /** - * Inits layout messages by message storage(s), loading and adding messages to layout messages block + * Initializing layout messages by message storage(s), loading and adding messages to layout messages block * * @param string|array $messagesStorage * @return Mage_Core_Controller_Varien_Action @@ -644,7 +656,7 @@ protected function _initLayoutMessages($messagesStorage) } /** - * Inits layout messages by message storage(s), loading and adding messages to layout messages block + * Initializing layout messages by message storage(s), loading and adding messages to layout messages block * * @param string|array $messagesStorage * @return Mage_Core_Controller_Varien_Action @@ -671,17 +683,42 @@ protected function _redirectUrl($url) * * @param string $path * @param array $arguments + * @return Mage_Core_Controller_Varien_Action + */ + protected function _redirect($path, $arguments = array()) + { + return $this->setRedirectWithCookieCheck($path, $arguments); + } + + /** + * Set redirect into response with session id in URL if it is enabled. + * It allows to distinguish primordial request from browser with cookies disabled. + * + * @param string $path + * @param array $arguments + * @return Mage_Core_Controller_Varien_Action */ - protected function _redirect($path, $arguments=array()) + public function setRedirectWithCookieCheck($path, array $arguments = array()) { + /** @var $session Mage_Core_Model_Session */ + $session = Mage::getSingleton('core/session', array('name' => $this->_sessionNamespace)); + if ($session->getCookieShouldBeReceived() && Mage::app()->getUseSessionInUrl() + && $this->_sessionNamespace != Mage_Adminhtml_Controller_Action::SESSION_NAMESPACE + ) { + $arguments += array('_query' => array( + $session->getSessionIdQueryParam() => $session->getSessionId() + )); + } $this->getResponse()->setRedirect(Mage::getUrl($path, $arguments)); return $this; } + /** * Redirect to success page * * @param string $defaultUrl + * @return Mage_Core_Controller_Varien_Action */ protected function _redirectSuccess($defaultUrl) { @@ -700,6 +737,7 @@ protected function _redirectSuccess($defaultUrl) * Redirect to error page * * @param string $defaultUrl + * @return Mage_Core_Controller_Varien_Action */ protected function _redirectError($defaultUrl) { @@ -715,7 +753,7 @@ protected function _redirectError($defaultUrl) } /** - * Set referer url for redirect in responce + * Set referer url for redirect in response * * @param string $defaultUrl * @return Mage_Core_Controller_Varien_Action @@ -885,6 +923,7 @@ protected function _validateFormKey() * * @see self::_renderTitles() * @param string|false|-1|null $text + * @param bool $resetIfExists * @return Mage_Core_Controller_Varien_Action */ protected function _title($text = null, $resetIfExists = true) @@ -990,7 +1029,7 @@ protected function _filterDateTime($array, $dateFields) } /** - * Declare headers and content file in responce for file download + * Declare headers and content file in response for file download * * @param string $fileName * @param string|array $content set to null to avoid starting output, $contentLength should be set explicitly in diff --git a/app/code/core/Mage/Core/Controller/Varien/Front.php b/app/code/core/Mage/Core/Controller/Varien/Front.php index 93312bbc42..a3c9fe933c 100644 --- a/app/code/core/Mage/Core/Controller/Varien/Front.php +++ b/app/code/core/Mage/Core/Controller/Varien/Front.php @@ -296,35 +296,38 @@ protected function _checkBaseUrl($request) if (!Mage::isInstalled() || $request->getPost()) { return; } - if (!Mage::getStoreConfig('web/url/redirect_to_base')) { + + $redirectCode = (int)Mage::getStoreConfig('web/url/redirect_to_base'); + if (!$redirectCode) { return; + } elseif ($redirectCode != 301) { + $redirectCode = 302; } - if ($this->_isAdminFrontNameMatched($request) - && (string)Mage::getConfig()->getNode(Mage_Adminhtml_Helper_Data::XML_PATH_USE_CUSTOM_ADMIN_URL) - ) { + if ($this->_isAdminFrontNameMatched($request)) { return; } - $baseUrl = Mage::getBaseUrl(Mage_Core_Model_Store::URL_TYPE_WEB, Mage::app()->getStore()->isCurrentlySecure()); - + $baseUrl = Mage::getBaseUrl( + Mage_Core_Model_Store::URL_TYPE_WEB, + Mage::getConfig()->shouldUrlBeSecure($request->getPathInfo()) + ); if (!$baseUrl) { return; } - $redirectCode = 302; - if (Mage::getStoreConfig('web/url/redirect_to_base') == 301) { - $redirectCode = 301; - } - - $uri = @parse_url($baseUrl); - $host = isset($uri['host']) ? $uri['host'] : ''; - $path = isset($uri['path']) ? $uri['path'] : ''; - + $uri = @parse_url($baseUrl); $requestUri = $request->getRequestUri() ? $request->getRequestUri() : '/'; - if ($host && $host != $request->getHttpHost() || $path && strpos($requestUri, $path) === false) { + if (isset($uri['scheme']) && $uri['scheme'] != $request->getScheme() + || isset($uri['host']) && $uri['host'] != $request->getHttpHost() + || isset($uri['path']) && strpos($requestUri, $uri['path']) === false + ) { + $redirectUrl = Mage::getSingleton('core/url')->getRedirectUrl( + Mage::getUrl(ltrim($request->getPathInfo(), '/'), array('_nosid' => true)) + ); + Mage::app()->getFrontController()->getResponse() - ->setRedirect($baseUrl, $redirectCode) + ->setRedirect($redirectUrl, $redirectCode) ->sendResponse(); exit; } @@ -338,7 +341,11 @@ protected function _checkBaseUrl($request) */ protected function _isAdminFrontNameMatched($request) { - $adminPath = (string)Mage::getConfig()->getNode(Mage_Adminhtml_Helper_Data::XML_PATH_CUSTOM_ADMIN_PATH); + $useCustomAdminPath = (bool)(string)Mage::getConfig() + ->getNode(Mage_Adminhtml_Helper_Data::XML_PATH_USE_CUSTOM_ADMIN_PATH); + $customAdminPath = (string)Mage::getConfig()->getNode(Mage_Adminhtml_Helper_Data::XML_PATH_CUSTOM_ADMIN_PATH); + $adminPath = ($useCustomAdminPath) ? $customAdminPath : null; + if (!$adminPath) { $adminPath = (string)Mage::getConfig() ->getNode(Mage_Adminhtml_Helper_Data::XML_PATH_ADMINHTML_ROUTER_FRONTNAME); diff --git a/app/code/core/Mage/Core/Controller/Varien/Router/Admin.php b/app/code/core/Mage/Core/Controller/Varien/Router/Admin.php index 46456ec9b5..11b587bbfa 100644 --- a/app/code/core/Mage/Core/Controller/Varien/Router/Admin.php +++ b/app/code/core/Mage/Core/Controller/Varien/Router/Admin.php @@ -88,16 +88,18 @@ protected function _noRouteShouldBeApplied() } /** - * Check whether url should be secure + * Check whether URL for corresponding path should use https protocol * - * @param mixed $path + * @param string $path * @return bool */ protected function _shouldBeSecure($path) { - return substr((string)Mage::getConfig()->getNode('default/web/unsecure/base_url'),0,5)==='https' - || Mage::getStoreConfigFlag('web/secure/use_in_adminhtml', Mage_Core_Model_App::ADMIN_STORE_ID) - && substr((string)Mage::getConfig()->getNode('default/web/secure/base_url'),0,5)==='https'; + $xmlPath = Mage::getStoreConfigFlag( + Mage_Core_Model_Store::XML_PATH_SECURE_IN_ADMINHTML, + Mage_Core_Model_App::ADMIN_STORE_ID + ) ? 'default/web/secure/base_url' : 'default/web/unsecure/base_url'; + return substr((string)Mage::getConfig()->getNode($xmlPath), 0, 5) === 'https'; } /** diff --git a/app/code/core/Mage/Core/Controller/Varien/Router/Standard.php b/app/code/core/Mage/Core/Controller/Varien/Router/Standard.php index 6c72e86e5b..d201d77382 100644 --- a/app/code/core/Mage/Core/Controller/Varien/Router/Standard.php +++ b/app/code/core/Mage/Core/Controller/Varien/Router/Standard.php @@ -412,7 +412,7 @@ public function rewrite(array $p) $p[2] = trim((string)$action); } } -#echo "
".print_r($p,1)."
"; + return $p; } @@ -452,11 +452,17 @@ protected function _getCurrentSecureUrl($request) return Mage::getBaseUrl('link', true).ltrim($request->getPathInfo(), '/'); } + /** + * Check whether URL for corresponding path should use https protocol + * + * @param string $path + * @return bool + */ protected function _shouldBeSecure($path) { - return substr(Mage::getStoreConfig('web/unsecure/base_url'),0,5)==='https' - || Mage::getStoreConfigFlag('web/secure/use_in_frontend') - && substr(Mage::getStoreConfig('web/secure/base_url'),0,5)=='https' - && Mage::getConfig()->shouldUrlBeSecure($path); + $xmlPath = Mage::getConfig()->shouldUrlBeSecure($path) + ? Mage_Core_Model_Store::XML_PATH_SECURE_BASE_URL + : Mage_Core_Model_Store::XML_PATH_UNSECURE_BASE_URL; + return substr(Mage::getStoreConfig($xmlPath), 0, 5) === 'https'; } } diff --git a/app/code/core/Mage/Core/Helper/Cookie.php b/app/code/core/Mage/Core/Helper/Cookie.php new file mode 100644 index 0000000000..cd28cea07f --- /dev/null +++ b/app/code/core/Mage/Core/Helper/Cookie.php @@ -0,0 +1,81 @@ + + */ +class Mage_Core_Helper_Cookie extends Mage_Core_Helper_Abstract +{ + /** + * Cookie name for users who allowed cookie save + */ + const IS_USER_ALLOWED_SAVE_COOKIE = 'user_allowed_save_cookie'; + + /** + * Path to configuration, check is enable cookie restriction mode + */ + const XML_PATH_COOKIE_RESTRICTION = 'web/cookie/cookie_restriction'; + + /** + * Check if cookie restriction notice should be displayed + * + * @return bool + */ + public function isUserNotAllowSaveCookie() + { + $acceptedSaveCookiesWebsites = $this->_getAcceptedSaveCookiesWebsites(); + return Mage::getStoreConfig(self::XML_PATH_COOKIE_RESTRICTION) && + empty($acceptedSaveCookiesWebsites[Mage::app()->getWebsite()->getId()]); + } + + /** + * Return serialzed list of accepted save cookie website + * + * @return string + */ + public function getAcceptedSaveCookiesWebsiteIds() + { + $acceptedSaveCookiesWebsites = $this->_getAcceptedSaveCookiesWebsites(); + $acceptedSaveCookiesWebsites[Mage::app()->getWebsite()->getId()] = 1; + return serialize($acceptedSaveCookiesWebsites); + } + + /** + * Get accepted save cookies websites + * + * @return array + */ + protected function _getAcceptedSaveCookiesWebsites() + { + $serializedList = Mage::getSingleton('core/cookie')->get(self::IS_USER_ALLOWED_SAVE_COOKIE); + $unSerializedList = unserialize($serializedList); + return is_array($unSerializedList) ? $unSerializedList : array(); + } +} diff --git a/app/code/core/Mage/Core/Helper/Js.php b/app/code/core/Mage/Core/Helper/Js.php index bb03d52f3d..96cbec5c78 100644 --- a/app/code/core/Mage/Core/Helper/Js.php +++ b/app/code/core/Mage/Core/Helper/Js.php @@ -84,7 +84,9 @@ public function getTranslatorScript() */ public function getScript($script) { - return ''; + return ''; } /** diff --git a/app/code/core/Mage/Core/Model/App.php b/app/code/core/Mage/Core/Model/App.php index befc1582ea..7703208e15 100644 --- a/app/code/core/Mage/Core/Model/App.php +++ b/app/code/core/Mage/Core/Model/App.php @@ -805,9 +805,11 @@ public function getArea($code) /** * Retrieve application store object * + * @param null|string|bool|int|Mage_Core_Model_Store $id * @return Mage_Core_Model_Store + * @throws Mage_Core_Model_Store_Exception */ - public function getStore($id=null) + public function getStore($id = null) { if (!Mage::isInstalled() || $this->getUpdateMode()) { return $this->_getDefaultStore(); @@ -817,13 +819,13 @@ public function getStore($id=null) return $this->_store; } - if (is_null($id) || ''===$id || $id === true) { + if (!isset($id) || ''===$id || $id === true) { $id = $this->_currentStore; } if ($id instanceof Mage_Core_Model_Store) { return $id; } - if (is_null($id)) { + if (!isset($id)) { $this->throwStoreException(); } @@ -1301,7 +1303,8 @@ public function dispatchEvent($eventName, $args) switch ($obs['type']) { case 'disabled': break; - case 'object': case 'model': + case 'object': + case 'model': $method = $obs['method']; $observer->addData($args); $object = Mage::getModel($obs['model']); @@ -1321,11 +1324,13 @@ public function dispatchEvent($eventName, $args) } /** - * Added not existin observers methods calls protection + * Performs non-existent observer method calls protection * * @param object $object * @param string $method * @param Varien_Event_Observer $observer + * @return Mage_Core_Model_App + * @throws Mage_Core_Exception */ protected function _callObserverMethod($object, $method, $observer) { diff --git a/app/code/core/Mage/Core/Model/Cache.php b/app/code/core/Mage/Core/Model/Cache.php index a548021533..acecd2cc58 100644 --- a/app/code/core/Mage/Core/Model/Cache.php +++ b/app/code/core/Mage/Core/Model/Cache.php @@ -578,7 +578,7 @@ protected function _getInvalidatedTypes() } /** - * Save invalicated cache types + * Save invalidated cache types * * @param array $types * @return Mage_Core_Model_Cache @@ -613,7 +613,7 @@ public function getInvalidatedTypes() * Mark specific cache type(s) as invalidated * * @param string|array $typeCode - * @return + * @return Mage_Core_Model_Cache */ public function invalidateType($typeCode) { @@ -631,7 +631,8 @@ public function invalidateType($typeCode) /** * Clean cached data for specific cache type * - * @param $typeCode + * @param string $typeCode + * @return Mage_Core_Model_Cache */ public function cleanType($typeCode) { diff --git a/app/code/core/Mage/Core/Model/Config.php b/app/code/core/Mage/Core/Model/Config.php index 58827f34ff..45ac03c4ff 100644 --- a/app/code/core/Mage/Core/Model/Config.php +++ b/app/code/core/Mage/Core/Model/Config.php @@ -630,7 +630,7 @@ public function getSectionNode($path) * * @param string $path * @param string $scope - * @param string $scopeCode + * @param string|int $scopeCode * @return Mage_Core_Model_Config_Element */ public function getNode($path=null, $scope='', $scopeCode=null) @@ -1338,7 +1338,7 @@ public function getModelClassName($modelClass) * * @param string $modelClass * @param array|object $constructArguments - * @return Mage_Core_Model_Abstract + * @return Mage_Core_Model_Abstract|false */ public function getModelInstance($modelClass='', $constructArguments=array()) { @@ -1349,10 +1349,6 @@ public function getModelInstance($modelClass='', $constructArguments=array()) Varien_Profiler::stop('CORE::create_object_of::'.$className); return $obj; } else { - /* throw Mage::exception( - 'Mage_Core', - Mage::helper('core')->__('Model class does not exist: %s.', $modelClass) - ); */ return false; } } @@ -1472,18 +1468,23 @@ public function getStoresConfigByPath($path, $allowValues = array(), $useAsKey = } /** - * Check security requirements for url + * Check whether given path should be secure according to configuration security requirements for URL + * "Secure" should not be confused with https protocol, it is about web/secure/*_url settings usage only * * @param string $url * @return bool */ public function shouldUrlBeSecure($url) { + if (!Mage::getStoreConfigFlag(Mage_Core_Model_Store::XML_PATH_SECURE_IN_FRONTEND)) { + return false; + } + if (!isset($this->_secureUrlCache[$url])) { $this->_secureUrlCache[$url] = false; $secureUrls = $this->getNode('frontend/secure_url'); foreach ($secureUrls->children() as $match) { - if (strpos($url, (string)$match)===0) { + if (strpos($url, (string)$match) === 0) { $this->_secureUrlCache[$url] = true; break; } diff --git a/app/code/core/Mage/Core/Model/Email/Template.php b/app/code/core/Mage/Core/Model/Email/Template.php index 32d28b2cda..214a469ba0 100644 --- a/app/code/core/Mage/Core/Model/Email/Template.php +++ b/app/code/core/Mage/Core/Model/Email/Template.php @@ -105,7 +105,7 @@ protected function _getLogoUrl($store) $store = Mage::app()->getStore($store); $fileName = $store->getConfig(self::XML_PATH_DESIGN_EMAIL_LOGO); if ($fileName) { - $uploadDir = Mage_Adminhtml_Model_System_Config_Backend_Email_logo::UPLOAD_DIR; + $uploadDir = Mage_Adminhtml_Model_System_Config_Backend_Email_Logo::UPLOAD_DIR; $fullFileName = Mage::getBaseDir('media') . DS . $uploadDir . DS . $fileName; if (file_exists($fullFileName)) { return Mage::getBaseUrl('media') . $uploadDir . '/' . $fileName; diff --git a/app/code/core/Mage/Core/Model/Input/Filter.php b/app/code/core/Mage/Core/Model/Input/Filter.php new file mode 100644 index 0000000000..2ceebf9e23 --- /dev/null +++ b/app/code/core/Mage/Core/Model/Input/Filter.php @@ -0,0 +1,366 @@ + + * /** @var $filter Mage_Core_Model_Input_Filter {@*} + * $filter = Mage::getModel('core/input_filter'); + * $filter->setFilters(array( + * 'list_values' => array( + * 'children_filters' => array( //filters will applied to all children + * array( + * 'zend' => 'StringToUpper', + * 'args' => array('encoding' => 'utf-8')), + * array('zend' => 'StripTags') + * ) + * ), + * 'list_values_with_name' => array( + * 'children_filters' => array( + * 'item1' => array( + * array( + * 'zend' => 'StringToUpper', + * 'args' => array('encoding' => 'utf-8'))), + * 'item2' => array( + * array('model' => 'core/input_filter_maliciousCode') + * ), + * 'item3' => array( + * array( + * 'helper' => 'core', + * 'method' => 'stripTags', + * 'args' => array('

', true)) + * ) + * ) + * ) + * )); + * $filter->addFilter('name2', new Zend_Filter_Alnum()); + * $filter->addFilter('name1', + * array( + * 'zend' => 'StringToUpper', + * 'args' => array('encoding' => 'utf-8'))); + * $filter->addFilter('name1', array('zend' => 'StripTags'), Zend_Filter::CHAIN_PREPEND); + * $filter->addFilters(protected $_filtersToAdd = array( + * 'list_values_with_name' => array( + * 'children_filters' => array( + * 'deep_list' => array( + * 'children_filters' => array( + * 'sub1' => array( + * array( + * 'zend' => 'StringToLower', + * 'args' => array('encoding' => 'utf-8'))), + * 'sub2' => array(array('zend' => 'Int')) + * ) + * ) + * ) + * ) + * )); + * $filter->filter(array( + * 'name1' => 'some string', + * 'name2' => '888 555', + * 'list_values' => array( + * 'some string2', + * 'some

string3

', + * ), + * 'list_values_with_name' => array( + * 'item1' => 'some string4', + * 'item2' => 'some string5', + * 'item3' => 'some

string5

bold
div
', + * 'deep_list' => array( + * 'sub1' => 'toLowString', + * 'sub2' => '5 TO INT', + * ) + * ) + * )); + * + * + * @see Mage_Core_Model_Input_FilterTest See this class for manual + * @category Mage + * @package Mage_Core + * @author Magento Api Team + */ +class Mage_Core_Model_Input_Filter implements Zend_Filter_Interface +{ + /** + * Filters data collectors + * + * @var array + */ + protected $_filters = array(); + + /** + * Add filter + * + * @param string $name + * @param array|Zend_Filter_Interface $filter + * @param string $placement + * @return Mage_Core_Model_Input_Filter + */ + public function addFilter($name, $filter, $placement = Zend_Filter::CHAIN_APPEND) + { + if ($placement == Zend_Filter::CHAIN_PREPEND) { + array_unshift($this->_filters[$name], $filter); + } else { + $this->_filters[$name][] = $filter; + } + return $this; + } + + /** + * Add a filter to the end of the chain + * + * @param array|Zend_Filter_Interface $filter + * @return Mage_Core_Model_Input_Filter + */ + public function appendFilter(Zend_Filter_Interface $filter) + { + return $this->addFilter($filter, Zend_Filter::CHAIN_APPEND); + } + + /** + * Add a filter to the start of the chain + * + * @param array|Zend_Filter_Interface $filter + * @return Mage_Core_Model_Input_Filter + */ + public function prependFilter($filter) + { + return $this->addFilter($filter, Zend_Filter::CHAIN_PREPEND); + } + + /** + * Add filters + * + * Filters data must be has view as + * array( + * 'key1' => $filters, + * 'key2' => array( ... ), //array filters data + * 'key2' => $filters + * ) + * + * @param array $filters + * @return Mage_Core_Model_Input_Filter + */ + public function addFilters(array $filters) + { + $this->_filters = array_merge_recursive($this->_filters, $filters); + return $this; + } + + /** + * Set filters + * + * @param array $filters + * @return Mage_Core_Model_Input_Filter + */ + public function setFilters(array $filters) + { + $this->_filters = $filters; + return $this; + } + + /** + * Get filters + * + * @param string|null $name Get filter for selected name + * @return array + */ + public function getFilters($name = null) + { + if (null === $name) { + return $this->_filters; + } else { + return isset($this->_filters[$name]) ? $this->_filters[$name] : null; + } + } + + /** + * Filter data + * + * @param array $data + * @return array Return filtered data + */ + public function filter($data) + { + return $this->_filter($data); + } + + /** + * Recursive filtering + * + * @param array $data + * @param array|null $filters + * @param bool $isFilterListSimple + * @return array + * @throws Exception Exception when filter is not found or not instance of defined instances + */ + protected function _filter(array $data, &$filters = null, $isFilterListSimple = false) + { + if (null === $filters) { + $filters = &$this->_filters; + } + foreach ($data as $key => $value) { + if (!$isFilterListSimple && !empty($filters[$key])) { + $itemFilters = $filters[$key]; + } elseif ($isFilterListSimple && !empty($filters)) { + $itemFilters = $filters; + } else { + continue; + } + + if (!$isFilterListSimple && is_array($value) && isset($filters[$key]['children_filters'])) { + $isChildrenFilterListSimple = is_numeric(implode('', array_keys($filters[$key]['children_filters']))); + $value = $this->_filter($value, $filters[$key]['children_filters'], $isChildrenFilterListSimple); + } else { + foreach ($itemFilters as $filterData) { + if ($zendFilter = $this->_getZendFilter($filterData)) { + $value = $zendFilter->filter($value); + } elseif ($filtrationHelper = $this->_getFiltrationHelper($filterData)) { + $value = $this->_applyFiltrationWithHelper($value, $filtrationHelper, $filterData); + } + } + } + $data[$key] = $value; + } + return $data; + } + + /** + * Call specified helper method for $value filtration + * + * @param mixed $value + * @param Mage_Core_Helper_Abstract $helper + * @param array $filterData + * @return mixed + */ + protected function _applyFiltrationWithHelper($value, Mage_Core_Helper_Abstract $helper, array $filterData) + { + if (!isset($filterData['method']) || empty($filterData['method'])) { + throw new Exception("Helper filtration method is not set"); + } + if (!isset($filterData['args']) || empty($filterData['args'])) { + $filterData['args'] = array(); + } + $filterData['args'] = array(-100 => $value) + $filterData['args']; + // apply filter + $value = call_user_func_array(array($helper, $filterData['method']), $filterData['args']); + return $value; + } + + /** + * Try to create Magento helper for filtration based on $filterData. Return false on failure + * + * @param $filterData + * @return bool|Mage_Core_Helper_Abstract + * @throws Exception + */ + protected function _getFiltrationHelper($filterData) + { + $helper = false; + if (isset($filterData['helper'])) { + $helper = $filterData['helper']; + if (is_string($helper)) { + $helper = Mage::helper($helper); + } + if (!($helper instanceof Mage_Core_Helper_Abstract)) { + throw new Exception("Filter '{$filterData['helper']}' not found"); + } + } + return $helper; + } + + /** + * Try to create Zend filter based on $filterData. Return false on failure + * + * @param $filterData + * @return bool|Zend_Filter_Interface + */ + protected function _getZendFilter($filterData) + { + $zendFilter = false; + if (is_object($filterData) && $filterData instanceof Zend_Filter_Interface) { + /** @var $zendFilter Zend_Filter_Interface */ + $zendFilter = $filterData; + } elseif (isset($filterData['model'])) { + $zendFilter = $this->_createCustomZendFilter($filterData); + } elseif (isset($filterData['zend'])) { + $zendFilter = $this->_createNativeZendFilter($filterData); + } + return $zendFilter; + } + + /** + * Get Magento filters + * + * @param $filterData + * @return Zend_Filter_Interface + * @throws Exception + */ + protected function _createCustomZendFilter($filterData) + { + $filter = $filterData['model']; + if (!isset($filterData['args'])) { + $filterData['args'] = null; + } else { + //use only first element because Mage factory cannot get more + $filterData['args'] = $filterData['args'][0]; + } + if (is_string($filterData['model'])) { + $filter = Mage::getModel($filterData['model'], $filterData['args']); + } + if (!($filter instanceof Zend_Filter_Interface)) { + throw new Exception('Filter is not instance of Zend_Filter_Interface'); + } + return $filter; + } + + /** + * Get native Zend_Filter + * + * @param $filterData + * @return Zend_Filter_Interface + * @throws Exception + */ + protected function _createNativeZendFilter($filterData) + { + $filter = $filterData['zend']; + if (is_string($filter)) { + $class = new ReflectionClass('Zend_Filter_' . $filter); + if ($class->implementsInterface('Zend_Filter_Interface')) { + if (isset($filterData['args']) && $class->hasMethod('__construct')) { + $filter = $class->newInstanceArgs($filterData['args']); + } else { + $filter = $class->newInstance(); + } + } else { + throw new Exception('Filter is not instance of Zend_Filter_Interface'); + } + } + return $filter; + } +} diff --git a/app/code/core/Mage/Core/Model/Magento/Api.php b/app/code/core/Mage/Core/Model/Magento/Api.php new file mode 100644 index 0000000000..ba292f3c54 --- /dev/null +++ b/app/code/core/Mage/Core/Model/Magento/Api.php @@ -0,0 +1,49 @@ + + */ +class Mage_Core_Model_Magento_Api extends Mage_Api_Model_Resource_Abstract +{ + /** + * Retrieve information about current Magento installation + * + * @return array + */ + public function info() + { + $result = array(); + $result['magento_edition'] = Mage::getEdition(); + $result['magento_version'] = Mage::getVersion(); + + return $result; + } +} diff --git a/app/code/core/Mage/Core/Model/Magento/Api/V2.php b/app/code/core/Mage/Core/Model/Magento/Api/V2.php new file mode 100644 index 0000000000..a5ff04abcb --- /dev/null +++ b/app/code/core/Mage/Core/Model/Magento/Api/V2.php @@ -0,0 +1,36 @@ + + */ +class Mage_Core_Model_Magento_Api_V2 extends Mage_Core_Model_Magento_Api +{ +} diff --git a/app/code/core/Mage/Core/Model/Resource/Iterator.php b/app/code/core/Mage/Core/Model/Resource/Iterator.php index 4a14bfa743..42eece9dd8 100644 --- a/app/code/core/Mage/Core/Model/Resource/Iterator.php +++ b/app/code/core/Mage/Core/Model/Resource/Iterator.php @@ -37,7 +37,7 @@ class Mage_Core_Model_Resource_Iterator extends Varien_Object * @param Zend_Db_Statement_Interface|Zend_Db_Select|string $query * @param array|string $callbacks * @param array $args - * @param Varien_Db_Adapter_interface $adapter + * @param Varien_Db_Adapter_Interface $adapter * @return Mage_Core_Model_Resource_Iterator */ public function walk($query, array $callbacks, array $args=array(), $adapter = null) diff --git a/app/code/core/Mage/Core/Model/Session.php b/app/code/core/Mage/Core/Model/Session.php index ea52f0690e..5abbc92b63 100644 --- a/app/code/core/Mage/Core/Model/Session.php +++ b/app/code/core/Mage/Core/Model/Session.php @@ -30,6 +30,9 @@ * * @todo extend from Mage_Core_Model_Session_Abstract * + * @method null|bool getCookieShouldBeReceived() + * @method Mage_Core_Model_Session setCookieShouldBeReceived(bool $flag) + * @method Mage_Core_Model_Session unsCookieShouldBeReceived() */ class Mage_Core_Model_Session extends Mage_Core_Model_Session_Abstract { diff --git a/app/code/core/Mage/Core/Model/Store.php b/app/code/core/Mage/Core/Model/Store.php index bb9cd89079..18163e8480 100644 --- a/app/code/core/Mage/Core/Model/Store.php +++ b/app/code/core/Mage/Core/Model/Store.php @@ -585,11 +585,26 @@ protected function _updatePathUseRewrites($url) || !$this->getConfig(self::XML_PATH_USE_REWRITES) || !Mage::isInstalled() ) { - $url .= basename($_SERVER['SCRIPT_FILENAME']) . '/'; + if ($this->_isCustomEntryPoint()) { + $indexFileName = 'index.php'; + } else { + $indexFileName = basename($_SERVER['SCRIPT_FILENAME']); + } + $url .= $indexFileName . '/'; } return $url; } + /** + * Check if used entry point is custom + * + * @return bool + */ + protected function _isCustomEntryPoint() + { + return (bool)Mage::registry('custom_entry_point'); + } + /** * Retrieve URL for media catalog * @@ -674,7 +689,7 @@ public function isAdminUrlSecure() } /** - * Check if forntend URLs should be secure + * Check if frontend URLs should be secure * * @return boolean */ diff --git a/app/code/core/Mage/Core/Model/Url.php b/app/code/core/Mage/Core/Model/Url.php index cef90cfc86..c87bf481a3 100644 --- a/app/code/core/Mage/Core/Model/Url.php +++ b/app/code/core/Mage/Core/Model/Url.php @@ -864,7 +864,7 @@ public function purgeQueryParams() } /** - * Retrurn Query Params + * Return Query Params * * @return array */ @@ -1035,13 +1035,11 @@ protected function _prepareSessionUrlWithParams($url, array $params) /** @var $session Mage_Core_Model_Session */ $session = Mage::getSingleton('core/session', $params); - if (Mage::app()->getUseSessionVar() && !$session->getSessionIdForHost($url)) { + $sessionId = $session->getSessionIdForHost($url); + if (Mage::app()->getUseSessionVar() && !$sessionId) { $this->setQueryParam('___SID', $this->getSecure() ? 'S' : 'U'); // Secure/Unsecure - } else { - $sessionId = $session->getSessionIdForHost($url); - if ($sessionId) { - $this->setQueryParam($session->getSessionIdQueryParam(), $sessionId); - } + } else if ($sessionId) { + $this->setQueryParam($session->getSessionIdQueryParam(), $sessionId); } return $this; } diff --git a/app/code/core/Mage/Core/Model/Url/Validator.php b/app/code/core/Mage/Core/Model/Url/Validator.php new file mode 100644 index 0000000000..6cad2be8d5 --- /dev/null +++ b/app/code/core/Mage/Core/Model/Url/Validator.php @@ -0,0 +1,78 @@ + + */ +class Mage_Core_Model_Url_Validator extends Zend_Validate_Abstract +{ + /**#@+ + * Error keys + */ + const INVALID_URL = 'invalidUrl'; + /**#@-*/ + + /** + * Object constructor + */ + public function __construct() + { + // set translated message template + $this->setMessage(Mage::helper('core')->__("Invalid URL '%value%'."), self::INVALID_URL); + } + + /** + * Validation failure message template definitions + * + * @var array + */ + protected $_messageTemplates = array( + self::INVALID_URL => "Invalid URL '%value%'.", + ); + + /** + * Validate value + * + * @param string $value + * @return bool + */ + public function isValid($value) + { + $this->_setValue($value); + + //check valid URL + if (!Zend_Uri::check($value)) { + $this->_error(self::INVALID_URL); + return false; + } + + return true; + } +} diff --git a/app/code/core/Mage/Core/etc/api.xml b/app/code/core/Mage/Core/etc/api.xml index 9a57a460d6..1d7daf379e 100644 --- a/app/code/core/Mage/Core/etc/api.xml +++ b/app/code/core/Mage/Core/etc/api.xml @@ -50,13 +50,26 @@ + + core/magento_api + Magento info API + core/magento + + + Get info about current Magento installation + core/magento/info + + + core_store + core_magento store + magento @@ -77,6 +90,12 @@ List of stores + + Magento info + + Retrieve info about current Magento installation + + diff --git a/app/code/core/Mage/Core/etc/config.xml b/app/code/core/Mage/Core/etc/config.xml index 15ca68af86..a36a57421d 100644 --- a/app/code/core/Mage/Core/etc/config.xml +++ b/app/code/core/Mage/Core/etc/config.xml @@ -271,6 +271,7 @@ 3600 css + css_secure js @@ -338,6 +339,7 @@ 3600 1 0 + 31536000 0 diff --git a/app/code/core/Mage/Core/etc/system.xml b/app/code/core/Mage/Core/etc/system.xml index 775910c632..aaadb7a248 100644 --- a/app/code/core/Mage/Core/etc/system.xml +++ b/app/code/core/Mage/Core/etc/system.xml @@ -120,8 +120,8 @@ text - adminhtml/system_config_backend_email_address validate-email + adminhtml/system_config_backend_email_address 2 1 1 @@ -150,8 +150,8 @@ text - adminhtml/system_config_backend_email_address validate-email + adminhtml/system_config_backend_email_address 2 1 1 @@ -180,8 +180,8 @@ text - adminhtml/system_config_backend_email_address validate-email + adminhtml/system_config_backend_email_address 2 1 1 @@ -210,8 +210,8 @@ text - adminhtml/system_config_backend_email_address validate-email + adminhtml/system_config_backend_email_address 2 1 1 @@ -240,8 +240,8 @@ text - adminhtml/system_config_backend_email_address validate-email + adminhtml/system_config_backend_email_address 2 1 1 @@ -785,7 +785,7 @@ 25 1 1 - 1 + 0 1 @@ -794,7 +794,7 @@ 27 1 1 - 1 + 0 1
@@ -893,8 +893,8 @@ text - adminhtml/system_config_backend_email_address validate-email + adminhtml/system_config_backend_email_address 80 1 0 @@ -977,8 +977,8 @@ adminhtml/system_config_source_email_template 10 1 - 1 - 1 + 0 + 0 @@ -986,8 +986,8 @@ adminhtml/system_config_source_email_identity 20 1 - 1 - 1 + 0 + 0 @@ -1411,6 +1411,7 @@ select adminhtml/system_config_source_yesno + adminhtml/system_config_backend_cookie 50 1 1 diff --git a/app/code/core/Mage/Core/etc/wsdl.xml b/app/code/core/Mage/Core/etc/wsdl.xml index 0689e69f79..8f3116b73f 100644 --- a/app/code/core/Mage/Core/etc/wsdl.xml +++ b/app/code/core/Mage/Core/etc/wsdl.xml @@ -23,6 +23,12 @@ + + + + + + @@ -31,6 +37,12 @@ + + + + + + @@ -49,6 +61,11 @@ + + Info about current Magento installation + + + @@ -70,6 +87,15 @@ + + + + + + + + + diff --git a/app/code/core/Mage/Core/etc/wsi.xml b/app/code/core/Mage/Core/etc/wsi.xml index 8877c0fbdb..7e2b3b7155 100644 --- a/app/code/core/Mage/Core/etc/wsi.xml +++ b/app/code/core/Mage/Core/etc/wsi.xml @@ -24,6 +24,12 @@ + + + + + + @@ -53,6 +59,20 @@ + + + + + + + + + + + + + + @@ -67,6 +87,12 @@ + + + + + + List of stores @@ -78,6 +104,11 @@ + + Info about current Magento installation + + + @@ -99,6 +130,15 @@ + + + + + + + + + diff --git a/app/code/core/Mage/Customer/Block/Widget/Abstract.php b/app/code/core/Mage/Customer/Block/Widget/Abstract.php index 0dade6d7eb..7a56500d62 100644 --- a/app/code/core/Mage/Customer/Block/Widget/Abstract.php +++ b/app/code/core/Mage/Customer/Block/Widget/Abstract.php @@ -61,7 +61,7 @@ public function getFieldName($field) * Retrieve customer attribute instance * * @param string $attributeCode - * @return Mage_Customer_Model_Attribute + * @return Mage_Customer_Model_Attribute|false */ protected function _getAttribute($attributeCode) { diff --git a/app/code/core/Mage/Customer/Block/Widget/Name.php b/app/code/core/Mage/Customer/Block/Widget/Name.php index 50c40addcc..e749eeedda 100644 --- a/app/code/core/Mage/Customer/Block/Widget/Name.php +++ b/app/code/core/Mage/Customer/Block/Widget/Name.php @@ -42,11 +42,7 @@ public function _construct() */ protected function _showConfig($key) { - $value = $this->getConfig($key); - if (empty($value)) { - return false; - } - return true; + return (bool)$this->getConfig($key); } /** @@ -70,7 +66,7 @@ public function isPrefixRequired() } /** - * Retrieve name prefix dropdown options + * Retrieve name prefix drop-down options * * @return array|bool */ @@ -126,7 +122,7 @@ public function isSuffixRequired() } /** - * Retrieve name suffix dropdown options + * Retrieve name suffix drop-down options * * @return array|bool */ @@ -171,7 +167,7 @@ public function getContainerClassName() * Retrieve customer or customer address attribute instance * * @param string $attributeCode - * @return Mage_Eav_Model_Entity_Attribute_Abstract + * @return Mage_Customer_Model_Attribute|false */ protected function _getAttribute($attributeCode) { @@ -179,16 +175,16 @@ protected function _getAttribute($attributeCode) return parent::_getAttribute($attributeCode); } - $_attribute = Mage::getSingleton('eav/config')->getAttribute('customer_address', $attributeCode); + $attribute = Mage::getSingleton('eav/config')->getAttribute('customer_address', $attributeCode); - if ($this->getForceUseCustomerRequiredAttributes() && !$_attribute->getIsRequired()) { + if ($this->getForceUseCustomerRequiredAttributes() && $attribute && !$attribute->getIsRequired()) { $customerAttribute = parent::_getAttribute($attributeCode); if ($customerAttribute && $customerAttribute->getIsRequired()) { - $_attribute = $customerAttribute; + $attribute = $customerAttribute; } } - return $_attribute; + return $attribute; } /** @@ -200,9 +196,6 @@ protected function _getAttribute($attributeCode) public function getStoreLabel($attributeCode) { $attribute = $this->_getAttribute($attributeCode); - if ($attribute) { - return $this->__($attribute->getStoreLabel()); - } - return ''; + return $attribute ? $this->__($attribute->getStoreLabel()) : ''; } } diff --git a/app/code/core/Mage/Customer/Helper/Address.php b/app/code/core/Mage/Customer/Helper/Address.php index 81c6d1b4f2..bfb68a5f21 100644 --- a/app/code/core/Mage/Customer/Helper/Address.php +++ b/app/code/core/Mage/Customer/Helper/Address.php @@ -38,6 +38,7 @@ class Mage_Customer_Helper_Address extends Mage_Core_Helper_Abstract const XML_PATH_VIV_ON_EACH_TRANSACTION = 'customer/create_account/viv_on_each_transaction'; const XML_PATH_VAT_VALIDATION_ENABLED = 'customer/create_account/auto_group_assign'; const XML_PATH_VIV_TAX_CALCULATION_ADDRESS_TYPE = 'customer/create_account/tax_calculation_address_type'; + const XML_PATH_VAT_FRONTEND_VISIBILITY = 'customer/create_account/vat_frontend_visibility'; /** * Array of Customer Address Attributes @@ -138,17 +139,14 @@ public function getFormat($code) } /** - * Determine if specified address config value can show + * Determine if specified address config value can be shown * + * @param string $key * @return bool */ public function canShowConfig($key) { - $value = $this->getConfig($key); - if (empty($value)) { - return false; - } - return true; + return (bool)$this->getConfig($key); } /** @@ -169,6 +167,34 @@ public function getAttributes() return $this->_attributes; } + /** + * Get string with frontend validation classes for attribute + * + * @param string $attributeCode + * @return string + */ + public function getAttributeValidationClass($attributeCode) + { + /** @var $attribute Mage_Customer_Model_Attribute */ + $attribute = isset($this->_attributes[$attributeCode]) ? $this->_attributes[$attributeCode] + : Mage::getSingleton('eav/config')->getAttribute('customer_address', $attributeCode); + $class = $attribute ? $attribute->getFrontend()->getClass() : ''; + + if (in_array($attributeCode, array('firstname', 'middlename', 'lastname', 'prefix', 'suffix', 'taxvat'))) { + if ($class && !$attribute->getIsVisible()) { + $class = ''; // address attribute is not visible thus its validation rules are not applied + } + + /** @var $customerAttribute Mage_Customer_Model_Attribute */ + $customerAttribute = Mage::getSingleton('eav/config')->getAttribute('customer', $attributeCode); + $class .= $customerAttribute && $customerAttribute->getIsVisible() + ? $customerAttribute->getFrontend()->getClass() : ''; + $class = implode(' ', array_unique(array_filter(explode(' ', $class)))); + } + + return $class; + } + /** * Convert streets array to new street lines count * Examples of use: @@ -251,4 +277,14 @@ public function getTaxCalculationAddressType($store = null) { return (string)Mage::getStoreConfig(self::XML_PATH_VIV_TAX_CALCULATION_ADDRESS_TYPE, $store); } + + /** + * Check if VAT ID address attribute has to be shown on frontend (on Customer Address management forms) + * + * @return boolean + */ + public function isVatAttributeVisible() + { + return (bool)Mage::getStoreConfig(self::XML_PATH_VAT_FRONTEND_VISIBILITY); + } } diff --git a/app/code/core/Mage/Customer/Helper/Data.php b/app/code/core/Mage/Customer/Helper/Data.php index fb746fb141..4cc32b6cdf 100644 --- a/app/code/core/Mage/Customer/Helper/Data.php +++ b/app/code/core/Mage/Customer/Helper/Data.php @@ -39,13 +39,18 @@ class Mage_Customer_Helper_Data extends Mage_Core_Helper_Abstract */ const REFERER_QUERY_PARAM_NAME = 'referer'; + /** + * Route for customer account login page + */ + const ROUTE_ACCOUNT_LOGIN = 'customer/account/login'; + /** * Config name for Redirect Customer to Account Dashboard after Logging in setting */ const XML_PATH_CUSTOMER_STARTUP_REDIRECT_TO_DASHBOARD = 'customer/startup/redirect_dashboard'; /** - * Config pathes to VAT related customer groups + * Config paths to VAT related customer groups */ const XML_PATH_CUSTOMER_VIV_INTRA_UNION_GROUP = 'customer/create_account/viv_intra_union_group'; const XML_PATH_CUSTOMER_VIV_DOMESTIC_GROUP = 'customer/create_account/viv_domestic_group'; @@ -128,7 +133,7 @@ public function getGroups() } /** - * Retrieve current (loggined) customer object + * Retrieve current (logged in) customer object * * @return Mage_Customer_Model_Customer */ @@ -167,22 +172,33 @@ public function customerHasAddresses() * @return string */ public function getLoginUrl() + { + return $this->_getUrl(self::ROUTE_ACCOUNT_LOGIN, $this->getLoginUrlParams()); + } + + /** + * Retrieve parameters of customer login url + * + * @return array + */ + public function getLoginUrlParams() { $params = array(); $referer = $this->_getRequest()->getParam(self::REFERER_QUERY_PARAM_NAME); - if (!$referer && !Mage::getStoreConfigFlag(self::XML_PATH_CUSTOMER_STARTUP_REDIRECT_TO_DASHBOARD)) { - if (!Mage::getSingleton('customer/session')->getNoReferer()) { - $referer = Mage::getUrl('*/*/*', array('_current' => true, '_use_rewrite' => true)); - $referer = Mage::helper('core')->urlEncode($referer); - } + if (!$referer && !Mage::getStoreConfigFlag(self::XML_PATH_CUSTOMER_STARTUP_REDIRECT_TO_DASHBOARD) + && !Mage::getSingleton('customer/session')->getNoReferer() + ) { + $referer = Mage::getUrl('*/*/*', array('_current' => true, '_use_rewrite' => true)); + $referer = Mage::helper('core')->urlEncode($referer); } + if ($referer) { $params = array(self::REFERER_QUERY_PARAM_NAME => $referer); } - return $this->_getUrl('customer/account/login', $params); + return $params; } /** diff --git a/app/code/core/Mage/Customer/Model/Api2/Customer.php b/app/code/core/Mage/Customer/Model/Api2/Customer.php new file mode 100644 index 0000000000..de14198c24 --- /dev/null +++ b/app/code/core/Mage/Customer/Model/Api2/Customer.php @@ -0,0 +1,45 @@ + + */ +class Mage_Customer_Model_Api2_Customer extends Mage_Api2_Model_Resource +{ + /** + * Resource specific method to retrieve attributes' codes. May be overriden in child. + * + * @return array + */ + protected function _getResourceAttributes() + { + return $this->getEavAttributes(Mage_Api2_Model_Auth_User_Admin::USER_TYPE != $this->getUserType(), true); + } +} diff --git a/app/code/core/Mage/Customer/Model/Api2/Customer/Address.php b/app/code/core/Mage/Customer/Model/Api2/Customer/Address.php new file mode 100644 index 0000000000..e37c4936f0 --- /dev/null +++ b/app/code/core/Mage/Customer/Model/Api2/Customer/Address.php @@ -0,0 +1,129 @@ + + */ +class Mage_Customer_Model_Api2_Customer_Address extends Mage_Api2_Model_Resource +{ + /** + * Resource specific method to retrieve attributes' codes. May be overriden in child. + * + * @return array + */ + protected function _getResourceAttributes() + { + return $this->getEavAttributes(Mage_Api2_Model_Auth_User_Admin::USER_TYPE != $this->getUserType()); + } + + /** + * Get customer address resource validator instance + * + * @return Mage_Customer_Model_Api2_Customer_Address_Validator + */ + protected function _getValidator() + { + return Mage::getModel('customer/api2_customer_address_validator', array('resource' => $this)); + } + + /** + * Is specified address a default billing address? + * + * @param Mage_Customer_Model_Address $address + * @return bool + */ + protected function _isDefaultBillingAddress(Mage_Customer_Model_Address $address) + { + return $address->getCustomer()->getDefaultBilling() == $address->getId(); + } + + /** + * Is specified address a default shipping address? + * + * @param Mage_Customer_Model_Address $address + * @return bool + */ + protected function _isDefaultShippingAddress(Mage_Customer_Model_Address $address) + { + return $address->getCustomer()->getDefaultShipping() == $address->getId(); + } + + /** + * Get region id by name or code + * If id is not found then return passed $region + * + * @param string $region + * @return int|string + */ + protected function _getRegionIdByNameOrCode($region) + { + $id = Mage::getResourceModel('directory/region_collection') + ->addFieldToFilter(array('default_name', 'code'), array($region, $region)) + ->getFirstItem() + ->getId(); + return $id ? $id : $region; + } + + /** + * Load customer address by id + * + * @param int $id + * @return Mage_Customer_Model_Address + */ + protected function _loadCustomerAddressById($id) + { + /* @var $address Mage_Customer_Model_Address */ + $address = Mage::getModel('customer/address')->load($id); + + if (!$address->getId()) { + $this->_critical(self::RESOURCE_NOT_FOUND); + } + $address->addData($this->_getDefaultAddressesInfo($address)); + + return $address; + } + + /** + * Load customer by id + * + * @param int $id + * @throws Mage_Api2_Exception + * @return Mage_Customer_Model_Customer + */ + protected function _loadCustomerById($id) + { + /* @var $customer Mage_Customer_Model_Customer */ + $customer = Mage::getModel('customer/customer')->load($id); + if (!$customer->getId()) { + $this->_critical(self::RESOURCE_NOT_FOUND); + } + return $customer; + } +} diff --git a/app/code/core/Mage/Customer/Model/Api2/Customer/Address/Rest.php b/app/code/core/Mage/Customer/Model/Api2/Customer/Address/Rest.php new file mode 100644 index 0000000000..bbb54bfb4d --- /dev/null +++ b/app/code/core/Mage/Customer/Model/Api2/Customer/Address/Rest.php @@ -0,0 +1,192 @@ + + */ +abstract class Mage_Customer_Model_Api2_Customer_Address_Rest extends Mage_Customer_Model_Api2_Customer_Address +{ + /** + * Create customer address + * + * @param array $data + * @throws Mage_Api2_Exception + * @return string + */ + protected function _create(array $data) + { + /* @var $customer Mage_Customer_Model_Customer */ + $customer = $this->_loadCustomerById($this->getRequest()->getParam('customer_id')); + $validator = $this->_getValidator(); + + $data = $validator->filter($data); + if (!$validator->isValidData($data) || !$validator->isValidDataForCreateAssociationWithCountry($data)) { + foreach ($validator->getErrors() as $error) { + $this->_error($error, Mage_Api2_Model_Server::HTTP_BAD_REQUEST); + } + $this->_critical(self::RESOURCE_DATA_PRE_VALIDATION_ERROR); + } + + $data['region'] = $this->_getRegionIdByNameOrCode($data['region']); + + /* @var $address Mage_Customer_Model_Address */ + $address = Mage::getModel('customer/address'); + $address->setData($data); + $address->setCustomer($customer); + + try { + $address->save(); + } catch (Mage_Core_Exception $e) { + $this->_error($e->getMessage(), Mage_Api2_Model_Server::HTTP_INTERNAL_ERROR); + } catch (Exception $e) { + $this->_critical(self::RESOURCE_INTERNAL_ERROR); + } + return $this->_getLocation($address); + } + + /** + * Retrieve information about specified customer address + * + * @throws Mage_Api2_Exception + * @return array + */ + protected function _retrieve() + { + /* @var $address Mage_Customer_Model_Address */ + $address = $this->_loadCustomerAddressById($this->getRequest()->getParam('id')); + $addressData = $address->getData(); + $addressData['street'] = $address->getStreet(); + return $addressData; + } + + /** + * Get customer addresses list + * + * @return array + */ + protected function _retrieveCollection() + { + $data = array(); + /* @var $address Mage_Customer_Model_Address */ + foreach ($this->_getCollectionForRetrieve() as $address) { + $addressData = $address->getData(); + $addressData['street'] = $address->getStreet(); + $data[] = array_merge($addressData, $this->_getDefaultAddressesInfo($address)); + } + return $data; + } + + /** + * Retrieve collection instances + * + * @return Mage_Customer_Model_Resource_Address_Collection + */ + protected function _getCollectionForRetrieve() + { + /* @var $customer Mage_Customer_Model_Customer */ + $customer = $this->_loadCustomerById($this->getRequest()->getParam('customer_id')); + + /* @var $collection Mage_Customer_Model_Resource_Address_Collection */ + $collection = $customer->getAddressesCollection(); + + $this->_applyCollectionModifiers($collection); + return $collection; + } + + /** + * Get array with default addresses information if possible + * + * @param Mage_Customer_Model_Address $address + * @return array + */ + protected function _getDefaultAddressesInfo(Mage_Customer_Model_Address $address) + { + return array( + 'is_default_billing' => (int)$this->_isDefaultBillingAddress($address), + 'is_default_shipping' => (int)$this->_isDefaultShippingAddress($address) + ); + } + + /** + * Update specified stock item + * + * @param array $data + * @throws Mage_Api2_Exception + */ + protected function _update(array $data) + { + /* @var $address Mage_Customer_Model_Address */ + $address = $this->_loadCustomerAddressById($this->getRequest()->getParam('id')); + $validator = $this->_getValidator(); + + $data = $validator->filter($data); + if (!$validator->isValidData($data, true) + || !$validator->isValidDataForChangeAssociationWithCountry($address, $data)) { + foreach ($validator->getErrors() as $error) { + $this->_error($error, Mage_Api2_Model_Server::HTTP_BAD_REQUEST); + } + $this->_critical(self::RESOURCE_DATA_PRE_VALIDATION_ERROR); + } + + $data['region'] = isset($data['region']) ? $this->_getRegionIdByNameOrCode($data['region']) : null; + $address->addData($data); + + try { + $address->save(); + } catch (Mage_Core_Exception $e) { + $this->_error($e->getMessage(), Mage_Api2_Model_Server::HTTP_INTERNAL_ERROR); + } catch (Exception $e) { + $this->_critical(self::RESOURCE_INTERNAL_ERROR); + } + } + + /** + * Delete customer + */ + protected function _delete() + { + /* @var $address Mage_Customer_Model_Address */ + $address = $this->_loadCustomerAddressById($this->getRequest()->getParam('id')); + + if ($this->_isDefaultBillingAddress($address) || $this->_isDefaultShippingAddress($address)) { + $this->_critical( + 'Address is default for customer so is not allowed to be deleted', + Mage_Api2_Model_Server::HTTP_BAD_REQUEST + ); + } + try { + $address->delete(); + } catch (Mage_Core_Exception $e) { + $this->_critical($e->getMessage(), Mage_Api2_Model_Server::HTTP_INTERNAL_ERROR); + } catch (Exception $e) { + $this->_critical(self::RESOURCE_INTERNAL_ERROR); + } + } +} diff --git a/app/code/core/Mage/Customer/Model/Api2/Customer/Address/Rest/Admin/V1.php b/app/code/core/Mage/Customer/Model/Api2/Customer/Address/Rest/Admin/V1.php new file mode 100644 index 0000000000..cefed11a9b --- /dev/null +++ b/app/code/core/Mage/Customer/Model/Api2/Customer/Address/Rest/Admin/V1.php @@ -0,0 +1,36 @@ + + */ +class Mage_Customer_Model_Api2_Customer_Address_Rest_Admin_V1 extends Mage_Customer_Model_Api2_Customer_Address_Rest +{ +} diff --git a/app/code/core/Mage/Customer/Model/Api2/Customer/Address/Rest/Customer/V1.php b/app/code/core/Mage/Customer/Model/Api2/Customer/Address/Rest/Customer/V1.php new file mode 100644 index 0000000000..6b03257911 --- /dev/null +++ b/app/code/core/Mage/Customer/Model/Api2/Customer/Address/Rest/Customer/V1.php @@ -0,0 +1,71 @@ + + */ +class Mage_Customer_Model_Api2_Customer_Address_Rest_Customer_V1 extends Mage_Customer_Model_Api2_Customer_Address_Rest +{ + /** + * Load customer address by id + * + * @param int $id + * @throws Mage_Api2_Exception + * @return Mage_Customer_Model_Address + */ + protected function _loadCustomerAddressById($id) + { + /* @var $customerAddress Mage_Customer_Model_Address */ + $customerAddress = parent::_loadCustomerAddressById($id); + // check owner + if ($this->getApiUser()->getUserId() != $customerAddress->getCustomerId()) { + $this->_critical(self::RESOURCE_NOT_FOUND); + } + return $customerAddress; + } + + /** + * Load customer by id + * + * @param int $id + * @throws Mage_Api2_Exception + * @return Mage_Customer_Model_Customer + */ + protected function _loadCustomerById($id) + { + /* @var $customer Mage_Customer_Model_Customer */ + $customer = parent::_loadCustomerById($id); + // check customer accaunt owner + if ($this->getApiUser()->getUserId() != $customer->getId()) { + $this->_critical(self::RESOURCE_NOT_FOUND); + } + return $customer; + } +} diff --git a/app/code/core/Mage/Customer/Model/Api2/Customer/Address/Validator.php b/app/code/core/Mage/Customer/Model/Api2/Customer/Address/Validator.php new file mode 100644 index 0000000000..c86db50d53 --- /dev/null +++ b/app/code/core/Mage/Customer/Model/Api2/Customer/Address/Validator.php @@ -0,0 +1,187 @@ + + */ +class Mage_Customer_Model_Api2_Customer_Address_Validator extends Mage_Api2_Model_Resource_Validator_Eav +{ + /** + * Separator for multistreet + */ + const STREET_SEPARATOR = '; '; + + /** + * Filter request data. + * + * @param array $data + * @return array Filtered data + */ + public function filter(array $data) + { + $data = parent::filter($data); + + // If the array contains more than two elements, then combine the extra elements in a string + if (isset($data['street']) && is_array($data['street']) && count($data['street']) > 2) { + $data['street'][1] .= self::STREET_SEPARATOR + . implode(self::STREET_SEPARATOR, array_slice($data['street'], 2)); + $filteredData['street'] = array_slice($data['street'], 0, 2); + } + + return $data; + } + + /** + * Validate data for create association with the country + * + * @param array $data + * @return bool + */ + public function isValidDataForCreateAssociationWithCountry(array $data) + { + // Check the country + $country = $this->_checkCountry($data); + if (false == $country) { + // break the validation if the country is not valid + return false; + } + + // Check the region + return $this->_checkRegion($data, $country); + } + + /** + * Validate data for change association with the country + * + * @param Mage_Customer_Model_Address $address + * @param array $data + * @return bool + */ + public function isValidDataForChangeAssociationWithCountry(Mage_Customer_Model_Address $address, array $data) + { + if (!isset($data['country_id']) && !isset($data['region'])) { + return true; + } + + // Check the country + if (array_key_exists('country_id', $data)) { + $country = $this->_checkCountry($data); + if (false == $country) { + // break the validation if the country is not valid + return false; + } + } else { + // if the country is not passed load the current country + $country = $address->getCountryModel(); + } + + // Check the region + return $this->_checkRegion($data, $country); + } + + /** + * Check country + * + * @param array $data + * @return bool|Mage_Directory_Model_Country + */ + protected function _checkCountry($data) + { + if (!array_key_exists('country_id', $data)) { + $this->_addError('"Country" is required.'); + return false; + } + + if (!is_string($data['country_id'])) { + $this->_addError('Invalid country identifier type.'); + return false; + } + + if ('' == trim($data['country_id'])) { + $this->_addError('"Country" is required.'); + return false; + } + + $validator = new Zend_Validate_StringLength(array('min' => 2, 'max' => 3)); + if (!$validator->isValid($data['country_id'])) { + $this->_addError("Country is not between '2' and '3' inclusively."); + return false; + } + + /* @var $country Mage_Directory_Model_Country */ + $country = Mage::getModel('directory/country')->loadByCode($data['country_id']); + if (!$country->getId()) { + $this->_addError('Country does not exist.'); + return false; + } + + return $country; + } + + /** + * Check region + * + * @param array $data + * @param Mage_Directory_Model_Country $country + * @return bool + */ + protected function _checkRegion($data, Mage_Directory_Model_Country $country) + { + /* @var $regions Mage_Directory_Model_Resource_Region_Collection */ + $regions = $country->getRegions(); + // Is it the country with predifined regions? + if ($regions->count()) { + if (!array_key_exists('region', $data) || empty($data['region'])) { + $this->_addError('"State/Province" is required.'); + return false; + } + + if (!is_string($data['region'])) { + $this->_addError('Invalid "State/Province" type.'); + return false; + } + + $count = $regions->addFieldToFilter(array('default_name', 'code'), array($data['region'], $data['region'])) + ->clear() + ->count(); + if (!$count) { + $this->_addError('State/Province does not exist.'); + return false; + } + } else { + if (array_key_exists('region', $data) && !is_string($data['region'])) { + $this->_addError('Invalid "State/Province" type.'); + return false; + } + } + + return true; + } +} diff --git a/app/code/core/Mage/Customer/Model/Api2/Customer/Rest.php b/app/code/core/Mage/Customer/Model/Api2/Customer/Rest.php new file mode 100644 index 0000000000..772bb38c66 --- /dev/null +++ b/app/code/core/Mage/Customer/Model/Api2/Customer/Rest.php @@ -0,0 +1,166 @@ + + */ +abstract class Mage_Customer_Model_Api2_Customer_Rest extends Mage_Customer_Model_Api2_Customer +{ + /** + * Create customer + * + * @param array $data + * @return string + */ + protected function _create(array $data) + { + /** @var $validator Mage_Api2_Model_Resource_Validator_Eav */ + $validator = Mage::getResourceModel('api2/validator_eav', array( + 'resource' => $this, + 'operation' => self::OPERATION_CREATE + )); + + $data = $validator->filter($data); + if (!$validator->isValidData($data)) { + foreach ($validator->getErrors() as $error) { + $this->_error($error, Mage_Api2_Model_Server::HTTP_BAD_REQUEST); + } + $this->_critical(self::RESOURCE_DATA_PRE_VALIDATION_ERROR); + } + + /** @var $customer Mage_Customer_Model_Customer */ + $customer = Mage::getModel('customer/customer'); + $customer->setData($data); + + try { + $customer->save(); + } catch (Mage_Core_Exception $e) { + $this->_error($e->getMessage(), Mage_Api2_Model_Server::HTTP_INTERNAL_ERROR); + } catch (Exception $e) { + $this->_critical(self::RESOURCE_INTERNAL_ERROR); + } + + return $this->_getLocation($customer); + } + + /** + * Retrieve information about customer + * + * @throws Mage_Api2_Exception + * @return array + */ + protected function _retrieve() + { + /** @var $customer Mage_Customer_Model_Customer */ + $customer = $this->_loadCustomerById($this->getRequest()->getParam('id')); + return $customer->getData(); + } + + /** + * Get customers list + * + * @return array + */ + protected function _retrieveCollection() + { + $data = $this->_getCollectionForRetrieve()->load()->toArray(); + return isset($data['items']) ? $data['items'] : $data; + } + + /** + * Update customer + * + * @param array $data + * @throws Mage_Api2_Exception + */ + protected function _update(array $data) + { + /** @var $customer Mage_Customer_Model_Customer */ + $customer = $this->_loadCustomerById($this->getRequest()->getParam('id')); + + /** @var $validator Mage_Api2_Model_Resource_Validator_Eav */ + $validator = Mage::getResourceModel('api2/validator_eav', array( + 'resource' => $this, + 'operation' => self::OPERATION_UPDATE + )); + + $data = $validator->filter($data); + if (!$validator->isValidData($data)) { + foreach ($validator->getErrors() as $error) { + $this->_error($error, Mage_Api2_Model_Server::HTTP_BAD_REQUEST); + } + $this->_critical(self::RESOURCE_DATA_PRE_VALIDATION_ERROR); + } + + $customer->addData($data); + + try { + $customer->save(); + } catch (Mage_Core_Exception $e) { + $this->_error($e->getMessage(), Mage_Api2_Model_Server::HTTP_INTERNAL_ERROR); + } catch (Exception $e) { + $this->_critical(self::RESOURCE_INTERNAL_ERROR); + } + } + + /** + * Load customer by id + * + * @param int $id + * @throws Mage_Api2_Exception + * @return Mage_Customer_Model_Customer + */ + protected function _loadCustomerById($id) + { + /** @var $customer Mage_Customer_Model_Customer */ + $customer = Mage::getModel('customer/customer')->load($id); + if (!$customer->getId()) { + $this->_critical(self::RESOURCE_NOT_FOUND); + } + return $customer; + } + + /** + * Retrieve collection instances + * + * @return Mage_Customer_Model_Resource_Customer_Collection + */ + protected function _getCollectionForRetrieve() + { + /** @var $collection Mage_Customer_Model_Resource_Customer_Collection */ + $collection = Mage::getResourceModel('customer/customer_collection'); + $collection->addAttributeToSelect(array_keys( + $this->getAvailableAttributes($this->getUserType(), Mage_Api2_Model_Resource::OPERATION_ATTRIBUTE_READ) + )); + + $this->_applyCollectionModifiers($collection); + return $collection; + } +} diff --git a/app/code/core/Mage/Customer/Model/Api2/Customer/Rest/Admin/V1.php b/app/code/core/Mage/Customer/Model/Api2/Customer/Rest/Admin/V1.php new file mode 100644 index 0000000000..8f408e8778 --- /dev/null +++ b/app/code/core/Mage/Customer/Model/Api2/Customer/Rest/Admin/V1.php @@ -0,0 +1,75 @@ + + */ +class Mage_Customer_Model_Api2_Customer_Rest_Admin_V1 extends Mage_Customer_Model_Api2_Customer_Rest +{ + /** + * Retrieve information about customer + * Add last logged in datetime + * + * @throws Mage_Api2_Exception + * @return array + */ + protected function _retrieve() + { + /** @var $log Mage_Log_Model_Customer */ + $log = Mage::getModel('log/customer'); + $log->loadByCustomer($this->getRequest()->getParam('id')); + + $data = parent::_retrieve(); + $data['is_confirmed'] = (int) !(isset($data['confirmation']) && $data['confirmation']); + + $lastLoginAt = $log->getLoginAt(); + if (null !== $lastLoginAt) { + $data['last_logged_in'] = $lastLoginAt; + } + return $data; + } + + /** + * Delete customer + */ + protected function _delete() + { + /** @var $customer Mage_Customer_Model_Customer */ + $customer = parent::_loadCustomerById($this->getRequest()->getParam('id')); + + try { + $customer->delete(); + } catch (Mage_Core_Exception $e) { + $this->_critical($e->getMessage(), Mage_Api2_Model_Server::HTTP_INTERNAL_ERROR); + } catch (Exception $e) { + $this->_critical(self::RESOURCE_INTERNAL_ERROR); + } + } +} diff --git a/app/code/core/Mage/Customer/Model/Api2/Customer/Rest/Customer/V1.php b/app/code/core/Mage/Customer/Model/Api2/Customer/Rest/Customer/V1.php new file mode 100644 index 0000000000..a4e5c97058 --- /dev/null +++ b/app/code/core/Mage/Customer/Model/Api2/Customer/Rest/Customer/V1.php @@ -0,0 +1,97 @@ + + */ +class Mage_Customer_Model_Api2_Customer_Rest_Customer_V1 extends Mage_Customer_Model_Api2_Customer_Rest +{ + /** + * Is customer has rights to retrieve/update customer item + * + * @param int $customerId + * @throws Mage_Api2_Exception + * @return bool + */ + protected function _isOwner($customerId) + { + if ($this->getApiUser()->getUserId() !== $customerId) { + $this->_critical(self::RESOURCE_NOT_FOUND); + } + return true; + } + + /** + * Retrieve information about customer + * + * @throws Mage_Api2_Exception + * @return array + */ + protected function _retrieve() + { + if ($this->_isOwner($this->getRequest()->getParam('id'))) { + return parent::_retrieve(); + } + } + + /** + * Retrieve collection with only current customer instance + * + * @return Mage_Customer_Model_Resource_Customer_Collection + */ + protected function _getCollectionForRetrieve() + { + return parent::_getCollectionForRetrieve()->addAttributeToFilter('entity_id', $this->getApiUser()->getUserId()); + } + + /** + * Update customer + * + * @param array $data + * @throws Mage_Api2_Exception + */ + protected function _update(array $data) + { + if ($this->_isOwner($this->getRequest()->getParam('id'))) { + parent::_update($data); + } + } + + /** + * Update customers + * + * @param array $data + * @throws Mage_Api2_Exception + */ + protected function _multiUpdate(array $data) + { + $this->_critical(self::RESOURCE_METHOD_NOT_ALLOWED, Mage_Api2_Model_Server::HTTP_FORBIDDEN); + } +} diff --git a/app/code/core/Mage/Customer/Model/Session.php b/app/code/core/Mage/Customer/Model/Session.php index e89d4b6961..4610c0797f 100644 --- a/app/code/core/Mage/Customer/Model/Session.php +++ b/app/code/core/Mage/Customer/Model/Session.php @@ -260,19 +260,25 @@ public function logout() * Authenticate controller action by login customer * * @param Mage_Core_Controller_Varien_Action $action + * @param bool $loginUrl * @return bool */ public function authenticate(Mage_Core_Controller_Varien_Action $action, $loginUrl = null) { - if (!$this->isLoggedIn()) { - $this->setBeforeAuthUrl(Mage::getUrl('*/*/*', array('_current'=>true))); - if (is_null($loginUrl)) { - $loginUrl = Mage::helper('customer')->getLoginUrl(); - } + if ($this->isLoggedIn()) { + return true; + } + + $this->setBeforeAuthUrl(Mage::getUrl('*/*/*', array('_current' => true))); + if (isset($loginUrl)) { $action->getResponse()->setRedirect($loginUrl); - return false; + } else { + $action->setRedirectWithCookieCheck(Mage_Customer_Helper_Data::ROUTE_ACCOUNT_LOGIN, + Mage::helper('customer')->getLoginUrlParams() + ); } - return true; + + return false; } /** diff --git a/app/code/core/Mage/Customer/controllers/AccountController.php b/app/code/core/Mage/Customer/controllers/AccountController.php index f6b8d9192c..98bac6a509 100644 --- a/app/code/core/Mage/Customer/controllers/AccountController.php +++ b/app/code/core/Mage/Customer/controllers/AccountController.php @@ -183,7 +183,6 @@ protected function _loginPostRedirect() $session = $this->_getSession(); if (!$session->getBeforeAuthUrl() || $session->getBeforeAuthUrl() == Mage::getBaseUrl()) { - // Set default URL to redirect customer to $session->setBeforeAuthUrl(Mage::helper('customer')->getAccountUrl()); // Redirect customer to the last page visited after logging in diff --git a/app/code/core/Mage/Customer/etc/api2.xml b/app/code/core/Mage/Customer/etc/api2.xml new file mode 100644 index 0000000000..869d2193e5 --- /dev/null +++ b/app/code/core/Mage/Customer/etc/api2.xml @@ -0,0 +1,213 @@ + + + + + + + Customer + 130 + + + + + customer + 90 + customer/api2_customer + customer/customer + Customer + + + 1 + 1 + 1 + + + 1 + 1 + + + + Customer ID + Last Logged In + First Name + Last Name + Email + Associate to Website + Created From + Group + Is Confirmed + Disable automatic group change based on VAT ID + + + + + 1 + 1 + 1 + 1 + + + + + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + + + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + + + + + + + customer/customer + customer/form + + adminhtml_customer + + + + customer/customer + customer/form + + customer_account_edit + + + + + + + /customers/:id + entity + + + /customers + collection + + + 1 + + + customer + 110 + customer/api2_customer_address + customer/address + Customer Address + + + 1 + 1 + 1 + 1 + + + 1 + 1 + 1 + 1 + + + + Customer Address ID + Is Default Billing Address + Is Default Shipping Address + + + + + 1 + 1 + 1 + 1 + 1 + + + 1 + 1 + 1 + 1 + 1 + 1 + + + + + 1 + + + 1 + 1 + + + + + + + customer/address + customer/form + + adminhtml_customer_address + + + + customer/address + customer/form + + customer_address_edit + + + + + + + /customers/addresses/:id + entity + + + /customers/:customer_id/addresses + collection + + + 1 + + + + diff --git a/app/code/core/Mage/Customer/etc/config.xml b/app/code/core/Mage/Customer/etc/config.xml index edb5dc4374..590af35b42 100644 --- a/app/code/core/Mage/Customer/etc/config.xml +++ b/app/code/core/Mage/Customer/etc/config.xml @@ -510,6 +510,7 @@ customer_create_account_email_template customer_create_account_email_confirmation_template customer_create_account_email_confirmed_template + 0 1 diff --git a/app/code/core/Mage/Customer/etc/system.xml b/app/code/core/Mage/Customer/etc/system.xml index 766a8ed384..acb3732cf9 100644 --- a/app/code/core/Mage/Customer/etc/system.xml +++ b/app/code/core/Mage/Customer/etc/system.xml @@ -88,8 +88,9 @@ 1 1 - + + To show VAT number on frontend, set Show VAT Number on Frontend option to Yes. select adminhtml/system_config_source_yesno 10 @@ -175,6 +176,15 @@ 0 0 + + + select + adminhtml/system_config_source_yesno + 58 + 1 + 1 + 0 + text @@ -458,18 +468,8 @@ - - - - adminhtml/system_config_backend_customer_groupAutoAssign - - - - - adminhtml/system_config_backend_customer_groupAutoAssign - Validate VAT Number @@ -477,7 +477,7 @@ 28 1 1 - 1 + 0 diff --git a/app/code/core/Mage/Customer/sql/customer_setup/upgrade-1.6.1.0-1.6.2.0.php b/app/code/core/Mage/Customer/sql/customer_setup/upgrade-1.6.1.0-1.6.2.0.php index bb6260a2e9..44adab138d 100644 --- a/app/code/core/Mage/Customer/sql/customer_setup/upgrade-1.6.1.0-1.6.2.0.php +++ b/app/code/core/Mage/Customer/sql/customer_setup/upgrade-1.6.1.0-1.6.2.0.php @@ -31,7 +31,7 @@ $installer->addAttribute('customer', $disableAGCAttributeCode, array( 'type' => 'static', - 'label' => 'Disable automatic group change based on VAT ID', + 'label' => 'Disable Automatic Group Change Based on VAT ID', 'input' => 'boolean', 'backend' => 'customer/attribute_backend_data_boolean', 'position' => 28, diff --git a/app/code/core/Mage/Dataflow/Model/Convert/Adapter/Io.php b/app/code/core/Mage/Dataflow/Model/Convert/Adapter/Io.php index f414472516..07e3db74f8 100644 --- a/app/code/core/Mage/Dataflow/Model/Convert/Adapter/Io.php +++ b/app/code/core/Mage/Dataflow/Model/Convert/Adapter/Io.php @@ -43,7 +43,7 @@ public function getResource($forWrite = false) { if (!$this->_resource) { $type = $this->getVar('type', 'file'); - $className = 'Varien_Io_'.ucwords($type); + $className = 'Varien_Io_' . ucwords($type); $this->_resource = new $className(); $isError = false; diff --git a/app/code/core/Mage/Directory/etc/system.xml b/app/code/core/Mage/Directory/etc/system.xml index c2fc6717db..bc37086380 100644 --- a/app/code/core/Mage/Directory/etc/system.xml +++ b/app/code/core/Mage/Directory/etc/system.xml @@ -114,6 +114,7 @@ text + validate-email 5 1 1 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 aedbed15b7..6156e3bfd0 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 @@ -159,7 +159,7 @@ public function getLinkData() foreach ($links as $item) { $tmpLinkItem = array( 'link_id' => $item->getId(), - 'title' => $item->getTitle(), + 'title' => $this->escapeHtml($item->getTitle()), 'price' => $this->getCanReadPrice() ? $this->getPriceValue($item->getPrice()) : '', 'number_of_downloads' => $item->getNumberOfDownloads(), 'is_shareable' => $item->getIsShareable(), 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 f6e1c41589..94e9040e5e 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 @@ -31,7 +31,8 @@ * @package Mage_Downloadable * @author Magento Core Team */ -class Mage_Downloadable_Block_Adminhtml_Catalog_Product_Edit_Tab_Downloadable_Samples extends Mage_Adminhtml_Block_Widget +class Mage_Downloadable_Block_Adminhtml_Catalog_Product_Edit_Tab_Downloadable_Samples + extends Mage_Adminhtml_Block_Widget { /** * Class constructor @@ -92,7 +93,7 @@ public function getSampleData() foreach ($samples as $item) { $tmpSampleItem = array( 'sample_id' => $item->getId(), - 'title' => $item->getTitle(), + 'title' => $this->escapeHtml($item->getTitle()), 'sample_url' => $item->getSampleUrl(), 'sample_type' => $item->getSampleType(), 'sort_order' => $item->getSortOrder(), @@ -176,7 +177,9 @@ public function getUploadButtonHtml() */ public function getConfigJson() { - $this->getConfig()->setUrl(Mage::getModel('adminhtml/url')->addSessionParam()->getUrl('*/downloadable_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/controllers/DownloadController.php b/app/code/core/Mage/Downloadable/controllers/DownloadController.php index cef5be6c47..a4f019070a 100644 --- a/app/code/core/Mage/Downloadable/controllers/DownloadController.php +++ b/app/code/core/Mage/Downloadable/controllers/DownloadController.php @@ -112,7 +112,7 @@ public function sampleAction() $this->_processDownload($resource, $resourceType); exit(0); } catch (Mage_Core_Exception $e) { - $this->_getSession()->addError(Mage::helper('downloadable')->__('An error occurred while getting requested content. Please contact the store owner.')); + $this->_getSession()->addError(Mage::helper('downloadable')->__('Sorry, there was an error getting requested content. Please contact the store owner.')); } } return $this->_redirectReferer(); @@ -142,7 +142,7 @@ public function linkSampleAction() $this->_processDownload($resource, $resourceType); exit(0); } catch (Mage_Core_Exception $e) { - $this->_getCustomerSession()->addError(Mage::helper('downloadable')->__('An error occurred while getting requested content. Please contact the store owner.')); + $this->_getCustomerSession()->addError(Mage::helper('downloadable')->__('Sorry, there was an error getting requested content. Please contact the store owner.')); } } return $this->_redirectReferer(); diff --git a/app/code/core/Mage/Downloadable/etc/config.xml b/app/code/core/Mage/Downloadable/etc/config.xml index 96409de891..bb412c2843 100644 --- a/app/code/core/Mage/Downloadable/etc/config.xml +++ b/app/code/core/Mage/Downloadable/etc/config.xml @@ -252,6 +252,7 @@ /downloadable/customer/ + /downloadable/download/ diff --git a/app/code/core/Mage/Eav/Model/Config.php b/app/code/core/Mage/Eav/Model/Config.php index fb51b52a45..80c1278c82 100644 --- a/app/code/core/Mage/Eav/Model/Config.php +++ b/app/code/core/Mage/Eav/Model/Config.php @@ -118,7 +118,7 @@ public function clear() } /** - * Get object by idetifier + * Get object by identifier * * @param mixed $id * @return mixed @@ -373,7 +373,7 @@ protected function _initAttributes($entityType) * * @param mixed $entityType * @param mixed $code - * @return Mage_Eav_Model_Entity_Attribute_Abstract + * @return Mage_Eav_Model_Entity_Attribute_Abstract|false */ public function getAttribute($entityType, $code) { @@ -406,7 +406,6 @@ public function getAttribute($entityType, $code) return $attribute; } - $attribute = false; if (isset($this->_attributeData[$entityTypeCode][$code])) { $data = $this->_attributeData[$entityTypeCode][$code]; unset($this->_attributeData[$entityTypeCode][$code]); @@ -505,7 +504,9 @@ public function preloadAttributes($entityType, $attributes) $this->_preloadedAttributes[$entityTypeCode] = $attributes; } else { $attributes = array_diff($attributes, $this->_preloadedAttributes[$entityTypeCode]); - $this->_preloadedAttributes[$entityTypeCode] = array_merge($this->_preloadedAttributes[$entityTypeCode], $attributes); + $this->_preloadedAttributes[$entityTypeCode] = array_merge($this->_preloadedAttributes[$entityTypeCode], + $attributes + ); } if (empty($attributes)) { diff --git a/app/code/core/Mage/Eav/Model/Entity/Attribute/Frontend/Abstract.php b/app/code/core/Mage/Eav/Model/Entity/Attribute/Frontend/Abstract.php index 7afb91e2d3..77b55ebbf3 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Attribute/Frontend/Abstract.php +++ b/app/code/core/Mage/Eav/Model/Entity/Attribute/Frontend/Abstract.php @@ -134,7 +134,7 @@ public function isVisible() } /** - * Retreive frontend class + * Retrieve frontend class * * @return string */ diff --git a/app/code/core/Mage/Eav/Model/Entity/Collection/Abstract.php b/app/code/core/Mage/Eav/Model/Entity/Collection/Abstract.php index 88b37d1031..bef9e0d54f 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Collection/Abstract.php +++ b/app/code/core/Mage/Eav/Model/Entity/Collection/Abstract.php @@ -430,7 +430,8 @@ public function addAttributeToSelect($attribute, $joinType = false) if ($joinType !== false && !$this->getEntity()->getAttribute($attribute)->isStatic()) { $this->_addAttributeJoin($attribute, $joinType); } elseif ('*' === $attribute) { - $attributes = $this->getEntity() + $entity = clone $this->getEntity(); + $attributes = $entity ->loadAllAttributes() ->getAttributesByCode(); foreach ($attributes as $attrCode=>$attr) { diff --git a/app/code/core/Mage/GoogleCheckout/Block/Adminhtml/Shipping/Merchant.php b/app/code/core/Mage/GoogleCheckout/Block/Adminhtml/Shipping/Merchant.php index 0a2d9b310f..12c42b515e 100644 --- a/app/code/core/Mage/GoogleCheckout/Block/Adminhtml/Shipping/Merchant.php +++ b/app/code/core/Mage/GoogleCheckout/Block/Adminhtml/Shipping/Merchant.php @@ -53,21 +53,27 @@ protected function _getElementHtml(Varien_Data_Form_Element_Abstract $element) return $html; } - protected function _getRowTemplateHtml($i=0) + /** + * Retrieve html template for shipping method row + * + * @param int $rowIndex + * @return string + */ + protected function _getRowTemplateHtml($rowIndex = 0) { $html = '
  • '; $html .= '_getDisabled() . '/> '; + . $this->_getValue('price/' . $rowIndex) . '" ' . $this->_getDisabled() . '/> '; $html .= $this->_getRemoveRowButtonHtml(); $html .= '
  • '; @@ -152,7 +158,6 @@ protected function _getAddRowButtonHtml($container, $template, $title='Add') ->setType('button') ->setClass('add ' . $this->_getDisabled()) ->setLabel($this->__($title)) - //$this->__('Add') ->setOnClick("Element.insert($('" . $container . "'), {bottom: $('" . $template . "').innerHTML})") ->setDisabled($this->_getDisabled()) ->toHtml(); @@ -167,7 +172,6 @@ protected function _getRemoveRowButtonHtml($selector = 'li', $title = 'Remove') ->setType('button') ->setClass('delete v-middle ' . $this->_getDisabled()) ->setLabel($this->__($title)) - //$this->__('Remove') ->setOnClick("Element.remove($(this).up('" . $selector . "'))") ->setDisabled($this->_getDisabled()) ->toHtml(); 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 b087b24fd7..bd06cd4958 100644 --- a/app/code/core/Mage/GoogleCheckout/Model/Api/Xml/Checkout.php +++ b/app/code/core/Mage/GoogleCheckout/Model/Api/Xml/Checkout.php @@ -129,12 +129,17 @@ protected function _getItemsXml() $weight = (float) $item->getWeight(); $weightUnit = self::ITEM_WEIGHT_UNIT; + $unitPrice = $item->getBaseCalculationPrice(); + if (Mage::helper('weee')->includeInSubtotal()) { + $unitPrice += $item->getBaseWeeeTaxAppliedAmount(); + } + $xml .= << getSku()}]]> getName()}]]> getDescription()}]]> - {$item->getBaseCalculationPrice()} + {$unitPrice} {$item->getQty()} {$taxClass} diff --git a/app/code/core/Mage/GoogleCheckout/etc/config.xml b/app/code/core/Mage/GoogleCheckout/etc/config.xml index efbe5f33a4..88f487d08e 100644 --- a/app/code/core/Mage/GoogleCheckout/etc/config.xml +++ b/app/code/core/Mage/GoogleCheckout/etc/config.xml @@ -82,6 +82,7 @@ /googlecheckout/redirect/ /googlecheckout/api/beacon/ + /googlecheckout/api/ diff --git a/app/code/core/Mage/GoogleCheckout/etc/system.xml b/app/code/core/Mage/GoogleCheckout/etc/system.xml index 8e04efaf45..a82f6d58d0 100644 --- a/app/code/core/Mage/GoogleCheckout/etc/system.xml +++ b/app/code/core/Mage/GoogleCheckout/etc/system.xml @@ -79,7 +79,7 @@ 1 1 0 - Required for live Google Checkout transactions. + Required for live Google Checkout transactions. Make sure that this option corresponds to Use Secure URLs in Frontend ("Web" > "Secure"). diff --git a/app/code/core/Mage/ImportExport/Helper/Data.php b/app/code/core/Mage/ImportExport/Helper/Data.php index e75a9455d4..f714b36fd4 100644 --- a/app/code/core/Mage/ImportExport/Helper/Data.php +++ b/app/code/core/Mage/ImportExport/Helper/Data.php @@ -33,7 +33,11 @@ */ class Mage_ImportExport_Helper_Data extends Mage_Core_Helper_Data { + /** + * XML path for config data + */ const XML_PATH_EXPORT_LOCAL_VALID_PATH = 'general/file/importexport_local_valid_paths'; + const XML_PATH_BUNCH_SIZE = 'general/file/bunch_size'; /** * Maximum size of uploaded files. @@ -55,4 +59,14 @@ public function getLocalValidPaths() $paths = Mage::getStoreConfig(self::XML_PATH_EXPORT_LOCAL_VALID_PATH); return $paths; } + + /** + * Retrieve size of bunch (how much products should be involved in one import iteration) + * + * @return int + */ + public function getBunchSize() + { + return (int)Mage::getStoreConfig(self::XML_PATH_BUNCH_SIZE); + } } diff --git a/app/code/core/Mage/ImportExport/Model/Export/Entity/Product.php b/app/code/core/Mage/ImportExport/Model/Export/Entity/Product.php index d1e6fc0e11..e7a3a2b83b 100644 --- a/app/code/core/Mage/ImportExport/Model/Export/Entity/Product.php +++ b/app/code/core/Mage/ImportExport/Model/Export/Entity/Product.php @@ -50,6 +50,7 @@ class Mage_ImportExport_Model_Export_Entity_Product extends Mage_ImportExport_Mo const COL_ATTR_SET = '_attribute_set'; const COL_TYPE = '_type'; const COL_CATEGORY = '_category'; + const COL_ROOT_CATEGORY = '_root_category'; const COL_SKU = 'sku'; /** @@ -66,6 +67,13 @@ class Mage_ImportExport_Model_Export_Entity_Product extends Mage_ImportExport_Mo */ protected $_categories = array(); + /** + * Root category names for each category + * + * @var array + */ + protected $_rootCategories = array(); + /** * Attributes with index (not label) value. * @@ -158,13 +166,17 @@ protected function _initCategories() foreach ($collection as $category) { $structure = preg_split('#/+#', $category->getPath()); $pathSize = count($structure); - if ($pathSize > 2) { + if ($pathSize > 1) { $path = array(); - for ($i = 2; $i < $pathSize; $i++) { + for ($i = 1; $i < $pathSize; $i++) { $path[] = $collection->getItemById($structure[$i])->getName(); } - $this->_categories[$category->getId()] = implode('/', $path); + $this->_rootCategories[$category->getId()] = array_shift($path); + if ($pathSize > 2) { + $this->_categories[$category->getId()] = implode('/', $path); + } } + } return $this; } @@ -509,6 +521,29 @@ protected function _prepareConfigurableProductPrice(array $productIds) return $configurablePrice; } + /** + * Update data row with information about categories. Return true, if data row was updated + * + * @param array $dataRow + * @param array $rowCategories + * @param int $productId + * @return bool + */ + protected function _updateDataWithCategoryColumns(&$dataRow, &$rowCategories, $productId) + { + if (!isset($rowCategories[$productId])) { + return false; + } + + $categoryId = array_shift($rowCategories[$productId]); + $dataRow[self::COL_ROOT_CATEGORY] = $this->_rootCategories[$categoryId]; + if (isset($this->_categories[$categoryId])) { + $dataRow[self::COL_CATEGORY] = $this->_categories[$categoryId]; + } + + return true; + } + /** * Export process. * @@ -642,9 +677,10 @@ public function export() break; } - // remove root categories - foreach ($rowCategories as $productId => &$categories) { - $categories = array_intersect($categories, array_keys($this->_categories)); + // remove unused categories + $allCategoriesIds = array_merge(array_keys($this->_categories), array_keys($this->_rootCategories)); + foreach ($rowCategories as &$categories) { + $categories = array_intersect($categories, $allCategoriesIds); } // prepare catalog inventory information @@ -789,7 +825,7 @@ public function export() $headerCols = array_merge( array( self::COL_SKU, self::COL_STORE, self::COL_ATTR_SET, - self::COL_TYPE, self::COL_CATEGORY, '_product_websites' + self::COL_TYPE, self::COL_CATEGORY, self::COL_ROOT_CATEGORY, '_product_websites' ), $validAttrCodes, reset($stockItemRows) ? array_keys(end($stockItemRows)) : array(), @@ -836,9 +872,8 @@ public function export() $dataRow[self::COL_STORE] = null; $dataRow += $stockItemRows[$productId]; } - if ($rowCategories[$productId]) { - $dataRow[self::COL_CATEGORY] = $this->_categories[array_shift($rowCategories[$productId])]; - } + + $this->_updateDataWithCategoryColumns($dataRow, $rowCategories, $productId); if ($rowWebsites[$productId]) { $dataRow['_product_websites'] = $this->_websiteIdToCode[array_shift($rowWebsites[$productId])]; } @@ -917,9 +952,7 @@ public function export() for ($i = 0; $i < $additionalRowsCount; $i++) { $dataRow = array(); - if ($rowCategories[$productId]) { - $dataRow[self::COL_CATEGORY] = $this->_categories[array_shift($rowCategories[$productId])]; - } + $this->_updateDataWithCategoryColumns($dataRow, $rowCategories, $productId); if ($rowWebsites[$productId]) { $dataRow['_product_websites'] = $this ->_websiteIdToCode[array_shift($rowWebsites[$productId])]; diff --git a/app/code/core/Mage/ImportExport/Model/Import/Entity/Abstract.php b/app/code/core/Mage/ImportExport/Model/Import/Entity/Abstract.php index 6aede50b19..d81bb355b3 100644 --- a/app/code/core/Mage/ImportExport/Model/Import/Entity/Abstract.php +++ b/app/code/core/Mage/ImportExport/Model/Import/Entity/Abstract.php @@ -272,6 +272,7 @@ protected function _saveValidatedBunches() $startNewBunch = false; $nextRowBackup = array(); $maxDataSize = Mage::getResourceHelper('importexport')->getMaxDataSize(); + $bunchSize = Mage::helper('importexport')->getBunchSize(); $source->rewind(); $this->_dataSourceModel->cleanBunches(); @@ -295,9 +296,11 @@ protected function _saveValidatedBunches() if ($this->validateRow($rowData, $source->key())) { // add row to bunch for save $rowData = $this->_prepareRowForDb($rowData); - $rowSize = strlen(serialize($rowData)); + $rowSize = strlen(Mage::helper('core')->jsonEncode($rowData)); - if (($productDataSize + $rowSize) >= $maxDataSize) { // check bunch size + $isBunchSizeExceeded = ($bunchSize > 0 && count($bunchRows) >= $bunchSize); + + if (($productDataSize + $rowSize) >= $maxDataSize || $isBunchSizeExceeded) { $startNewBunch = true; $nextRowBackup = array($source->key() => $rowData); } else { diff --git a/app/code/core/Mage/ImportExport/Model/Import/Entity/Product.php b/app/code/core/Mage/ImportExport/Model/Import/Entity/Product.php index 4bd0abf3c5..815cb6c5e7 100644 --- a/app/code/core/Mage/ImportExport/Model/Import/Entity/Product.php +++ b/app/code/core/Mage/ImportExport/Model/Import/Entity/Product.php @@ -63,6 +63,7 @@ class Mage_ImportExport_Model_Import_Entity_Product extends Mage_ImportExport_Mo const COL_ATTR_SET = '_attribute_set'; const COL_TYPE = '_type'; const COL_CATEGORY = '_category'; + const COL_ROOT_CATEGORY = '_root_category'; const COL_SKU = 'sku'; /** @@ -115,6 +116,13 @@ class Mage_ImportExport_Model_Import_Entity_Product extends Mage_ImportExport_Mo */ protected $_categories = array(); + /** + * Categories text-path to ID hash with roots checking. + * + * @var array + */ + protected $_categoriesWithRoots = array(); + /** * Customer groups ID-to-name. * @@ -211,9 +219,9 @@ class Mage_ImportExport_Model_Import_Entity_Product extends Mage_ImportExport_Mo * @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', - '_group_price_website', '_group_price_customer_group', '_group_price_price', + '_store', '_attribute_set', '_type', self::COL_CATEGORY, self::COL_ROOT_CATEGORY, '_product_websites', + '_tier_price_website', '_tier_price_customer_group', '_tier_price_qty', '_tier_price_price', + '_links_related_sku', '_group_price_website', '_group_price_customer_group', '_group_price_price', '_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', @@ -376,12 +384,20 @@ protected function _initCategories() foreach ($collection as $category) { $structure = explode('/', $category->getPath()); $pathSize = count($structure); - if ($pathSize > 2) { + if ($pathSize > 1) { $path = array(); - for ($i = 2; $i < $pathSize; $i++) { + for ($i = 1; $i < $pathSize; $i++) { $path[] = $collection->getItemById($structure[$i])->getName(); } - $this->_categories[implode('/', $path)] = $category->getId(); + $rootCategoryName = array_shift($path); + if (!isset($this->_categoriesWithRoots[$rootCategoryName])) { + $this->_categoriesWithRoots[$rootCategoryName] = array(); + } + $index = implode('/', $path); + $this->_categoriesWithRoots[$rootCategoryName][$index] = $category->getId(); + if ($pathSize > 2) { + $this->_categories[$index] = $category->getId(); + } } } return $this; @@ -491,7 +507,14 @@ protected function _initWebsites() */ protected function _isProductCategoryValid(array $rowData, $rowNum) { - if (!empty($rowData[self::COL_CATEGORY]) && !isset($this->_categories[$rowData[self::COL_CATEGORY]])) { + $emptyCategory = empty($rowData[self::COL_CATEGORY]); + $emptyRootCategory = empty($rowData[self::COL_ROOT_CATEGORY]); + $hasCategory = $emptyCategory ? false : isset($this->_categories[$rowData[self::COL_CATEGORY]]); + $category = $emptyRootCategory ? null : $this->_categoriesWithRoots[$rowData[self::COL_ROOT_CATEGORY]]; + if (!$emptyCategory && !$hasCategory + || !$emptyRootCategory && !isset($category) + || !$emptyRootCategory && !$emptyCategory && !isset($category[$rowData[self::COL_CATEGORY]]) + ) { $this->addRowError(self::ERROR_INVALID_CATEGORY, $rowNum); return false; } @@ -1164,10 +1187,17 @@ protected function _saveProducts() 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; + + // 3. Categories phase + $categoryPath = empty($rowData[self::COL_CATEGORY]) ? '' : $rowData[self::COL_CATEGORY]; + if (!empty($rowData[self::COL_ROOT_CATEGORY])) { + $categoryId = $this->_categoriesWithRoots[$rowData[self::COL_ROOT_CATEGORY]][$categoryPath]; + $categories[$rowSku][$categoryId] = true; + } elseif (!empty($categoryPath)) { + $categories[$rowSku][$this->_categories[$categoryPath]] = true; } - if (!empty($rowData['_tier_price_website'])) { // 4. Tier prices phase + + if (!empty($rowData['_tier_price_website'])) { // 4.1. 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) @@ -1178,7 +1208,7 @@ protected function _saveProducts() ? 0 : $this->_websiteCodeToId[$rowData['_tier_price_website']] ); } - if (!empty($rowData['_group_price_website'])) { // 5. Group prices phase + if (!empty($rowData['_group_price_website'])) { // 4.2. Group prices phase $groupPrices[$rowSku][] = array( 'all_groups' => $rowData['_group_price_customer_group'] == self::VALUE_ALL, 'customer_group_id' => ($rowData['_group_price_customer_group'] == self::VALUE_ALL) diff --git a/app/code/core/Mage/ImportExport/etc/config.xml b/app/code/core/Mage/ImportExport/etc/config.xml index 9a515fb7f8..81c348a83c 100644 --- a/app/code/core/Mage/ImportExport/etc/config.xml +++ b/app/code/core/Mage/ImportExport/etc/config.xml @@ -136,6 +136,7 @@ var/import/*/*.csv + 100 diff --git a/app/code/core/Mage/Log/etc/system.xml b/app/code/core/Mage/Log/etc/system.xml index ccac0a73d2..3068edc573 100644 --- a/app/code/core/Mage/Log/etc/system.xml +++ b/app/code/core/Mage/Log/etc/system.xml @@ -75,6 +75,7 @@ text + validate-email 5 1 0 diff --git a/app/code/core/Mage/OAuth/Block/Adminhtml/OAuth/Admin/Token.php b/app/code/core/Mage/OAuth/Block/Adminhtml/OAuth/Admin/Token.php new file mode 100644 index 0000000000..8bbcee994a --- /dev/null +++ b/app/code/core/Mage/OAuth/Block/Adminhtml/OAuth/Admin/Token.php @@ -0,0 +1,48 @@ + + */ +class Mage_OAuth_Block_Adminhtml_OAuth_Admin_Token extends Mage_Adminhtml_Block_Widget_Grid_Container +{ + /** + * Construct grid container + */ + public function __construct() + { + parent::__construct(); + + $this->_blockGroup = 'oauth'; + $this->_controller = 'adminhtml_oAuth_admin_token'; + $this->_headerText = Mage::helper('adminhtml')->__('My Applications'); + $this->_removeButton('add'); + } +} diff --git a/app/code/core/Mage/OAuth/Block/Adminhtml/OAuth/Admin/Token/Grid.php b/app/code/core/Mage/OAuth/Block/Adminhtml/OAuth/Admin/Token/Grid.php new file mode 100644 index 0000000000..c7bfbc6b19 --- /dev/null +++ b/app/code/core/Mage/OAuth/Block/Adminhtml/OAuth/Admin/Token/Grid.php @@ -0,0 +1,141 @@ + + */ +class Mage_OAuth_Block_Adminhtml_OAuth_Admin_Token_Grid extends Mage_Adminhtml_Block_Widget_Grid +{ + /** + * Construct grid block + */ + public function __construct() + { + parent::__construct(); + $this->setId('adminTokenGrid'); + $this->setUseAjax(true); + $this->setSaveParametersInSession(true); + $this->setDefaultSort('entity_id') + ->setDefaultDir(Varien_Db_Select::SQL_DESC); + } + + /** + * Prepare collection + * + * @return Mage_OAuth_Block_Adminhtml_OAuth_Admin_Token_Grid + */ + protected function _prepareCollection() + { + /** @var $user Mage_Admin_Model_User */ + $user = Mage::getSingleton('admin/session')->getData('user'); + + /** @var $collection Mage_OAuth_Model_Resource_Token_Collection */ + $collection = Mage::getModel('oauth/token')->getCollection(); + $collection->joinConsumerAsApplication() + ->addFilterByType(Mage_OAuth_Model_Token::TYPE_ACCESS) + ->addFilterByAdminId($user->getId()); + $this->setCollection($collection); + + parent::_prepareCollection(); + return $this; + } + + /** + * Prepare columns + * + * @return Mage_OAuth_Block_Adminhtml_OAuth_Admin_Token_Grid + */ + protected function _prepareColumns() + { + $this->addColumn('entity_id', array( + 'header' => Mage::helper('oauth')->__('ID'), + 'index' => 'entity_id', + 'align' => 'right', + 'width' => '50px', + )); + + $this->addColumn('name', array( + 'header' => $this->__('Application Name'), + 'index' => 'name', + 'escape' => true, + )); + + /** @var $sourceYesNo Mage_Adminhtml_Model_System_Config_Source_Yesno */ + $sourceYesNo = Mage::getSingleton('adminhtml/system_config_source_yesno'); + $this->addColumn('revoked', array( + 'header' => $this->__('Revoked'), + 'index' => 'revoked', + 'width' => '100px', + 'type' => 'options', + 'options' => $sourceYesNo->toArray(), + 'sortable' => true, + )); + + parent::_prepareColumns(); + return $this; + } + + /** + * Add mass-actions to grid + * + * @return Mage_OAuth_Block_Adminhtml_OAuth_Admin_Token_Grid + */ + protected function _prepareMassaction() + { + $this->setMassactionIdField('id'); + $block = $this->getMassactionBlock(); + + $block->setFormFieldName('items'); + $block->addItem('enable', array( + 'label' => Mage::helper('index')->__('Enable'), + 'url' => $this->getUrl('*/*/revoke', array('status' => 0)), + )); + $block->addItem('revoke', array( + 'label' => Mage::helper('index')->__('Revoke'), + 'url' => $this->getUrl('*/*/revoke', array('status' => 1)), + )); + $block->addItem('delete', array( + 'label' => Mage::helper('index')->__('Delete'), + 'url' => $this->getUrl('*/*/delete'), + )); + + return $this; + } + + /** + * Get grid URL + * + * @return string + */ + public function getGridUrl() + { + return $this->getUrl('*/*/grid', array('_current' => true)); + } +} diff --git a/app/code/core/Mage/OAuth/Block/Adminhtml/OAuth/Authorize.php b/app/code/core/Mage/OAuth/Block/Adminhtml/OAuth/Authorize.php new file mode 100644 index 0000000000..e3174c6e1e --- /dev/null +++ b/app/code/core/Mage/OAuth/Block/Adminhtml/OAuth/Authorize.php @@ -0,0 +1,79 @@ + + */ +class Mage_OAuth_Block_Adminhtml_OAuth_Authorize extends Mage_OAuth_Block_AuthorizeBaseAbstract +{ + /** + * Retrieve admin form posting url + * + * @return string + */ + public function getPostActionUrl() + { + $params = array(); + if ($this->getIsSimple()) { + $params['simple'] = 1; + } + return $this->getUrl('adminhtml/index/login', $params); + } + + /** + * Get form identity label + * + * @return string + */ + public function getIdentityLabel() + { + return $this->__('User Name'); + } + + /** + * Get form identity label + * + * @return string + */ + public function getFormTitle() + { + return $this->__('Log in as admin'); + } + + /** + * Retrieve reject application authorization URL + * + * @return string + */ + public function getRejectUrlPath() + { + return 'adminhtml/oAuth_authorize/reject'; + } +} diff --git a/app/code/core/Mage/OAuth/Block/Adminhtml/OAuth/Authorize/Button.php b/app/code/core/Mage/OAuth/Block/Adminhtml/OAuth/Authorize/Button.php new file mode 100644 index 0000000000..aa6e23537a --- /dev/null +++ b/app/code/core/Mage/OAuth/Block/Adminhtml/OAuth/Authorize/Button.php @@ -0,0 +1,55 @@ + + */ +class Mage_OAuth_Block_Adminhtml_OAuth_Authorize_Button extends Mage_OAuth_Block_Authorize_ButtonBaseAbstract +{ + /** + * Retrieve confirm authorization url path + * + * @return string + */ + public function getConfirmUrlPath() + { + return 'adminhtml/oAuth_authorize/confirm'; + } + + /** + * Retrieve reject authorization url path + * + * @return string + */ + public function getRejectUrlPath() + { + return 'adminhtml/oAuth_authorize/reject'; + } +} diff --git a/app/code/core/Mage/OAuth/Block/Adminhtml/OAuth/AuthorizedTokens.php b/app/code/core/Mage/OAuth/Block/Adminhtml/OAuth/AuthorizedTokens.php new file mode 100644 index 0000000000..b307a59af9 --- /dev/null +++ b/app/code/core/Mage/OAuth/Block/Adminhtml/OAuth/AuthorizedTokens.php @@ -0,0 +1,50 @@ + + */ +class Mage_OAuth_Block_Adminhtml_OAuth_AuthorizedTokens extends Mage_Adminhtml_Block_Widget_Grid_Container +{ + /** + * Construct grid container + */ + public function __construct() + { + parent::__construct(); + + $this->_blockGroup = 'oauth'; + $this->_controller = 'adminhtml_oAuth_authorizedTokens'; + $this->_headerText = Mage::helper('adminhtml')->__('Authorized OAuth Tokens'); + + $this->_removeButton('add'); + } +} + diff --git a/app/code/core/Mage/OAuth/Block/Adminhtml/OAuth/AuthorizedTokens/Grid.php b/app/code/core/Mage/OAuth/Block/Adminhtml/OAuth/AuthorizedTokens/Grid.php new file mode 100644 index 0000000000..69e3d948ac --- /dev/null +++ b/app/code/core/Mage/OAuth/Block/Adminhtml/OAuth/AuthorizedTokens/Grid.php @@ -0,0 +1,225 @@ + + */ +class Mage_OAuth_Block_Adminhtml_OAuth_AuthorizedTokens_Grid extends Mage_Adminhtml_Block_Widget_Grid +{ + /** + * Construct grid block + */ + public function __construct() + { + parent::__construct(); + $this->setId('authorizedTokensGrid'); + $this->setUseAjax(true); + $this->setSaveParametersInSession(true); + $this->setDefaultSort('entity_id') + ->setDefaultDir(Varien_Db_Select::SQL_DESC); + } + + /** + * Prepare collection + * + * @return Mage_OAuth_Block_Adminhtml_OAuth_AuthorizedTokens_Grid + */ + protected function _prepareCollection() + { + /** @var $collection Mage_OAuth_Model_Resource_Token_Collection */ + $collection = Mage::getModel('oauth/token')->getCollection(); + $collection->joinConsumerAsApplication() + ->addFilterByType(Mage_OAuth_Model_Token::TYPE_ACCESS); + $this->setCollection($collection); + + parent::_prepareCollection(); + return $this; + } + + /** + * Prepare columns + * + * @return Mage_OAuth_Block_Adminhtml_OAuth_AuthorizedTokens_Grid + */ + protected function _prepareColumns() + { + $this->addColumn('entity_id', array( + 'header' => Mage::helper('oauth')->__('ID'), + 'index' => 'entity_id', + 'align' => 'right', + 'width' => '50px', + + )); + + $this->addColumn('name', array( + 'header' => $this->__('Application Name'), + 'index' => 'name', + 'escape' => true, + )); + + $this->addColumn('type', array( + 'header' => $this->__('User Type'), + //'index' => array('customer_id', 'admin_id'), + 'options' => array(0 => $this->__('Admin'), 1 => $this->__('Customer')), + 'frame_callback' => array($this, 'decorateUserType') + )); + + $this->addColumn('user_id', array( + 'header' => $this->__('User ID'), + //'index' => array('customer_id', 'admin_id'), + 'frame_callback' => array($this, 'decorateUserId') + )); + + /** @var $sourceYesNo Mage_Adminhtml_Model_System_Config_Source_Yesno */ + $sourceYesNo = Mage::getSingleton('adminhtml/system_config_source_yesno'); + $this->addColumn('revoked', array( + 'header' => $this->__('Revoked'), + 'index' => 'revoked', + 'width' => '100px', + 'type' => 'options', + 'options' => $sourceYesNo->toArray(), + 'sortable' => true, + )); + + parent::_prepareColumns(); + return $this; + } + + /** + * Get grid URL + * + * @return string + */ + public function getGridUrl() + { + return $this->getUrl('*/*/grid', array('_current' => true)); + } + + /** + * Get revoke URL + * + * @param Mage_OAuth_Model_Token $row + * @return string|null + */ + public function getRevokeUrl($row) + { + return $this->getUrl('*/*/revoke', array('id' => $row->getId())); + } + + /** + * Get delete URL + * + * @param Mage_OAuth_Model_Token $row + * @return string|null + */ + public function getDeleteUrl($row) + { + return $this->getUrl('*/*/delete', array('id' => $row->getId())); + } + + /** + * Add mass-actions to grid + * + * @return Mage_OAuth_Block_Adminhtml_OAuth_AuthorizedTokens_Grid + */ + protected function _prepareMassaction() + { + if(!$this->_isAllowed()) { + return $this; + } + + $this->setMassactionIdField('id'); + $block = $this->getMassactionBlock(); + + $block->setFormFieldName('items'); + $block->addItem('enable', array( + 'label' => Mage::helper('index')->__('Enable'), + 'url' => $this->getUrl('*/*/revoke', array('status' => 0)), + )); + $block->addItem('revoke', array( + 'label' => Mage::helper('index')->__('Revoke'), + 'url' => $this->getUrl('*/*/revoke', array('status' => 1)), + )); + $block->addItem('delete', array( + 'label' => Mage::helper('index')->__('Delete'), + 'url' => $this->getUrl('*/*/delete'), + )); + + return $this; + } + + /** + * Decorate user type column + * + * @param string $value + * @param Mage_OAuth_Model_Token $row + * @param Mage_Adminhtml_Block_Widget_Grid_Column $column + * @param bool $isExport + * @return mixed + */ + public function decorateUserType($value, $row, $column, $isExport) + { + $options = $column->getOptions(); + + $value = ($row->getCustomerId()) ?$options[1] :$options[0]; + $cell = $value; + + return $cell; + } + + /** + * Decorate user type column + * + * @param string $value + * @param Mage_OAuth_Model_Token $row + * @param Mage_Adminhtml_Block_Widget_Grid_Column $column + * @param bool $isExport + * @return mixed + */ + public function decorateUserId($value, $row, $column, $isExport) + { + $value = ($row->getCustomerId()) ?$row->getCustomerId() :$row->getAdminId(); + $cell = $value; + + return $cell; + } + + /** + * Check admin permissions + * + * @return boolean + */ + protected function _isAllowed() + { + /** @var $session Mage_Admin_Model_Session */ + $session = Mage::getSingleton('admin/session'); + return $session->isAllowed('system/oauth/authorizedTokens'); + } +} diff --git a/app/code/core/Mage/OAuth/Block/Adminhtml/OAuth/Consumer.php b/app/code/core/Mage/OAuth/Block/Adminhtml/OAuth/Consumer.php new file mode 100644 index 0000000000..b4c9dca891 --- /dev/null +++ b/app/code/core/Mage/OAuth/Block/Adminhtml/OAuth/Consumer.php @@ -0,0 +1,54 @@ + + */ +class Mage_OAuth_Block_Adminhtml_OAuth_Consumer extends Mage_Adminhtml_Block_Widget_Grid_Container +{ + /** + * Construct grid container + */ + public function __construct() + { + parent::__construct(); + + $this->_blockGroup = 'oauth'; + $this->_controller = 'adminhtml_oAuth_consumer'; + $this->_headerText = Mage::helper('adminhtml')->__('OAuth Consumers'); + + //check allow edit + /** @var $session Mage_Admin_Model_Session */ + $session = Mage::getSingleton('admin/session'); + if (!$session->isAllowed('system/oauth/consumer/edit')) { + $this->_removeButton('add'); + } + } +} diff --git a/app/code/core/Mage/OAuth/Block/Adminhtml/OAuth/Consumer/Edit.php b/app/code/core/Mage/OAuth/Block/Adminhtml/OAuth/Consumer/Edit.php new file mode 100644 index 0000000000..de7eb17b4b --- /dev/null +++ b/app/code/core/Mage/OAuth/Block/Adminhtml/OAuth/Consumer/Edit.php @@ -0,0 +1,100 @@ + + */ +class Mage_OAuth_Block_Adminhtml_OAuth_Consumer_Edit extends Mage_Adminhtml_Block_Widget_Form_Container +{ + /** + * Consumer model + * + * @var Mage_OAuth_Model_Consumer + */ + protected $_model; + + /** + * Get consumer model + * + * @return Mage_OAuth_Model_Consumer + */ + public function getModel() + { + if (null === $this->_model) { + $this->_model = Mage::registry('current_consumer'); + } + return $this->_model; + } + + /** + * Construct edit page + */ + public function __construct() + { + parent::__construct(); + $this->_blockGroup = 'oauth'; + $this->_controller = 'adminhtml_oAuth_consumer'; + $this->_mode = 'edit'; + + $this->_addButton('save_and_continue', array( + 'label' => Mage::helper('oauth')->__('Save and Continue Edit'), + 'onclick' => 'saveAndContinueEdit()', + 'class' => 'save' + ), 100); + + $this->_formScripts[] = "function saveAndContinueEdit()" . + "{editForm.submit($('edit_form').action + 'back/edit/')}"; + + $this->_updateButton('save', 'label', $this->__('Save')); + $this->_updateButton('save', 'id', 'save_button'); + $this->_updateButton('delete', 'label', $this->__('Delete')); + + /** @var $session Mage_Admin_Model_Session */ + $session = Mage::getSingleton('admin/session'); + if (!$this->getModel()->getId() || !$session->isAllowed('system/oauth/consumer/delete')) { + $this->_removeButton('delete'); + } + } + + /** + * Get header text + * + * @return string + */ + public function getHeaderText() + { + if ($this->getModel()->getId()) { + return $this->__('Edit Consumer'); + } else { + return $this->__('New Consumer'); + } + } +} diff --git a/app/code/core/Mage/OAuth/Block/Adminhtml/OAuth/Consumer/Edit/Form.php b/app/code/core/Mage/OAuth/Block/Adminhtml/OAuth/Consumer/Edit/Form.php new file mode 100644 index 0000000000..8d27f2a1c4 --- /dev/null +++ b/app/code/core/Mage/OAuth/Block/Adminhtml/OAuth/Consumer/Edit/Form.php @@ -0,0 +1,126 @@ + + */ +class Mage_OAuth_Block_Adminhtml_OAuth_Consumer_Edit_Form extends Mage_Adminhtml_Block_Widget_Form +{ + /** + * Consumer model + * + * @var Mage_OAuth_Model_Consumer + */ + protected $_model; + + /** + * Get consumer model + * + * @return Mage_OAuth_Model_Consumer + */ + public function getModel() + { + if (null === $this->_model) { + $this->_model = Mage::registry('current_consumer'); + } + return $this->_model; + } + + /** + * Prepare form before rendering HTML + * + * @return Mage_OAuth_Block_Adminhtml_OAuth_Consumer_Edit_Form + */ + protected function _prepareForm() + { + $model = $this->getModel(); + $form = new Varien_Data_Form(array( + 'id' => 'edit_form', 'action' => $this->getData('action'), 'method' => 'post' + )); + + $fieldset = $form->addFieldset('base_fieldset', array( + 'legend' => Mage::helper('oauth')->__('Consumer Information'), 'class' => 'fieldset-wide' + )); + + if ($model->getId()) { + $fieldset->addField('id', 'hidden', array('name' => 'id', 'value' => $model->getId())); + } + $fieldset->addField('name', 'text', array( + 'name' => 'name', + 'label' => Mage::helper('oauth')->__('Name'), + 'title' => Mage::helper('oauth')->__('Name'), + 'required' => true, + 'value' => $model->getName(), + )); + + $fieldset->addField('key', 'text', array( + 'name' => 'key', + 'label' => Mage::helper('oauth')->__('Key'), + 'title' => Mage::helper('oauth')->__('Key'), + 'disabled' => true, + 'required' => true, + 'value' => $model->getKey(), + )); + + $fieldset->addField('secret', 'text', array( + 'name' => 'secret', + 'label' => Mage::helper('oauth')->__('Secret'), + 'title' => Mage::helper('oauth')->__('Secret'), + 'disabled' => true, + 'required' => true, + 'value' => $model->getSecret(), + )); + + $fieldset->addField('callback_url', 'text', array( + 'name' => 'callback_url', + 'label' => Mage::helper('oauth')->__('Callback URL'), + 'title' => Mage::helper('oauth')->__('Callback URL'), + 'required' => false, + 'value' => $model->getCallbackUrl(), + 'class' => 'validate-url', + )); + + $fieldset->addField('rejected_callback_url', 'text', array( + 'name' => 'rejected_callback_url', + 'label' => Mage::helper('oauth')->__('Rejected Callback URL'), + 'title' => Mage::helper('oauth')->__('Rejected Callback URL'), + 'required' => false, + 'value' => $model->getRejectedCallbackUrl(), + 'class' => 'validate-url', + )); + + $form->setAction($this->getUrl('*/*/save')); + $form->setUseContainer(true); + $this->setForm($form); + + return parent::_prepareForm(); + } +} diff --git a/app/code/core/Mage/OAuth/Block/Adminhtml/OAuth/Consumer/Grid.php b/app/code/core/Mage/OAuth/Block/Adminhtml/OAuth/Consumer/Grid.php new file mode 100644 index 0000000000..b97e0aee5b --- /dev/null +++ b/app/code/core/Mage/OAuth/Block/Adminhtml/OAuth/Consumer/Grid.php @@ -0,0 +1,118 @@ + + */ +class Mage_OAuth_Block_Adminhtml_OAuth_Consumer_Grid extends Mage_Adminhtml_Block_Widget_Grid +{ + /** + * Allow edit status + * + * @var bool + */ + protected $_editAllow = false; + + /** + * Construct grid block + */ + public function __construct() + { + parent::__construct(); + $this->setId('consumerGrid'); + $this->setUseAjax(true); + $this->setSaveParametersInSession(true); + $this->setDefaultSort('entity_id') + ->setDefaultDir(Varien_Db_Select::SQL_DESC); + + /** @var $session Mage_Admin_Model_Session */ + $session = Mage::getSingleton('admin/session'); + $this->_editAllow = $session->isAllowed('system/oauth/consumer/edit'); + } + + /** + * Prepare collection + * + * @return Mage_OAuth_Block_Adminhtml_OAuth_Consumer_Grid + */ + protected function _prepareCollection() + { + $collection = Mage::getModel('oauth/consumer')->getCollection(); + $this->setCollection($collection); + + return parent::_prepareCollection(); + } + + /** + * Prepare columns + * + * @return Mage_OAuth_Block_Adminhtml_OAuth_Consumer_Grid + */ + protected function _prepareColumns() + { + $this->addColumn('entity_id', array( + 'header' => Mage::helper('oauth')->__('ID'), 'index' => 'entity_id', 'align' => 'right', 'width' => '50px' + )); + + $this->addColumn('name', array( + 'header' => Mage::helper('oauth')->__('Consumer Name'), 'index' => 'name', 'escape' => true + )); + + $this->addColumn('created_at', array( + 'header' => Mage::helper('oauth')->__('Created At'), 'index' => 'created_at' + )); + + return parent::_prepareColumns(); + } + + /** + * Get grid URL + * + * @return string + */ + public function getGridUrl() + { + return $this->getUrl('*/*/grid', array('_current' => true)); + } + + /** + * Get row URL + * + * @param Mage_OAuth_Model_Consumer $row + * @return string|null + */ + public function getRowUrl($row) + { + if ($this->_editAllow) { + return $this->getUrl('*/*/edit', array('id' => $row->getId())); + } + return null; + } +} diff --git a/app/code/core/Mage/OAuth/Block/Authorize.php b/app/code/core/Mage/OAuth/Block/Authorize.php new file mode 100644 index 0000000000..5aa0c78fd4 --- /dev/null +++ b/app/code/core/Mage/OAuth/Block/Authorize.php @@ -0,0 +1,81 @@ + + */ +class Mage_OAuth_Block_Authorize extends Mage_OAuth_Block_AuthorizeBaseAbstract +{ + /** + * Retrieve customer form posting url + * + * @return string + */ + public function getPostActionUrl() + { + /** @var $helper Mage_Customer_Helper_Data */ + $helper = $this->helper('customer'); + $url = $helper->getLoginPostUrl(); + if ($this->getIsSimple()) { + $url = rtrim($url, '/') . '/simple/1'; + } + return $url; + } + + /** + * Get form identity label + * + * @return string + */ + public function getIdentityLabel() + { + return $this->__('Email Address'); + } + + /** + * Get form identity label + * + * @return string + */ + public function getFormTitle() + { + return $this->__('Log in as customer'); + } + + /** + * Retrieve reject URL path + * + * @return string + */ + public function getRejectUrlPath() + { + return 'oauth/authorize/reject'; + } +} diff --git a/app/code/core/Mage/OAuth/Block/Authorize/Abstract.php b/app/code/core/Mage/OAuth/Block/Authorize/Abstract.php new file mode 100644 index 0000000000..96ec2e751a --- /dev/null +++ b/app/code/core/Mage/OAuth/Block/Authorize/Abstract.php @@ -0,0 +1,106 @@ + + * @method string getToken() + * @method Mage_OAuth_Block_AuthorizeBaseAbstract setToken() setToken(string $token) + * @method boolean getIsSimple() + * @method Mage_OAuth_Block_Authorize_Button setIsSimple() setIsSimple(boolean $flag) + * @method boolean getHasException() + * @method Mage_OAuth_Block_AuthorizeBaseAbstract setIsException() setHasException(boolean $flag) + * @method boolean getVerifier() + * @method Mage_OAuth_Block_AuthorizeBaseAbstract setVerifier() setVerifier(string $verifier) + * @method boolean getIsLogged() + * @method Mage_OAuth_Block_AuthorizeBaseAbstract setIsLogged() setIsLogged(boolean $flag) + */ +abstract class Mage_OAuth_Block_Authorize_Abstract extends Mage_Core_Block_Template +{ + /** + * Helper + * + * @var Mage_OAuth_Helper_Data + */ + protected $_helper; + + /** + * Consumer model + * + * @var Mage_OAuth_Model_Consumer + */ + protected $_consumer; + + /** + * Constructor + */ + public function __construct() + { + parent::__construct(); + $this->_helper = Mage::helper('oauth'); + } + + /** + * Get consumer instance by token value + * + * @return Mage_OAuth_Model_Consumer + */ + public function getConsumer() + { + if (null === $this->_consumer) { + /** @var $token Mage_OAuth_Model_Token */ + $token = Mage::getModel('oauth/token'); + $token->load($this->getToken(), 'token'); + $this->_consumer = $token->getConsumer(); + } + return $this->_consumer; + } + + /** + * Get absolute path to template + * + * Load template from adminhtml/default area flag is_simple is set + * + * @return string + */ + public function getTemplateFile() + { + if (!$this->getIsSimple()) { + return parent::getTemplateFile(); + } + + //load base template from admin area + $params = array( + '_relative' => true, + '_area' => 'adminhtml', + '_package' => 'default' + ); + return Mage::getDesign()->getTemplateFilename($this->getTemplate(), $params); + } +} diff --git a/app/code/core/Mage/OAuth/Block/Authorize/Button.php b/app/code/core/Mage/OAuth/Block/Authorize/Button.php new file mode 100644 index 0000000000..82eda40c7d --- /dev/null +++ b/app/code/core/Mage/OAuth/Block/Authorize/Button.php @@ -0,0 +1,55 @@ + + */ +class Mage_OAuth_Block_Authorize_Button extends Mage_OAuth_Block_Authorize_ButtonBaseAbstract +{ + /** + * Retrieve confirm authorization url path + * + * @return string + */ + public function getConfirmUrlPath() + { + return 'oauth/authorize/confirm'; + } + + /** + * Retrieve reject authorization url path + * + * @return string + */ + public function getRejectUrlPath() + { + return 'oauth/authorize/reject'; + } +} diff --git a/app/code/core/Mage/OAuth/Block/Authorize/ButtonBaseAbstract.php b/app/code/core/Mage/OAuth/Block/Authorize/ButtonBaseAbstract.php new file mode 100644 index 0000000000..27199bed54 --- /dev/null +++ b/app/code/core/Mage/OAuth/Block/Authorize/ButtonBaseAbstract.php @@ -0,0 +1,69 @@ + + */ +abstract class Mage_OAuth_Block_Authorize_ButtonBaseAbstract extends Mage_OAuth_Block_Authorize_Abstract +{ + /** + * Get confirm url path + * + * @return string + */ + abstract public function getConfirmUrlPath(); + + /** + * Get reject url path + * + * @return string + */ + abstract public function getRejectUrlPath(); + + /** + * Retrieve reject authorization url + * + * @return string + */ + public function getConfirmUrl() + { + return $this->getUrl($this->getConfirmUrlPath() . ($this->getIsSimple() ? 'Simple' : '')); + } + + /** + * Retrieve reject authorization url + * + * @return string + */ + public function getRejectUrl() + { + return $this->getUrl($this->getRejectUrlPath() . ($this->getIsSimple() ? 'Simple' : '')); + } +} diff --git a/app/code/core/Mage/OAuth/Block/Authorize/Style.php b/app/code/core/Mage/OAuth/Block/Authorize/Style.php new file mode 100644 index 0000000000..0f166479a1 --- /dev/null +++ b/app/code/core/Mage/OAuth/Block/Authorize/Style.php @@ -0,0 +1,47 @@ + + */ +class Mage_OAuth_Block_Authorize_Style extends Mage_OAuth_Block_Authorize_Abstract +{ + /** + * Set default data + */ + public function __construct() + { + parent::__construct(); + + //default load template from admin package + $this->setIsSimple(true); + } + +} diff --git a/app/code/core/Mage/OAuth/Block/AuthorizeBaseAbstract.php b/app/code/core/Mage/OAuth/Block/AuthorizeBaseAbstract.php new file mode 100644 index 0000000000..44d9e60b5a --- /dev/null +++ b/app/code/core/Mage/OAuth/Block/AuthorizeBaseAbstract.php @@ -0,0 +1,75 @@ + + */ +abstract class Mage_OAuth_Block_AuthorizeBaseAbstract extends Mage_OAuth_Block_Authorize_Abstract +{ + /** + * Retrieve user authorize form posting url + * + * @return string + */ + abstract public function getPostActionUrl(); + + /** + * Retrieve reject authorization url + * + * @return string + */ + public function getRejectUrl() + { + $url = $this->getUrl($this->getRejectUrlPath() . ($this->getIsSimple() ? 'Simple' : ''), + array('_query' => array('oauth_token' => $this->getToken()))); + return $url; + } + + /** + * Retrieve reject URL path + * + * @return string + */ + abstract public function getRejectUrlPath(); + + /** + * Get form identity label + * + * @return string + */ + abstract public function getIdentityLabel(); + + /** + * Get form identity label + * + * @return string + */ + abstract public function getFormTitle(); +} diff --git a/app/code/core/Mage/OAuth/Block/Customer/Token/List.php b/app/code/core/Mage/OAuth/Block/Customer/Token/List.php new file mode 100644 index 0000000000..d1cc4f04da --- /dev/null +++ b/app/code/core/Mage/OAuth/Block/Customer/Token/List.php @@ -0,0 +1,172 @@ + + */ +class Mage_OAuth_Block_Customer_Token_List extends Mage_Customer_Block_Account_Dashboard +{ + /** + * Collection model + * + * @var Mage_OAuth_Model_Resource_Token_Collection + */ + protected $_collection; + + /** + * Prepare collection + */ + protected function _construct() + { + /** @var $session Mage_Customer_Model_Session */ + $session = Mage::getSingleton('customer/session'); + + /** @var $collection Mage_OAuth_Model_Resource_Token_Collection */ + $collection = Mage::getModel('oauth/token')->getCollection(); + $collection->joinConsumerAsApplication() + ->addFilterByType(Mage_OAuth_Model_Token::TYPE_ACCESS) + ->addFilterByCustomerId($session->getCustomerId()); + $this->_collection = $collection; + } + + /** + * Get count of total records + * + * @return int + */ + public function count() + { + return $this->_collection->getSize(); + } + + /** + * Get toolbar html + * + * @return string + */ + public function getToolbarHtml() + { + return $this->getChildHtml('toolbar'); + } + + /** + * Prepare layout + * + * @return Mage_OAuth_Block_Customer_Token_List + */ + protected function _prepareLayout() + { + /** @var $toolbar Mage_Page_Block_Html_Pager */ + $toolbar = $this->getLayout()->createBlock('page/html_pager', 'customer_token.toolbar'); + $toolbar->setCollection($this->_collection); + $this->setChild('toolbar', $toolbar); + parent::_prepareLayout(); + return $this; + } + + /** + * Get collection + * + * @return Mage_OAuth_Model_Resource_Token_Collection + */ + public function getCollection() + { + return $this->_collection; + } + + /** + * Get link for update revoke status + * + * @param Mage_OAuth_Model_Token $model + * @return string + */ + public function getUpdateRevokeLink(Mage_OAuth_Model_Token $model) + { + return Mage::getUrl('oauth/customer_token/revoke/', + array('id' => $model->getId(), 'status' => (int) !$model->getRevoked())); + } + + /** + * Get delete link + * + * @param Mage_OAuth_Model_Token $model + * @return string + */ + public function getDeleteLink(Mage_OAuth_Model_Token $model) + { + return Mage::getUrl('oauth/customer_token/delete/', array('id' => $model->getId())); + } + + /** + * Retrieve a token status label + * + * @param int $revokedStatus Token status of revoking + * @return string + */ + public function getStatusLabel($revokedStatus) + { + $labels = array( + $this->__('Enabled'), + $this->__('Disabled') + ); + return $labels[$revokedStatus]; + } + + /** + * Retrieve a label of link to change a token status + * + * @param int $revokedStatus Token status of revoking + * @return string + */ + public function getChangeStatusLabel($revokedStatus) + { + $labels = array( + $this->__('Disable'), + $this->__('Enable') + ); + return $labels[$revokedStatus]; + } + + /** + * Retrieve a message to confirm an action to change a token status + * + * @param int $revokedStatus Token status of revoking + * @return string + */ + public function getChangeStatusConfirmMessage($revokedStatus) + { + $messages = array( + $this->__('Are you sure you want to disable this application?'), + $this->__('Are you sure you want to enable this application?') + ); + return $messages[$revokedStatus]; + } +} diff --git a/app/code/core/Mage/OAuth/Exception.php b/app/code/core/Mage/OAuth/Exception.php new file mode 100644 index 0000000000..972d05bc52 --- /dev/null +++ b/app/code/core/Mage/OAuth/Exception.php @@ -0,0 +1,36 @@ + + */ +class Mage_OAuth_Exception extends Zend_Exception +{ +} diff --git a/app/code/core/Mage/OAuth/Helper/Data.php b/app/code/core/Mage/OAuth/Helper/Data.php new file mode 100644 index 0000000000..6443d9156a --- /dev/null +++ b/app/code/core/Mage/OAuth/Helper/Data.php @@ -0,0 +1,245 @@ + + */ +class Mage_OAuth_Helper_Data extends Mage_Core_Helper_Abstract +{ + /**#@+ + * Endpoint types with appropriate routes + */ + const ENDPOINT_AUTHORIZE_CUSTOMER = 'oauth/authorize'; + const ENDPOINT_AUTHORIZE_ADMIN = 'adminhtml/oAuth_authorize'; + const ENDPOINT_AUTHORIZE_CUSTOMER_SIMPLE = 'oauth/authorize/simple'; + const ENDPOINT_AUTHORIZE_ADMIN_SIMPLE = 'adminhtml/oAuth_authorize/simple'; + const ENDPOINT_INITIATE = 'oauth/initiate'; + const ENDPOINT_TOKEN = 'oauth/token'; + /**#@-*/ + + /**#@+ + * Cleanup xpath config settings + */ + const XML_PATH_CLEANUP_PROBABILITY = 'oauth/cleanup/cleanup_probability'; + const XML_PATH_CLEANUP_EXPIRATION_PERIOD = 'oauth/cleanup/expiration_period'; + /**#@-*/ + + /**#@+ Email template */ + const XML_PATH_EMAIL_TEMPLATE = 'oauth/email/template'; + const XML_PATH_EMAIL_IDENTITY = 'oauth/email/identity'; + /**#@-*/ + + /** + * Cleanup expiration period in minutes + */ + const CLEANUP_EXPIRATION_PERIOD_DEFAULT = 120; + + /** + * Query parameter as a sign that user rejects + */ + const QUERY_PARAM_REJECTED = 'rejected'; + + /** + * Available endpoints list + * + * @var array + */ + protected $_endpoints = array( + self::ENDPOINT_AUTHORIZE_CUSTOMER, + self::ENDPOINT_AUTHORIZE_ADMIN, + self::ENDPOINT_AUTHORIZE_CUSTOMER_SIMPLE, + self::ENDPOINT_AUTHORIZE_ADMIN_SIMPLE, + self::ENDPOINT_INITIATE, + self::ENDPOINT_TOKEN + ); + + /** + * Generate random string for token or secret or verifier + * + * @param int $length String length + * @return string + */ + protected function _generateRandomString($length) + { + /** @var $helper Mage_Core_Helper_Data */ + $helper = Mage::helper('core'); + + return $helper->getRandomString( + $length, Mage_Core_Helper_Data::CHARS_DIGITS . Mage_Core_Helper_Data::CHARS_LOWERS + ); + } + + /** + * Generate random string for token + * + * @return string + */ + public function generateToken() + { + return $this->_generateRandomString(Mage_OAuth_Model_Token::LENGTH_TOKEN); + } + + /** + * Generate random string for token secret + * + * @return string + */ + public function generateTokenSecret() + { + return $this->_generateRandomString(Mage_OAuth_Model_Token::LENGTH_SECRET); + } + + /** + * Generate random string for verifier + * + * @return string + */ + public function generateVerifier() + { + return $this->_generateRandomString(Mage_OAuth_Model_Token::LENGTH_VERIFIER); + } + + /** + * Generate random string for consumer key + * + * @return string + */ + public function generateConsumerKey() + { + return $this->_generateRandomString(Mage_OAuth_Model_Consumer::KEY_LENGTH); + } + + /** + * Generate random string for consumer secret + * + * @return string + */ + public function generateConsumerSecret() + { + return $this->_generateRandomString(Mage_OAuth_Model_Consumer::SECRET_LENGTH); + } + + /** + * Return complete callback URL or boolean FALSE if no callback provided + * + * @param Mage_OAuth_Model_Token $token Token object + * @param bool $rejected OPTIONAL Add user reject sign + * @return bool|string + */ + public function getFullCallbackUrl(Mage_OAuth_Model_Token $token, $rejected = false) + { + $callbackUrl = $token->getCallbackUrl(); + + if (Mage_OAuth_Model_Server::CALLBACK_ESTABLISHED == $callbackUrl) { + return false; + } + if ($rejected) { + /** @var $consumer Mage_OAuth_Model_Consumer */ + $consumer = Mage::getModel('oauth/consumer')->load($token->getConsumerId()); + + if ($consumer->getId() && $consumer->getRejectedCallbackUrl()) { + $callbackUrl = $consumer->getRejectedCallbackUrl(); + } + } elseif (!$token->getAuthorized()) { + Mage::throwException('Token is not authorized'); + } + $callbackUrl .= (false === strpos($callbackUrl, '?') ? '?' : '&'); + $callbackUrl .= 'oauth_token=' . $token->getToken() . '&'; + $callbackUrl .= $rejected ? self::QUERY_PARAM_REJECTED . '=1' : 'oauth_verifier=' . $token->getVerifier(); + + return $callbackUrl; + } + + /** + * Retrieve URL of specified endpoint. + * + * @param string $type Endpoint type (one of ENDPOINT_ constants) + * @return string + * @throws Exception Exception when endpoint not found + */ + public function getProtocolEndpointUrl($type) + { + if (!in_array($type, $this->_endpoints)) { + throw new Exception('Invalid endpoint type passed.'); + } + return rtrim(Mage::getUrl($type), '/'); + } + + /** + * Calculate cleanup possibility for data with lifetime property + * + * @return bool + */ + public function isCleanupProbability() + { + // Safe get cleanup probability value from system configuration + $configValue = (int) Mage::getStoreConfig(self::XML_PATH_CLEANUP_PROBABILITY); + return $configValue > 0 ? 1 == mt_rand(1, $configValue) : false; + } + + /** + * Get cleanup expiration period value from system configuration in minutes + * + * @return int + */ + public function getCleanupExpirationPeriod() + { + $minutes = (int) Mage::getStoreConfig(self::XML_PATH_CLEANUP_EXPIRATION_PERIOD); + return $minutes > 0 ? $minutes : self::CLEANUP_EXPIRATION_PERIOD_DEFAULT; + } + + /** + * Send Email to Token owner + * + * @param string $userEmail + * @param string $userName + * @param string $applicationName + * @param string $status + */ + public function sendNotificationOnTokenStatusChange($userEmail, $userName, $applicationName, $status) + { + /* @var $mailTemplate Mage_Core_Model_Email_Template */ + $mailTemplate = Mage::getModel('core/email_template'); + + $mailTemplate->sendTransactional( + Mage::getStoreConfig(self::XML_PATH_EMAIL_TEMPLATE), + Mage::getStoreConfig(self::XML_PATH_EMAIL_IDENTITY), + $userEmail, + $userName, + array( + 'name' => $userName, + 'email' => $userEmail, + 'applicationName' => $applicationName, + 'status' => $status, + + ) + ); + } +} diff --git a/app/code/core/Mage/OAuth/Model/Consumer.php b/app/code/core/Mage/OAuth/Model/Consumer.php new file mode 100644 index 0000000000..c67a336ef1 --- /dev/null +++ b/app/code/core/Mage/OAuth/Model/Consumer.php @@ -0,0 +1,131 @@ + + * @method Mage_OAuth_Model_Resource_Consumer _getResource() + * @method Mage_OAuth_Model_Resource_Consumer getResource() + * @method Mage_OAuth_Model_Resource_Consumer_Collection getCollection() + * @method Mage_OAuth_Model_Resource_Consumer_Collection getResourceCollection() + * @method string getName() + * @method Mage_OAuth_Model_Consumer setName() setName(string $name) + * @method string getKey() + * @method Mage_OAuth_Model_Consumer setKey() setKey(string $key) + * @method string getSecret() + * @method Mage_OAuth_Model_Consumer setSecret() setSecret(string $secret) + * @method string getCallbackUrl() + * @method Mage_OAuth_Model_Consumer setCallbackUrl() setCallbackUrl(string $url) + * @method string getCreatedAt() + * @method Mage_OAuth_Model_Consumer setCreatedAt() setCreatedAt(string $date) + * @method string getUpdatedAt() + * @method Mage_OAuth_Model_Consumer setUpdatedAt() setUpdatedAt(string $date) + * @method string getRejectedCallbackUrl() + * @method Mage_OAuth_Model_Consumer setRejectedCallbackUrl() setRejectedCallbackUrl(string $rejectedCallbackUrl) + */ +class Mage_OAuth_Model_Consumer extends Mage_Core_Model_Abstract +{ + /** + * Key hash length + */ + const KEY_LENGTH = 32; + + /** + * Secret hash length + */ + const SECRET_LENGTH = 32; + + /** + * Initialize resource model + * + * @return void + */ + protected function _construct() + { + $this->_init('oauth/consumer'); + } + + /** + * BeforeSave actions + * + * @return Mage_OAuth_Model_Consumer + */ + protected function _beforeSave() + { + if (!$this->getId()) { + $this->setUpdatedAt(time()); + } + $this->validate(); + parent::_beforeSave(); + return $this; + } + + /** + * Validate data + * + * @return array|bool + * @throw Mage_Core_Exception|Exception Throw exception on fail validation + */ + public function validate() + { + if ($this->getCallbackUrl() || $this->getRejectedCallbackUrl()) { + $this->setCallbackUrl(trim($this->getCallbackUrl())); + $this->setRejectedCallbackUrl(trim($this->getRejectedCallbackUrl())); + + /** @var $validatorUrl Mage_Core_Model_Url_Validator */ + $validatorUrl = Mage::getSingleton('core/url_validator'); + + if ($this->getCallbackUrl() && !$validatorUrl->isValid($this->getCallbackUrl())) { + Mage::throwException(Mage::helper('oauth')->__('Invalid Callback URL')); + } + if ($this->getRejectedCallbackUrl() && !$validatorUrl->isValid($this->getRejectedCallbackUrl())) { + Mage::throwException(Mage::helper('oauth')->__('Invalid Rejected Callback URL')); + } + } + + /** @var $validatorLength Mage_OAuth_Model_Consumer_Validator_KeyLength */ + $validatorLength = Mage::getModel( + 'oauth/consumer_validator_keyLength', + array('length' => self::KEY_LENGTH)); + + $validatorLength->setName('Consumer Key'); + if (!$validatorLength->isValid($this->getKey())) { + $messages = $validatorLength->getMessages(); + Mage::throwException(array_shift($messages)); + } + + $validatorLength->setLength(self::SECRET_LENGTH); + $validatorLength->setName('Consumer Secret'); + if (!$validatorLength->isValid($this->getSecret())) { + $messages = $validatorLength->getMessages(); + Mage::throwException(array_shift($messages)); + } + return true; + } +} diff --git a/app/code/core/Mage/OAuth/Model/Consumer/Validator/KeyLength.php b/app/code/core/Mage/OAuth/Model/Consumer/Validator/KeyLength.php new file mode 100644 index 0000000000..bee2243f84 --- /dev/null +++ b/app/code/core/Mage/OAuth/Model/Consumer/Validator/KeyLength.php @@ -0,0 +1,157 @@ + + */ +class Mage_OAuth_Model_Consumer_Validator_KeyLength extends Zend_Validate_StringLength +{ + /** + * Key name + * + * @var string + */ + protected $_name = 'Key'; + + /** + * Sets validator options + * + * @param integer|array|Zend_Config $options + */ + public function __construct($options = array()) + { + if (!is_array($options)) { + $options = func_get_args(); + if (!isset($options[1])) { + $options[1] = 'utf-8'; + } + parent::__construct($options[0], $options[0], $options[1]); + return; + } else { + if (isset($options['length'])) { + $options['max'] = + $options['min'] = $options['length']; + } + if (isset($options['name'])) { + $this->_name = $options['name']; + } + } + parent::__construct($options); + } + + /** + * Init validation failure message template definitions + * + * @return Mage_OAuth_Model_Consumer_Validator_KeyLength + */ + protected function _initMessageTemplates() + { + $_messageTemplates[self::TOO_LONG] = + Mage::helper('oauth')->__("%name% '%value%' is too long. It must has length %min% symbols."); + $_messageTemplates[self::TOO_SHORT] = + Mage::helper('oauth')->__("%name% '%value%' is too short. It must has length %min% symbols."); + + return $this; + } + + /** + * Additional variables available for validation failure messages + * + * @var array + */ + protected $_messageVariables = array( + 'min' => '_min', + 'max' => '_max', + 'name' => '_name' + ); + + /** + * Set length + * + * @param $length + * @return Mage_OAuth_Model_Consumer_Validator_KeyLength + */ + public function setLength($length) + { + parent::setMax($length); + parent::setMin($length); + return $this; + } + + /** + * Set length + * + * @return int + */ + public function getLength() + { + return parent::getMin(); + } + + /** + * Defined by Zend_Validate_Interface + * + * Returns true if and only if the string length of $value is at least the min option and + * no greater than the max option (when the max option is not null). + * + * @param string $value + * @return boolean + */ + public function isValid($value) + { + $result = parent::isValid($value); + if (!$result && isset($this->_messages[self::INVALID])) { + throw new Exception($this->_messages[self::INVALID]); + } + return $result; + } + + /** + * Set key name + * + * @param string $name + * @return Mage_OAuth_Model_Consumer_Validator_KeyLength + */ + public function setName($name) + { + $this->_name = $name; + return $this; + } + + /** + * Get key name + * + * @return string + */ + public function getName() + { + return $this->_name; + } +} diff --git a/app/code/core/Mage/OAuth/Model/Nonce.php b/app/code/core/Mage/OAuth/Model/Nonce.php new file mode 100644 index 0000000000..ce92f848c3 --- /dev/null +++ b/app/code/core/Mage/OAuth/Model/Nonce.php @@ -0,0 +1,70 @@ + + * @method string getNonce() + * @method Mage_OAuth_Model_Nonce setNonce() setNonce(string $nonce) + * @method string getTimestamp() + * @method Mage_OAuth_Model_Nonce setTimestamp() setTimestamp(string $timestamp) + * @method Mage_OAuth_Model_Resource_Nonce getResource() + * @method Mage_OAuth_Model_Resource_Nonce _getResource() + */ +class Mage_OAuth_Model_Nonce extends Mage_Core_Model_Abstract +{ + /** + * Initialize resource model + * + * @return void + */ + protected function _construct() + { + $this->_init('oauth/nonce'); + } + + /** + * "After save" actions + * + * @return Mage_OAuth_Model_Nonce + */ + protected function _afterSave() + { + parent::_afterSave(); + + //Cleanup old entries + /** @var $helper Mage_OAuth_Helper_Data */ + $helper = Mage::helper('oauth'); + if ($helper->isCleanupProbability()) { + $this->_getResource()->deleteOldEntries($helper->getCleanupExpirationPeriod()); + } + return $this; + } +} diff --git a/app/code/core/Mage/OAuth/Model/Observer.php b/app/code/core/Mage/OAuth/Model/Observer.php new file mode 100644 index 0000000000..6232a69b2c --- /dev/null +++ b/app/code/core/Mage/OAuth/Model/Observer.php @@ -0,0 +1,131 @@ + + */ +class Mage_OAuth_Model_Observer +{ + /** + * Get callback url + * + * @param string $userType + * @return string + */ + protected function _getAfterAuthUrl($userType) + { + $simple = Mage::app()->getRequest()->getParam('simple'); + + if (Mage_OAuth_Model_Token::USER_TYPE_CUSTOMER == $userType) { + if ($simple) { + $route = Mage_OAuth_Helper_Data::ENDPOINT_AUTHORIZE_CUSTOMER_SIMPLE; + } else { + $route = Mage_OAuth_Helper_Data::ENDPOINT_AUTHORIZE_CUSTOMER; + } + } elseif (Mage_OAuth_Model_Token::USER_TYPE_ADMIN == $userType) { + if ($simple) { + $route = Mage_OAuth_Helper_Data::ENDPOINT_AUTHORIZE_ADMIN_SIMPLE; + } else { + $route = Mage_OAuth_Helper_Data::ENDPOINT_AUTHORIZE_ADMIN; + } + } else { + throw new Exception('Invalid user type.'); + } + + return Mage::getUrl($route, array('_query' => array('oauth_token' => $this->_getOauthToken()))); + } + + /** + * Retrieve oauth_token param from request + * + * @return string|null + */ + protected function _getOauthToken() + { + return Mage::app()->getRequest()->getParam('oauth_token', null); + } + + /** + * Redirect customer to callback page after login success + * + * @param Varien_Event_Observer $observer + * @return void + */ + public function afterCustomerLogin(Varien_Event_Observer $observer) + { + if (null !== $this->_getOauthToken()) { + /** @var $session Mage_Customer_Model_Session */ + $session = Mage::getSingleton('customer/session'); + $session->setAfterAuthUrl($this->_getAfterAuthUrl(Mage_OAuth_Model_Token::USER_TYPE_CUSTOMER)); + } + } + + /** + * Redirect admin to authorize controller after login success + * + * @param Varien_Event_Observer $observer + * @return void + */ + public function afterAdminLogin(Varien_Event_Observer $observer) + { + if (null !== $this->_getOauthToken()) { + $userType = Mage_OAuth_Model_Token::USER_TYPE_ADMIN; + + $url = $this->_getAfterAuthUrl($userType); + Mage::app()->getResponse() + ->setRedirect($url) + ->sendHeaders() + ->sendResponse(); + } + } + + public function afterAdminLoginFailed(Varien_Event_Observer $observer) + { + if (null !== $this->_getOauthToken()) { + /** @var $session Mage_Admin_Model_Session */ + $session = Mage::getSingleton('admin/session'); + $session->addError($observer->getException()->getMessage()); + + $params = array('oauth_token' => $this->_getOauthToken()); + + if (Mage::app()->getRequest()->getParam('simple')) { + $route = Mage_OAuth_Helper_Data::ENDPOINT_AUTHORIZE_ADMIN_SIMPLE; + } else { + $route = Mage_OAuth_Helper_Data::ENDPOINT_AUTHORIZE_ADMIN; + } + $url = Mage::getUrl($route, array('_query' => $params)); + + Mage::app()->getResponse() + ->setRedirect($url) + ->sendHeaders() + ->sendResponse(); + } + } +} diff --git a/app/code/core/Mage/OAuth/Model/Resource/Consumer.php b/app/code/core/Mage/OAuth/Model/Resource/Consumer.php new file mode 100644 index 0000000000..ade3cad2af --- /dev/null +++ b/app/code/core/Mage/OAuth/Model/Resource/Consumer.php @@ -0,0 +1,45 @@ + + */ +class Mage_OAuth_Model_Resource_Consumer extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Initialize resource model + * + * @return void + */ + protected function _construct() + { + $this->_init('oauth/consumer', 'entity_id'); + } +} diff --git a/app/code/core/Mage/OAuth/Model/Resource/Consumer/Collection.php b/app/code/core/Mage/OAuth/Model/Resource/Consumer/Collection.php new file mode 100644 index 0000000000..b9e5859e86 --- /dev/null +++ b/app/code/core/Mage/OAuth/Model/Resource/Consumer/Collection.php @@ -0,0 +1,45 @@ + + */ +class Mage_OAuth_Model_Resource_Consumer_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Initialize collection model + * + * @return void + */ + protected function _construct() + { + $this->_init('oauth/consumer'); + } +} diff --git a/app/code/core/Mage/OAuth/Model/Resource/Nonce.php b/app/code/core/Mage/OAuth/Model/Resource/Nonce.php new file mode 100644 index 0000000000..eaa744df57 --- /dev/null +++ b/app/code/core/Mage/OAuth/Model/Resource/Nonce.php @@ -0,0 +1,64 @@ + + */ +class Mage_OAuth_Model_Resource_Nonce extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Initialize resource model + * + * @return void + */ + protected function _construct() + { + $this->_init('oauth/nonce', null); + } + + /** + * Delete old entries + * + * @param int $minutes Delete entries older than + * @return int + */ + public function deleteOldEntries($minutes) + { + if ($minutes > 0) { + $adapter = $this->_getWriteAdapter(); + + return $adapter->delete( + $this->getMainTable(), $adapter->quoteInto('timestamp <= ?', time() - $minutes * 60, Zend_Db::INT_TYPE) + ); + } else { + return 0; + } + } +} diff --git a/app/code/core/Mage/OAuth/Model/Resource/Nonce/Collection.php b/app/code/core/Mage/OAuth/Model/Resource/Nonce/Collection.php new file mode 100644 index 0000000000..542e113448 --- /dev/null +++ b/app/code/core/Mage/OAuth/Model/Resource/Nonce/Collection.php @@ -0,0 +1,45 @@ + + */ +class Mage_OAuth_Model_Resource_Nonce_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Initialize collection model + * + * @return void + */ + protected function _construct() + { + $this->_init('oauth/nonce'); + } +} diff --git a/app/code/core/Mage/OAuth/Model/Resource/Setup.php b/app/code/core/Mage/OAuth/Model/Resource/Setup.php new file mode 100644 index 0000000000..facf382388 --- /dev/null +++ b/app/code/core/Mage/OAuth/Model/Resource/Setup.php @@ -0,0 +1,36 @@ + + */ +class Mage_OAuth_Model_Resource_Setup extends Mage_Core_Model_Resource_Setup +{ +} diff --git a/app/code/core/Mage/OAuth/Model/Resource/Token.php b/app/code/core/Mage/OAuth/Model/Resource/Token.php new file mode 100644 index 0000000000..9c16fcfc98 --- /dev/null +++ b/app/code/core/Mage/OAuth/Model/Resource/Token.php @@ -0,0 +1,95 @@ + + */ +class Mage_OAuth_Model_Resource_Token extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Initialize resource model + * + * @return void + */ + protected function _construct() + { + $this->_init('oauth/token', 'entity_id'); + } + + /** + * Clean up old authorized tokens for specified consumer-user pairs + * + * @param Mage_OAuth_Model_Token $exceptToken Token just created to exclude from delete + * @return int The number of affected rows + */ + public function cleanOldAuthorizedTokensExcept(Mage_OAuth_Model_Token $exceptToken) + { + if (!$exceptToken->getId() || !$exceptToken->getAuthorized()) { + Mage::throwException('Invalid token to except'); + } + $adapter = $this->_getWriteAdapter(); + $where = $adapter->quoteInto( + 'authorized = 1 AND consumer_id = ?', $exceptToken->getConsumerId(), Zend_Db::INT_TYPE + ); + $where .= $adapter->quoteInto(' AND entity_id <> ?', $exceptToken->getId(), Zend_Db::INT_TYPE); + + if ($exceptToken->getCustomerId()) { + $where .= $adapter->quoteInto(' AND customer_id = ?', $exceptToken->getCustomerId(), Zend_Db::INT_TYPE); + } elseif ($exceptToken->getAdminId()) { + $where .= $adapter->quoteInto(' AND admin_id = ?', $exceptToken->getAdminId(), Zend_Db::INT_TYPE); + } else { + Mage::throwException('Invalid token to except'); + } + return $adapter->delete($this->getMainTable(), $where); + } + + /** + * Delete old entries + * + * @param int $minutes + * @return int + */ + public function deleteOldEntries($minutes) + { + if ($minutes > 0) { + $adapter = $this->_getWriteAdapter(); + + return $adapter->delete( + $this->getMainTable(), + $adapter->quoteInto( + 'type = "' . Mage_OAuth_Model_Token::TYPE_REQUEST . '" AND created_at <= ?', + Varien_Date::formatDate(time() - $minutes * 60) + ) + ); + } else { + return 0; + } + } +} diff --git a/app/code/core/Mage/OAuth/Model/Resource/Token/Collection.php b/app/code/core/Mage/OAuth/Model/Resource/Token/Collection.php new file mode 100644 index 0000000000..418941f8e6 --- /dev/null +++ b/app/code/core/Mage/OAuth/Model/Resource/Token/Collection.php @@ -0,0 +1,136 @@ + + */ +class Mage_OAuth_Model_Resource_Token_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Initialize collection model + * + * @return void + */ + protected function _construct() + { + $this->_init('oauth/token'); + } + + /** + * Load collection with consumer data + * + * Method use for show applications list (token-consumer) + * + * @return Mage_OAuth_Model_Resource_Token_Collection + */ + public function joinConsumerAsApplication() + { + $select = $this->getSelect(); + $select->joinLeft( + array('c' => $this->getTable('oauth/consumer')), + 'c.entity_id = main_table.consumer_id', + 'name' + ); + + return $this; + } + + /** + * Add filter by admin ID + * + * @param int $adminId + * @return Mage_OAuth_Model_Resource_Token_Collection + */ + public function addFilterByAdminId($adminId) + { + $this->addFilter('main_table.admin_id', $adminId); + return $this; + } + + /** + * Add filter by customer ID + * + * @param int $customerId + * @return Mage_OAuth_Model_Resource_Token_Collection + */ + public function addFilterByCustomerId($customerId) + { + $this->addFilter('main_table.customer_id', $customerId); + return $this; + } + + /** + * Add filter by consumer ID + * + * @param int $consumerId + * @return Mage_OAuth_Model_Resource_Token_Collection + */ + public function addFilterByConsumerId($consumerId) + { + $this->addFilter('main_table.consumer_id', $consumerId); + return $this; + } + + /** + * Add filter by type + * + * @param string $type + * @return Mage_OAuth_Model_Resource_Token_Collection + */ + public function addFilterByType($type) + { + $this->addFilter('main_table.type', $type); + return $this; + } + + /** + * Add filter by ID + * + * @param array|int $id + * @return Mage_OAuth_Model_Resource_Token_Collection + */ + public function addFilterById($id) + { + $this->addFilter('main_table.entity_id', array('in' => $id), 'public'); + return $this; + } + + /** + * Add filter by "Is Revoked" status + * + * @param bool|int $flag + * @return Mage_OAuth_Model_Resource_Token_Collection + */ + public function addFilterByRevoked($flag) + { + $this->addFilter('main_table.revoked', (int) $flag, 'public'); + return $this; + } +} diff --git a/app/code/core/Mage/OAuth/Model/Server.php b/app/code/core/Mage/OAuth/Model/Server.php new file mode 100644 index 0000000000..892260ad37 --- /dev/null +++ b/app/code/core/Mage/OAuth/Model/Server.php @@ -0,0 +1,722 @@ + + */ +class Mage_OAuth_Model_Server +{ + /**#@+ + * OAuth result statuses + */ + const ERR_OK = 0; + const ERR_VERSION_REJECTED = 1; + const ERR_PARAMETER_ABSENT = 2; + const ERR_PARAMETER_REJECTED = 3; + const ERR_TIMESTAMP_REFUSED = 4; + const ERR_NONCE_USED = 5; + const ERR_SIGNATURE_METHOD_REJECTED = 6; + const ERR_SIGNATURE_INVALID = 7; + const ERR_CONSUMER_KEY_REJECTED = 8; + const ERR_TOKEN_USED = 9; + const ERR_TOKEN_EXPIRED = 10; + const ERR_TOKEN_REVOKED = 11; + const ERR_TOKEN_REJECTED = 12; + const ERR_VERIFIER_INVALID = 13; + const ERR_PERMISSION_UNKNOWN = 14; + const ERR_PERMISSION_DENIED = 15; + /**#@-*/ + + /**#@+ + * Signature Methods + */ + const SIGNATURE_HMAC = 'HMAC-SHA1'; + const SIGNATURE_RSA = 'RSA-SHA1'; + const SIGNATURE_PLAIN = 'PLAINTEXT'; + /**#@-*/ + + /**#@+ + * Request Types + */ + const REQUEST_INITIATE = 'initiate'; // ask for temporary credentials + const REQUEST_AUTHORIZE = 'authorize'; // display authorize form + const REQUEST_TOKEN = 'token'; // ask for permanent credentials + const REQUEST_RESOURCE = 'resource'; // ask for protected resource using permanent credentials + /**#@-*/ + + /**#@+ + * HTTP Response Codes + */ + const HTTP_OK = 200; + const HTTP_BAD_REQUEST = 400; + const HTTP_UNAUTHORIZED = 401; + const HTTP_INTERNAL_ERROR = 500; + /**#@-*/ + + /** + * Possible time deviation for timestamp validation in sec. + */ + const TIME_DEVIATION = 600; + + /** + * Value of callback URL when it is established or if cliaent is unable to receive callbacks + * + * @link http://tools.ietf.org/html/rfc5849#section-2.1 Requirement in RFC-5849 + */ + const CALLBACK_ESTABLISHED = 'oob'; + + /** + * Consumer object + * + * @var Mage_OAuth_Model_Consumer + */ + protected $_consumer; + + /** + * Error code to error messages pairs + * + * @var array + */ + protected $_errors = array( + self::ERR_VERSION_REJECTED => 'version_rejected', + self::ERR_PARAMETER_ABSENT => 'parameter_absent', + self::ERR_PARAMETER_REJECTED => 'parameter_rejected', + self::ERR_TIMESTAMP_REFUSED => 'timestamp_refused', + self::ERR_NONCE_USED => 'nonce_used', + self::ERR_SIGNATURE_METHOD_REJECTED => 'signature_method_rejected', + self::ERR_SIGNATURE_INVALID => 'signature_invalid', + self::ERR_CONSUMER_KEY_REJECTED => 'consumer_key_rejected', + self::ERR_TOKEN_USED => 'token_used', + self::ERR_TOKEN_EXPIRED => 'token_expired', + self::ERR_TOKEN_REVOKED => 'token_revoked', + self::ERR_TOKEN_REJECTED => 'token_rejected', + self::ERR_VERIFIER_INVALID => 'verifier_invalid', + self::ERR_PERMISSION_UNKNOWN => 'permission_unknown', + self::ERR_PERMISSION_DENIED => 'permission_denied' + ); + + /** + * Error code to HTTP error code + * + * @var array + */ + protected $_errorsToHttpCode = array( + self::ERR_VERSION_REJECTED => self::HTTP_BAD_REQUEST, + self::ERR_PARAMETER_ABSENT => self::HTTP_BAD_REQUEST, + self::ERR_PARAMETER_REJECTED => self::HTTP_BAD_REQUEST, + self::ERR_TIMESTAMP_REFUSED => self::HTTP_BAD_REQUEST, + self::ERR_NONCE_USED => self::HTTP_UNAUTHORIZED, + self::ERR_SIGNATURE_METHOD_REJECTED => self::HTTP_BAD_REQUEST, + self::ERR_SIGNATURE_INVALID => self::HTTP_UNAUTHORIZED, + self::ERR_CONSUMER_KEY_REJECTED => self::HTTP_UNAUTHORIZED, + self::ERR_TOKEN_USED => self::HTTP_UNAUTHORIZED, + self::ERR_TOKEN_EXPIRED => self::HTTP_UNAUTHORIZED, + self::ERR_TOKEN_REVOKED => self::HTTP_UNAUTHORIZED, + self::ERR_TOKEN_REJECTED => self::HTTP_UNAUTHORIZED, + self::ERR_VERIFIER_INVALID => self::HTTP_UNAUTHORIZED, + self::ERR_PERMISSION_UNKNOWN => self::HTTP_UNAUTHORIZED, + self::ERR_PERMISSION_DENIED => self::HTTP_UNAUTHORIZED + ); + + /** + * Request parameters + * + * @var array + */ + protected $_params = array(); + + /** + * Protocol parameters + * + * @var array + */ + protected $_protocolParams = array(); + + /** + * Request object + * + * @var Mage_Core_Controller_Request_Http + */ + protected $_request; + + /** + * Request type: initiate, permanent token request or authorized one + * + * @var string + */ + protected $_requestType; + + /** + * Response object + * + * @var Zend_Controller_Response_Http + */ + protected $_response = null; + + /** + * Token object + * + * @var Mage_OAuth_Model_Token + */ + protected $_token; + + /** + * Internal constructor not depended on params + * + * @param Zend_Controller_Request_Http $request OPTIONAL Request object (If not specified - use singleton) + * @throws Exception + */ + public function __construct($request = null) + { + if (is_object($request)) { + if (!$request instanceof Zend_Controller_Request_Http) { + throw new Exception('Invalid request object passed'); + } + $this->_request = $request; + } else { + $this->_request = Mage::app()->getRequest(); + } + } + + /** + * Retrieve protocol and request parameters from request object + * + * @link http://tools.ietf.org/html/rfc5849#section-3.5 + * @return Mage_OAuth_Model_Server + */ + protected function _fetchParams() + { + $authHeaderValue = $this->_request->getHeader('Authorization'); + + if ($authHeaderValue && 'oauth' === strtolower(substr($authHeaderValue, 0, 5))) { + $authHeaderValue = substr($authHeaderValue, 6); // ignore 'OAuth ' at the beginning + + foreach (explode(',', $authHeaderValue) as $paramStr) { + $nameAndValue = explode('=', trim($paramStr), 2); + + if (count($nameAndValue) < 2) { + continue; + } + if ($this->_isProtocolParameter($nameAndValue[0])) { + $this->_protocolParams[rawurldecode($nameAndValue[0])] = rawurldecode(trim($nameAndValue[1], '"')); + } + } + } + $contentTypeHeader = $this->_request->getHeader(Zend_Http_Client::CONTENT_TYPE); + + if ($contentTypeHeader && 0 === strpos($contentTypeHeader, Zend_Http_Client::ENC_URLENCODED)) { + $protocolParamsNotSet = !$this->_protocolParams; + + parse_str($this->_request->getRawBody(), $bodyParams); + + foreach ($bodyParams as $bodyParamName => $bodyParamValue) { + if (!$this->_isProtocolParameter($bodyParamName)) { + $this->_params[$bodyParamName] = $bodyParamValue; + } elseif ($protocolParamsNotSet) { + $this->_protocolParams[$bodyParamName] = $bodyParamValue; + } + } + } + $protocolParamsNotSet = !$this->_protocolParams; + + $url = $this->_request->getScheme() . '://' . $this->_request->getHttpHost() . $this->_request->getRequestUri(); + + if (($queryString = Zend_Uri_Http::fromString($url)->getQuery())) { + foreach (explode('&', $queryString) as $paramToValue) { + $paramData = explode('=', $paramToValue); + + if (2 === count($paramData) && !$this->_isProtocolParameter($paramData[0])) { + $this->_params[rawurldecode($paramData[0])] = rawurldecode($paramData[1]); + } + } + } + if ($protocolParamsNotSet) { + $this->_fetchProtocolParamsFromQuery(); + } + return $this; + } + + /** + * Retrieve protocol parameters from query string + * + * @return Mage_OAuth_Model_Server + */ + protected function _fetchProtocolParamsFromQuery() + { + foreach ($this->_request->getQuery() as $queryParamName => $queryParamValue) { + if ($this->_isProtocolParameter($queryParamName)) { + $this->_protocolParams[$queryParamName] = $queryParamValue; + } + } + return $this; + } + + /** + * Retrieve response object + * + * @return Zend_Controller_Response_Http + */ + protected function _getResponse() + { + if (null === $this->_response) { + $this->setResponse(Mage::app()->getResponse()); + } + return $this->_response; + } + + /** + * Initialize consumer + * + * @throws Mage_OAuth_Exception + */ + protected function _initConsumer() + { + $this->_consumer = Mage::getModel('oauth/consumer'); + + $this->_consumer->load($this->_protocolParams['oauth_consumer_key'], 'key'); + + if (!$this->_consumer->getId()) { + $this->_throwException('', self::ERR_CONSUMER_KEY_REJECTED); + } + } + + /** + * Load token object, validate it depending on request type, set access data and save + * + * @return Mage_OAuth_Model_Server + * @throws Mage_OAuth_Exception + */ + protected function _initToken() + { + $this->_token = Mage::getModel('oauth/token'); + + if (self::REQUEST_INITIATE != $this->_requestType) { + $this->_validateTokenParam(); + + $this->_token->load($this->_protocolParams['oauth_token'], 'token'); + + if (!$this->_token->getId()) { + $this->_throwException('', self::ERR_TOKEN_REJECTED); + } + if (self::REQUEST_TOKEN == $this->_requestType) { + $this->_validateVerifierParam(); + + if ($this->_token->getVerifier() != $this->_protocolParams['oauth_verifier']) { + $this->_throwException('', self::ERR_VERIFIER_INVALID); + } + if ($this->_token->getConsumerId() != $this->_consumer->getId()) { + $this->_throwException('', self::ERR_TOKEN_REJECTED); + } + if (Mage_OAuth_Model_Token::TYPE_REQUEST != $this->_token->getType()) { + $this->_throwException('', self::ERR_TOKEN_USED); + } + } elseif (self::REQUEST_AUTHORIZE == $this->_requestType) { + if ($this->_token->getAuthorized()) { + $this->_throwException('', self::ERR_TOKEN_USED); + } + } elseif (self::REQUEST_RESOURCE == $this->_requestType) { + if (Mage_OAuth_Model_Token::TYPE_ACCESS != $this->_token->getType()) { + $this->_throwException('', self::ERR_TOKEN_REJECTED); + } + if ($this->_token->getRevoked()) { + $this->_throwException('', self::ERR_TOKEN_REVOKED); + } + //TODO: Implement check for expiration (after it implemented in token model) + } + } else { + $this->_validateCallbackUrlParam(); + } + return $this; + } + + /** + * Is attribute is referred to oAuth protocol? + * + * @param string $attrName + * @return bool + */ + protected function _isProtocolParameter($attrName) + { + return (bool) preg_match('/oauth_[a-z_-]+/', $attrName); + } + + /** + * Extract parameters from sources (GET, FormBody, Authorization header), decode them and validate + * + * @param string $requestType Request type - one of REQUEST_... class constant + * @return Mage_OAuth_Model_Server + * @throws Mage_Core_Exception + */ + protected function _processRequest($requestType) + { + // validate request type to process (AUTHORIZE request is not allowed for method) + if (self::REQUEST_INITIATE != $requestType + && self::REQUEST_RESOURCE != $requestType + && self::REQUEST_TOKEN != $requestType + ) { + Mage::throwException('Invalid request type'); + } + $this->_requestType = $requestType; + + // get parameters from request + $this->_fetchParams(); + + // make generic validation of request parameters + $this->_validateProtocolParams(); + + // initialize consumer + $this->_initConsumer(); + + // initialize token + $this->_initToken(); + + // validate signature + $this->_validateSignature(); + + // save token if signature validation succeed + $this->_saveToken(); + + return $this; + } + + /** + * Save token + */ + protected function _saveToken() + { + if (self::REQUEST_INITIATE == $this->_requestType) { + if (self::CALLBACK_ESTABLISHED == $this->_protocolParams['oauth_callback'] + && $this->_consumer->getCallBackUrl()) { + $callbackUrl = $this->_consumer->getCallBackUrl(); + } else { + $callbackUrl = $this->_protocolParams['oauth_callback']; + } + $this->_token->createRequestToken($this->_consumer->getId(), $callbackUrl); + } elseif (self::REQUEST_TOKEN == $this->_requestType) { + $this->_token->convertToAccess(); + } + } + + /** + * Throw OAuth exception + * + * @param string $message Exception message + * @param int $code Exception code + * @throws Mage_OAuth_Exception + */ + protected function _throwException($message = '', $code = 0) + { + throw Mage::exception('Mage_OAuth', $message, $code); + } + + /** + * Check for 'oauth_callback' parameter + */ + protected function _validateCallbackUrlParam() + { + if (!isset($this->_protocolParams['oauth_callback'])) { + $this->_throwException('oauth_callback', self::ERR_PARAMETER_ABSENT); + } + if (!is_string($this->_protocolParams['oauth_callback'])) { + $this->_throwException('oauth_callback', self::ERR_PARAMETER_REJECTED); + } + if (self::CALLBACK_ESTABLISHED != $this->_protocolParams['oauth_callback'] + && !Zend_Uri::check($this->_protocolParams['oauth_callback']) + ) { + $this->_throwException('oauth_callback', self::ERR_PARAMETER_REJECTED); + } + } + + /** + * Validate nonce request data + * + * @param string $nonce Nonce string + * @param string|int $timestamp UNIX Timestamp + */ + protected function _validateNonce($nonce, $timestamp) + { + $timestamp = (int) $timestamp; + + if ($timestamp <= 0 || $timestamp > (time() + self::TIME_DEVIATION)) { + $this->_throwException('', self::ERR_TIMESTAMP_REFUSED); + } + /** @var $nonceObj Mage_OAuth_Model_Nonce */ + $nonceObj = Mage::getModel('oauth/nonce'); + + $nonceObj->load($nonce, 'nonce'); + + if ($nonceObj->getTimestamp() == $timestamp) { + $this->_throwException('', self::ERR_NONCE_USED); + } + $nonceObj->setNonce($nonce) + ->setTimestamp($timestamp) + ->save(); + } + + /** + * Validate protocol parameters + * + * @throws Mage_OAuth_Exception + */ + protected function _validateProtocolParams() + { + // validate version if specified + if (isset($this->_protocolParams['oauth_version']) && '1.0' != $this->_protocolParams['oauth_version']) { + $this->_throwException('', self::ERR_VERSION_REJECTED); + } + // required parameters validation + foreach (array('oauth_consumer_key', 'oauth_signature_method', 'oauth_signature') as $reqField) { + if (empty($this->_protocolParams[$reqField])) { + $this->_throwException($reqField, self::ERR_PARAMETER_ABSENT); + } + } + // validate parameters type + foreach ($this->_protocolParams as $paramName => $paramValue) { + if (!is_string($paramValue)) { + $this->_throwException($paramName, self::ERR_PARAMETER_REJECTED); + } + } + // validate consumer key length + if (strlen($this->_protocolParams['oauth_consumer_key']) != Mage_OAuth_Model_Consumer::KEY_LENGTH) { + $this->_throwException('', self::ERR_CONSUMER_KEY_REJECTED); + } + // validate signature method + if (!in_array($this->_protocolParams['oauth_signature_method'], self::getSupportedSignatureMethods())) { + $this->_throwException('', self::ERR_SIGNATURE_METHOD_REJECTED); + } + // validate nonce data if signature method is not PLAINTEXT + if (self::SIGNATURE_PLAIN != $this->_protocolParams['oauth_signature_method']) { + if (empty($this->_protocolParams['oauth_nonce'])) { + $this->_throwException('oauth_nonce', self::ERR_PARAMETER_ABSENT); + } + if (empty($this->_protocolParams['oauth_timestamp'])) { + $this->_throwException('oauth_timestamp', self::ERR_PARAMETER_ABSENT); + } + $this->_validateNonce($this->_protocolParams['oauth_nonce'], $this->_protocolParams['oauth_timestamp']); + } + } + + /** + * Validate signature + * + * @throws Mage_OAuth_Exception + */ + protected function _validateSignature() + { + $util = new Zend_Oauth_Http_Utility(); + + $calculatedSign = $util->sign( + array_merge($this->_params, $this->_protocolParams), + $this->_protocolParams['oauth_signature_method'], + $this->_consumer->getSecret(), + $this->_token->getSecret(), + $this->_request->getMethod(), + $this->_request->getScheme() . '://' . $this->_request->getHttpHost() . $this->_request->getRequestUri() + ); + + if ($calculatedSign != $this->_protocolParams['oauth_signature']) { + $this->_throwException($calculatedSign, self::ERR_SIGNATURE_INVALID); + } + } + + /** + * Check for 'oauth_token' parameter + */ + protected function _validateTokenParam() + { + if (empty($this->_protocolParams['oauth_token'])) { + $this->_throwException('oauth_token', self::ERR_PARAMETER_ABSENT); + } + if (!is_string($this->_protocolParams['oauth_token'])) { + $this->_throwException('', self::ERR_TOKEN_REJECTED); + } + if (strlen($this->_protocolParams['oauth_token']) != Mage_OAuth_Model_Token::LENGTH_TOKEN) { + $this->_throwException('', self::ERR_TOKEN_REJECTED); + } + } + + /** + * Check for 'oauth_verifier' parameter + */ + protected function _validateVerifierParam() + { + if (empty($this->_protocolParams['oauth_verifier'])) { + $this->_throwException('oauth_verifier', self::ERR_PARAMETER_ABSENT); + } + if (!is_string($this->_protocolParams['oauth_verifier'])) { + $this->_throwException('', self::ERR_VERIFIER_INVALID); + } + if (strlen($this->_protocolParams['oauth_verifier']) != Mage_OAuth_Model_Token::LENGTH_VERIFIER) { + $this->_throwException('', self::ERR_VERIFIER_INVALID); + } + } + + /** + * Process request for permanent access token + */ + public function accessToken() + { + try { + $this->_processRequest(self::REQUEST_TOKEN); + + $response = $this->_token->toString(); + } catch (Exception $e) { + $response = $this->reportProblem($e); + } + $this->_getResponse()->setBody($response); + } + + /** + * Validate request, authorize token and return it + * + * @param int $userId Authorization user identifier + * @param string $userType Authorization user type + * @return Mage_OAuth_Model_Token + */ + public function authorizeToken($userId, $userType) + { + $token = $this->checkAuthorizeRequest(); + + $token->authorize($userId, $userType); + + return $token; + } + + /** + * Validate request with access token for specified URL + * + * @return Mage_OAuth_Model_Token + */ + public function checkAccessRequest() + { + $this->_processRequest(self::REQUEST_RESOURCE); + + return $this->_token; + } + + /** + * Check authorize request for validity and return token + * + * @return Mage_OAuth_Model_Token + */ + public function checkAuthorizeRequest() + { + if (!$this->_request->isGet()) { + Mage::throwException('Request is not GET'); + } + $this->_requestType = self::REQUEST_AUTHORIZE; + + $this->_fetchProtocolParamsFromQuery(); + $this->_initToken(); + + return $this->_token; + } + + /** + * Retrieve array of supported signature methods + * + * @return array + */ + public static function getSupportedSignatureMethods() + { + return array(self::SIGNATURE_RSA, self::SIGNATURE_HMAC, self::SIGNATURE_PLAIN); + } + + /** + * Process request for temporary (initiative) token + */ + public function initiateToken() + { + try { + $this->_processRequest(self::REQUEST_INITIATE); + + $response = $this->_token->toString() . '&oauth_callback_confirmed=true'; + } catch (Exception $e) { + $response = $this->reportProblem($e); + } + $this->_getResponse()->setBody($response); + } + + /** + * Create response string for problem during request and set HTTP error code + * + * @param Exception $e + * @param Zend_Controller_Response_Http $response OPTIONAL If NULL - will use internal getter + * @return string + */ + public function reportProblem(Exception $e, Zend_Controller_Response_Http $response = null) + { + $eMsg = $e->getMessage(); + + if ($e instanceof Mage_OAuth_Exception) { + $eCode = $e->getCode(); + + if (isset($this->_errors[$eCode])) { + $errorMsg = $this->_errors[$eCode]; + $responseCode = $this->_errorsToHttpCode[$eCode]; + } else { + $errorMsg = 'unknown_problem&code=' . $eCode; + $responseCode = self::HTTP_INTERNAL_ERROR; + } + if (self::ERR_PARAMETER_ABSENT == $eCode) { + $errorMsg .= '&oauth_parameters_absent=' . $eMsg; + } elseif (self::ERR_SIGNATURE_INVALID == $eCode) { + $errorMsg .= '&debug_sbs=' . $eMsg; + } elseif ($eMsg) { + $errorMsg .= '&message=' . $eMsg; + } + } else { + $errorMsg = 'internal_error&message=' . ($eMsg ? $eMsg : 'empty_message'); + $responseCode = self::HTTP_INTERNAL_ERROR; + } + if (!$response) { + $response = $this->_getResponse(); + } + $response->setHttpResponseCode($responseCode); + + return 'oauth_problem=' . $errorMsg; + } + + /** + * Set response object + * + * @param Zend_Controller_Response_Http $response + * @return Mage_OAuth_Model_Server + */ + public function setResponse(Zend_Controller_Response_Http $response) + { + $this->_response = $response; + + $this->_response->setHeader(Zend_Http_Client::CONTENT_TYPE, Zend_Http_Client::ENC_URLENCODED, true); + $this->_response->setHttpResponseCode(self::HTTP_OK); + + return $this; + } +} diff --git a/app/code/core/Mage/OAuth/Model/Token.php b/app/code/core/Mage/OAuth/Model/Token.php new file mode 100644 index 0000000000..404e9cb00a --- /dev/null +++ b/app/code/core/Mage/OAuth/Model/Token.php @@ -0,0 +1,297 @@ + + * @method string getName() Consumer name (joined from consumer table) + * @method Mage_OAuth_Model_Resource_Token_Collection getCollection() + * @method Mage_OAuth_Model_Resource_Token_Collection getResourceCollection() + * @method Mage_OAuth_Model_Resource_Token getResource() + * @method Mage_OAuth_Model_Resource_Token _getResource() + * @method int getConsumerId() + * @method Mage_OAuth_Model_Token setConsumerId() setConsumerId(int $consumerId) + * @method int getAdminId() + * @method Mage_OAuth_Model_Token setAdminId() setAdminId(int $adminId) + * @method int getCustomerId() + * @method Mage_OAuth_Model_Token setCustomerId() setCustomerId(int $customerId) + * @method string getType() + * @method Mage_OAuth_Model_Token setType() setType(string $type) + * @method string getVerifier() + * @method Mage_OAuth_Model_Token setVerifier() setVerifier(string $verifier) + * @method string getCallbackUrl() + * @method Mage_OAuth_Model_Token setCallbackUrl() setCallbackUrl(string $callbackUrl) + * @method string getCreatedAt() + * @method Mage_OAuth_Model_Token setCreatedAt() setCreatedAt(string $createdAt) + * @method string getToken() + * @method Mage_OAuth_Model_Token setToken() setToken(string $token) + * @method string getSecret() + * @method Mage_OAuth_Model_Token setSecret() setSecret(string $tokenSecret) + * @method int getRevoked() + * @method Mage_OAuth_Model_Token setRevoked() setRevoked(int $revoked) + * @method int getAuthorized() + * @method Mage_OAuth_Model_Token setAuthorized() setAuthorized(int $authorized) + */ +class Mage_OAuth_Model_Token extends Mage_Core_Model_Abstract +{ + /**#@+ + * Token types + */ + const TYPE_REQUEST = 'request'; + const TYPE_ACCESS = 'access'; + /**#@- */ + + /**#@+ + * Lengths of token fields + */ + const LENGTH_TOKEN = 32; + const LENGTH_SECRET = 32; + const LENGTH_VERIFIER = 32; + /**#@- */ + + /**#@+ + * Customer types + */ + const USER_TYPE_ADMIN = 'admin'; + const USER_TYPE_CUSTOMER = 'customer'; + /**#@- */ + + /** + * Initialize resource model + * + * @return void + */ + protected function _construct() + { + $this->_init('oauth/token'); + } + + /** + * "After save" actions + * + * @return Mage_OAuth_Model_Token + */ + protected function _afterSave() + { + parent::_afterSave(); + + //Cleanup old entries + /** @var $helper Mage_OAuth_Helper_Data */ + $helper = Mage::helper('oauth'); + if ($helper->isCleanupProbability()) { + $this->_getResource()->deleteOldEntries($helper->getCleanupExpirationPeriod()); + } + return $this; + } + + /** + * Authorize token + * + * @param int $userId Authorization user identifier + * @param string $userType Authorization user type + * @return Mage_OAuth_Model_Token + */ + public function authorize($userId, $userType) + { + if (!$this->getId() || !$this->getConsumerId()) { + Mage::throwException('Token is not ready to be authorized'); + } + if ($this->getAuthorized()) { + Mage::throwException('Token is already authorized'); + } + if (self::USER_TYPE_ADMIN == $userType) { + $this->setAdminId($userId); + } elseif (self::USER_TYPE_CUSTOMER == $userType) { + $this->setCustomerId($userId); + } else { + Mage::throwException('User type is unknown'); + } + /** @var $helper Mage_OAuth_Helper_Data */ + $helper = Mage::helper('oauth'); + + $this->setVerifier($helper->generateVerifier()); + $this->setAuthorized(1); + $this->save(); + + $this->getResource()->cleanOldAuthorizedTokensExcept($this); + + return $this; + } + + /** + * Convert token to access type + * + * @return Mage_OAuth_Model_Token + */ + public function convertToAccess() + { + if (Mage_OAuth_Model_Token::TYPE_REQUEST != $this->getType()) { + Mage::throwException('Can not convert due to token is not request type'); + } + /** @var $helper Mage_OAuth_Helper_Data */ + $helper = Mage::helper('oauth'); + + $this->setType(self::TYPE_ACCESS); + $this->setToken($helper->generateToken()); + $this->setSecret($helper->generateTokenSecret()); + $this->save(); + + return $this; + } + + /** + * Generate and save request token + * + * @param int $consumerId Consumer identifier + * @param string $callbackUrl Callback URL + * @return Mage_OAuth_Model_Token + */ + public function createRequestToken($consumerId, $callbackUrl) + { + /** @var $helper Mage_OAuth_Helper_Data */ + $helper = Mage::helper('oauth'); + + $this->setData(array( + 'consumer_id' => $consumerId, + 'type' => self::TYPE_REQUEST, + 'token' => $helper->generateToken(), + 'secret' => $helper->generateTokenSecret(), + 'callback_url' => $callbackUrl + )); + $this->save(); + + return $this; + } + + /** + * Get OAuth user type + * + * @return string + * @throws Exception + */ + public function getUserType() + { + if ($this->getAdminId()) { + return self::USER_TYPE_ADMIN; + } elseif ($this->getCustomerId()) { + return self::USER_TYPE_CUSTOMER; + } else { + Mage::throwException('User type is unknown'); + } + } + + /** + * Get string representation of token + * + * @param string $format + * @return string + */ + public function toString($format = '') + { + return http_build_query(array('oauth_token' => $this->getToken(), 'oauth_token_secret' => $this->getSecret())); + } + + /** + * Before save actions + * + * @return Mage_OAuth_Model_Consumer + */ + protected function _beforeSave() + { + $this->validate(); + + if ($this->isObjectNew() && null === $this->getCreatedAt()) { + $this->setCreatedAt(Varien_Date::now()); + } + parent::_beforeSave(); + return $this; + } + + /** + * Validate data + * + * @return array|bool + * @throw Mage_Core_Exception|Exception Throw exception on fail validation + */ + public function validate() + { + /** @var $validatorUrl Mage_Core_Model_Url_Validator */ + $validatorUrl = Mage::getSingleton('core/url_validator'); + if (Mage_OAuth_Model_Server::CALLBACK_ESTABLISHED != $this->getCallbackUrl() + && !$validatorUrl->isValid($this->getCallbackUrl()) + ) { + $messages = $validatorUrl->getMessages(); + Mage::throwException(array_shift($messages)); + } + + /** @var $validatorLength Mage_OAuth_Model_Consumer_Validator_KeyLength */ + $validatorLength = Mage::getModel( + 'oauth/consumer_validator_keyLength'); + $validatorLength->setLength(self::LENGTH_SECRET); + $validatorLength->setName('Token Secret Key'); + if (!$validatorLength->isValid($this->getSecret())) { + $messages = $validatorLength->getMessages(); + Mage::throwException(array_shift($messages)); + } + + $validatorLength->setLength(self::LENGTH_TOKEN); + $validatorLength->setName('Token Key'); + if (!$validatorLength->isValid($this->getToken())) { + $messages = $validatorLength->getMessages(); + Mage::throwException(array_shift($messages)); + } + + if (null !== ($verifier = $this->getVerifier())) { + $validatorLength->setLength(self::LENGTH_VERIFIER); + $validatorLength->setName('Verifier Key'); + if (!$validatorLength->isValid($verifier)) { + $messages = $validatorLength->getMessages(); + Mage::throwException(array_shift($messages)); + } + } + return true; + } + + /** + * Get Token Consumer + * + * @return Mage_OAuth_Model_Consumer + */ + public function getConsumer() + { + if (!$this->getData('consumer')) { + /** @var $consumer Mage_OAuth_Model_Consumer */ + $consumer = Mage::getModel('oauth/consumer'); + $consumer->load($this->getConsumerId()); + $this->setData('consumer', $consumer); + } + + return $this->getData('consumer'); + } +} diff --git a/app/code/core/Mage/OAuth/controllers/Adminhtml/OAuth/Admin/TokenController.php b/app/code/core/Mage/OAuth/controllers/Adminhtml/OAuth/Admin/TokenController.php new file mode 100644 index 0000000000..4e6a080454 --- /dev/null +++ b/app/code/core/Mage/OAuth/controllers/Adminhtml/OAuth/Admin/TokenController.php @@ -0,0 +1,174 @@ + + */ +class Mage_OAuth_Adminhtml_OAuth_Admin_TokenController extends Mage_Adminhtml_Controller_Action +{ + /** + * Init titles + * + * @return Mage_OAuth_Adminhtml_OAuth_Admin_TokenController + */ + public function preDispatch() + { + $this->_title($this->__('System')) + ->_title($this->__('Permissions')) + ->_title($this->__('My Applications')); + parent::preDispatch(); + return $this; + } + + /** + * Render grid page + */ + public function indexAction() + { + $this->loadLayout(); + $this->renderLayout(); + } + + /** + * Render grid AJAX request + */ + public function gridAction() + { + $this->loadLayout(); + $this->renderLayout(); + } + + /** + * Update revoke status action + */ + public function revokeAction() + { + $ids = $this->getRequest()->getParam('items'); + $status = $this->getRequest()->getParam('status'); + + if (!is_array($ids) || !$ids) { + // No rows selected + $this->_getSession()->addError($this->__('Please select needed row(s).')); + $this->_redirect('*/*/index'); + return; + } + + if (null === $status) { + // No status selected + $this->_getSession()->addError($this->__('Please select revoke status.')); + $this->_redirect('*/*/index'); + return; + } + + try { + /** @var $user Mage_Admin_Model_User */ + $user = Mage::getSingleton('admin/session')->getData('user'); + + /** @var $collection Mage_OAuth_Model_Resource_Token_Collection */ + $collection = Mage::getModel('oauth/token')->getCollection(); + $collection->joinConsumerAsApplication() + ->addFilterByAdminId($user->getId()) + ->addFilterByType(Mage_OAuth_Model_Token::TYPE_ACCESS) + ->addFilterById($ids) + ->addFilterByRevoked(!$status); + + /** @var $item Mage_OAuth_Model_Token */ + foreach ($collection as $item) { + $item->load($item->getId()); + $item->setRevoked($status)->save(); + } + if ($status) { + $message = $this->__('Selected entries revoked.'); + } else { + $message = $this->__('Selected entries enabled.'); + } + $this->_getSession()->addSuccess($message); + } catch (Mage_Core_Exception $e) { + $this->_getSession()->addError($e->getMessage()); + } catch (Exception $e) { + $this->_getSession()->addError($this->__('An error occurred on update revoke status.')); + Mage::logException($e); + } + $this->_redirect('*/*/index'); + } + + /** + * Delete action + */ + public function deleteAction() + { + $ids = $this->getRequest()->getParam('items'); + + if (!is_array($ids) || !$ids) { + // No rows selected + $this->_getSession()->addError($this->__('Please select needed row(s).')); + $this->_redirect('*/*/index'); + return; + } + + try { + /** @var $user Mage_Admin_Model_User */ + $user = Mage::getSingleton('admin/session')->getData('user'); + + /** @var $collection Mage_OAuth_Model_Resource_Token_Collection */ + $collection = Mage::getModel('oauth/token')->getCollection(); + $collection->joinConsumerAsApplication() + ->addFilterByAdminId($user->getId()) + ->addFilterByType(Mage_OAuth_Model_Token::TYPE_ACCESS) + ->addFilterById($ids); + + /** @var $item Mage_OAuth_Model_Token */ + foreach ($collection as $item) { + $item->delete(); + } + $this->_getSession()->addSuccess($this->__('Selected entries has been deleted.')); + } catch (Mage_Core_Exception $e) { + $this->_getSession()->addError($e->getMessage()); + } catch (Exception $e) { + $this->_getSession()->addError($this->__('An error occurred on delete action.')); + Mage::logException($e); + } + $this->_redirect('*/*/index'); + } + + /** + * Check admin permissions for this controller + * + * @return boolean + */ + protected function _isAllowed() + { + /** @var $session Mage_Admin_Model_Session */ + $session = Mage::getSingleton('admin/session'); + return $session->isAllowed('system/acl/admin_token'); + } +} diff --git a/app/code/core/Mage/OAuth/controllers/Adminhtml/OAuth/AuthorizeController.php b/app/code/core/Mage/OAuth/controllers/Adminhtml/OAuth/AuthorizeController.php new file mode 100644 index 0000000000..0fad0ac13e --- /dev/null +++ b/app/code/core/Mage/OAuth/controllers/Adminhtml/OAuth/AuthorizeController.php @@ -0,0 +1,264 @@ + + */ +class Mage_OAuth_Adminhtml_OAuth_AuthorizeController extends Mage_Adminhtml_Controller_Action +{ + /** + * Session name + * + * @var string + */ + protected $_sessionName = 'admin/session'; + + /** + * Array of actions which can be processed without secret key validation + * + * @var array + */ + public $_publicActions = array('index', 'simple', 'confirm', 'confirmSimple','reject', 'rejectSimple'); + + /** + * Disable showing of login form + * + * @see Mage_Admin_Model_Observer::actionPreDispatchAdmin() method for explanation + * @return void + */ + public function preDispatch() + { + $this->getRequest()->setParam('forwarded', true); + parent::preDispatch(); + } + + /** + * Index action. + * + * @return void + */ + public function indexAction() + { + $this->_initForm(); + + $this->_initLayoutMessages($this->_sessionName); + $this->renderLayout(); + } + + /** + * Index action with a simple design + * + * @return void + */ + public function simpleAction() + { + $this->_initForm(true); + $this->_initLayoutMessages($this->_sessionName); + $this->renderLayout(); + } + + /** + * Init authorize page + * + * @param bool $simple + * @return Mage_OAuth_Adminhtml_OAuth_AuthorizeController + */ + protected function _initForm($simple = false) + { + /** @var $server Mage_OAuth_Model_Server */ + $server = Mage::getModel('oauth/server'); + /** @var $session Mage_Admin_Model_Session */ + $session = Mage::getSingleton($this->_sessionName); + + $isException = false; + try { + $server->checkAuthorizeRequest(); + } catch (Mage_Core_Exception $e) { + $session->addError($e->getMessage()); + } catch (Mage_OAuth_Exception $e) { + $isException = true; + $session->addException($e, $this->__('An error occurred. Your authorization request is invalid.')); + } catch (Exception $e) { + $isException = true; + $session->addException($e, $this->__('An error occurred.')); + } + + $this->loadLayout(); + $layout = $this->getLayout(); + $logged = $session->isLoggedIn(); + + $contentBlock = $layout->getBlock('content'); + if ($logged) { + $contentBlock->unsetChild('oauth.authorize.form'); + /** @var $block Mage_OAuth_Block_Adminhtml_OAuth_Authorize_Button */ + $block = $contentBlock->getChild('oauth.authorize.button'); + } else { + $contentBlock->unsetChild('oauth.authorize.button'); + /** @var $block Mage_OAuth_Block_Adminhtml_OAuth_Authorize */ + $block = $contentBlock->getChild('oauth.authorize.form'); + } + + if ($simple) { + /** @var $style Mage_OAuth_Block_Authorize_Style */ + $style = $layout->getBlock('oauth.authorize.style'); + $style->setIsLogged($logged); + } + + $block->setIsSimple($simple) + ->setToken($this->getRequest()->getQuery('oauth_token')) + ->setHasException($isException); + return $this; + } + + /** + * Init confirm page + * + * @param bool $simple + * @return Mage_OAuth_Adminhtml_OAuth_AuthorizeController + */ + protected function _initConfirmPage($simple = false) + { + /** @var $session Mage_Admin_Model_Session */ + $session = Mage::getSingleton($this->_sessionName); + + /** @var $server Mage_OAuth_Model_Server */ + $server = Mage::getModel('oauth/server'); + + $this->loadLayout(); + + /** @var $block Mage_OAuth_Block_Adminhtml_OAuth_Authorize */ + $block = $this->getLayout()->getBlock('content')->getChild('oauth.authorize.confirm'); + $block->setIsSimple($simple); + + try { + /** @var $user Mage_Admin_Model_User */ + $user = $session->getData('user'); + $token = $server->authorizeToken($user->getId(), Mage_OAuth_Model_Token::USER_TYPE_ADMIN); + + /** @var $helper Mage_OAuth_Helper_Data */ + $helper = Mage::helper('oauth'); + + if (($callback = $helper->getFullCallbackUrl($token))) { //false in case of OOB + $this->getResponse()->setRedirect($callback . ($simple ? '&simple=1' : '')); + return $this; + } else { + $session->addSuccess($this->__('Authorization confirmed.')); + $block->setVerifier($token->getVerifier()); + } + } catch (Mage_Core_Exception $e) { + $block->setHasException(true); + $session->addError($e->getMessage()); + } catch (Exception $e) { + $block->setHasException(true); + $session->addException($e, $this->__('An error occurred on confirm authorize.')); + } + + $this->_initLayoutMessages($this->_sessionName); + $this->renderLayout(); + + return $this; + } + + /** + * Init reject page + * + * @param bool $simple + * @return Mage_OAuth_AuthorizeController + */ + protected function _initRejectPage($simple = false) + { + /** @var $server Mage_OAuth_Model_Server */ + $server = Mage::getModel('oauth/server'); + + /** @var $session Mage_Admin_Model_Session */ + $session = Mage::getSingleton($this->_sessionName); + + $this->loadLayout(); + + /** @var $block Mage_OAuth_Block_Authorize */ + $block = $this->getLayout()->getBlock('oauth.authorize.reject'); + $block->setIsSimple($simple); + + try { + $token = $server->checkAuthorizeRequest(); + /** @var $helper Mage_OAuth_Helper_Data */ + $helper = Mage::helper('oauth'); + + if (($callback = $helper->getFullCallbackUrl($token, true))) { + $this->_redirectUrl($callback . ($simple ? '&simple=1' : '')); + return $this; + } else { + $session->addNotice($this->__('The application access request is rejected.')); + } + } catch (Mage_Core_Exception $e) { + $session->addError($e->getMessage()); + } catch (Exception $e) { + $session->addException($e, $this->__('An error occurred on reject authorize.')); + } + + //display exception + $this->_initLayoutMessages($this->_sessionName); + $this->renderLayout(); + + return $this; + } + + /** + * Confirm token authorization action + */ + public function confirmAction() + { + $this->_initConfirmPage(); + } + + /** + * Confirm token authorization simple page + */ + public function confirmSimpleAction() + { + $this->_initConfirmPage(); + } + + /** + * Reject token authorization action + */ + public function rejectAction() + { + $this->_initRejectPage(); + } + + /** + * Reject token authorization simple page + */ + public function rejectSimpleAction() + { + $this->_initRejectPage(); + } +} diff --git a/app/code/core/Mage/OAuth/controllers/Adminhtml/OAuth/AuthorizedTokensController.php b/app/code/core/Mage/OAuth/controllers/Adminhtml/OAuth/AuthorizedTokensController.php new file mode 100644 index 0000000000..3252bf0ddd --- /dev/null +++ b/app/code/core/Mage/OAuth/controllers/Adminhtml/OAuth/AuthorizedTokensController.php @@ -0,0 +1,203 @@ + + */ +class Mage_OAuth_Adminhtml_OAuth_AuthorizedTokensController extends Mage_Adminhtml_Controller_Action +{ + /** + * Init titles + * + * @return Mage_OAuth_Adminhtml_OAuth_AuthorizedTokensController + */ + public function preDispatch() + { + $this->_title($this->__('System')) + ->_title($this->__('OAuth')) + ->_title($this->__('Authorized Tokens')); + parent::preDispatch(); + return $this; + } + + /** + * Render grid page + */ + public function indexAction() + { + $this->loadLayout()->_setActiveMenu('system/oauth'); + $this->renderLayout(); + } + + /** + * Render grid AJAX request + */ + public function gridAction() + { + $this->loadLayout(); + $this->renderLayout(); + } + + /** + * Update revoke status action + */ + public function revokeAction() + { + $ids = $this->getRequest()->getParam('items'); + $status = $this->getRequest()->getParam('status'); + + if (!is_array($ids) || !$ids) { + // No rows selected + $this->_getSession()->addError($this->__('Please select needed row(s).')); + $this->_redirect('*/*/index'); + return; + } + + if (null === $status) { + // No status selected + $this->_getSession()->addError($this->__('Please select revoke status.')); + $this->_redirect('*/*/index'); + return; + } + + try { + /** @var $collection Mage_OAuth_Model_Resource_Token_Collection */ + $collection = Mage::getModel('oauth/token')->getCollection(); + $collection->joinConsumerAsApplication() + ->addFilterByType(Mage_OAuth_Model_Token::TYPE_ACCESS) + ->addFilterById($ids) + ->addFilterByRevoked(!$status); + + /** @var $item Mage_OAuth_Model_Token */ + foreach ($collection as $item) { + $item->load($item->getId()); + $item->setRevoked($status)->save(); + + $this->_sendTokenStatusChangeNotification($item, $status ? $this->__('revoked') : $this->__('enabled')); + } + if ($status) { + $message = $this->__('Selected entries revoked.'); + } else { + $message = $this->__('Selected entries enabled.'); + } + $this->_getSession()->addSuccess($message); + } catch (Mage_Core_Exception $e) { + $this->_getSession()->addError($e->getMessage()); + } catch (Exception $e) { + $this->_getSession()->addError($this->__('An error occurred on update revoke status.')); + Mage::logException($e); + } + $this->_redirect('*/*/index'); + } + + /** + * Delete action + */ + public function deleteAction() + { + $ids = $this->getRequest()->getParam('items'); + + if (!is_array($ids) || !$ids) { + // No rows selected + $this->_getSession()->addError($this->__('Please select needed row(s).')); + $this->_redirect('*/*/index'); + return; + } + + try { + /** @var $collection Mage_OAuth_Model_Resource_Token_Collection */ + $collection = Mage::getModel('oauth/token')->getCollection(); + $collection->joinConsumerAsApplication() + ->addFilterByType(Mage_OAuth_Model_Token::TYPE_ACCESS) + ->addFilterById($ids); + + /** @var $item Mage_OAuth_Model_Token */ + foreach ($collection as $item) { + $item->delete(); + + $this->_sendTokenStatusChangeNotification($item, $this->__('deleted')); + } + $this->_getSession()->addSuccess($this->__('Selected entries has been deleted.')); + } catch (Mage_Core_Exception $e) { + $this->_getSession()->addError($e->getMessage()); + } catch (Exception $e) { + $this->_getSession()->addError($this->__('An error occurred on delete action.')); + Mage::logException($e); + } + $this->_redirect('*/*/index'); + } + + /** + * Check admin permissions for this controller + * + * @return boolean + */ + protected function _isAllowed() + { + /** @var $session Mage_Admin_Model_Session */ + $session = Mage::getSingleton('admin/session'); + return $session->isAllowed('system/oauth/authorizedTokens'); + } + + /** + * Send email notification to user about token status change + * + * @param Mage_OAuth_Model_Token $token Token object + * @param string $newStatus Name of new token status + */ + protected function _sendTokenStatusChangeNotification($token, $newStatus) + { + if (($adminId = $token->getAdminId())) { + /** @var $session Mage_Admin_Model_Session */ + $session = Mage::getSingleton('admin/session'); + + /** @var $admin Mage_Admin_Model_User */ + $admin = $session->getUser(); + + if ($admin->getId() == $adminId) { // skip own tokens + return; + } + $email = $admin->getEmail(); + $name = $admin->getName(' '); + } else { + /** @var $customer Mage_Customer_Model_Customer */ + $customer = Mage::getModel('customer/customer'); + + $customer->load($token->getCustomerId()); + + $email = $customer->getEmail(); + $name = $customer->getName(); + } + /** @var $helper Mage_OAuth_Helper_Data */ + $helper = Mage::helper('oauth'); + + $helper->sendNotificationOnTokenStatusChange($email, $name, $token->getConsumer()->getName(), $newStatus); + } +} diff --git a/app/code/core/Mage/OAuth/controllers/Adminhtml/OAuth/ConsumerController.php b/app/code/core/Mage/OAuth/controllers/Adminhtml/OAuth/ConsumerController.php new file mode 100644 index 0000000000..f1760c205f --- /dev/null +++ b/app/code/core/Mage/OAuth/controllers/Adminhtml/OAuth/ConsumerController.php @@ -0,0 +1,288 @@ + + */ +class Mage_OAuth_Adminhtml_OAuth_ConsumerController extends Mage_Adminhtml_Controller_Action +{ + /** + * Unset unused data from request + * Skip getting "key" and "secret" because its generated from server side only + * + * @param array $data + * @return array + */ + protected function _filter(array $data) + { + foreach (array('id', 'back', 'form_key', 'key', 'secret') as $field) { + if (isset($data[$field])) { + unset($data[$field]); + } + } + return $data; + } + + /** + * Init titles + * + * @return Mage_OAuth_Adminhtml_OAuth_ConsumerController + */ + public function preDispatch() + { + $this->_title($this->__('System')) + ->_title($this->__('OAuth')) + ->_title($this->__('Consumers')); + parent::preDispatch(); + return $this; + } + + /** + * Render grid page + */ + public function indexAction() + { + $this->loadLayout(); + $this->renderLayout(); + } + + /** + * Render grid AJAX request + */ + public function gridAction() + { + $this->loadLayout(); + $this->renderLayout(); + } + + /** + * Create page action + */ + public function newAction() + { + /** @var $model Mage_OAuth_Model_Consumer */ + $model = Mage::getModel('oauth/consumer'); + + $formData = $this->_getFormData(); + if ($formData) { + $this->_setFormData($formData); + $model->addData($formData); + } else { + /** @var $helper Mage_OAuth_Helper_Data */ + $helper = Mage::helper('oauth'); + $model->setKey($helper->generateConsumerKey()); + $model->setSecret($helper->generateConsumerSecret()); + $this->_setFormData($model->getData()); + } + + Mage::register('current_consumer', $model); + + $this->loadLayout(); + $this->renderLayout(); + } + + /** + * Edit page action + */ + public function editAction() + { + $id = (int) $this->getRequest()->getParam('id'); + + if (!$id) { + $this->_getSession()->addError(Mage::helper('oauth')->__('Invalid ID parameter.')); + $this->_redirect('*/*/index'); + return; + } + + /** @var $model Mage_OAuth_Model_Consumer */ + $model = Mage::getModel('oauth/consumer'); + $model->load($id); + + if (!$model->getId()) { + $this->_getSession()->addError(Mage::helper('oauth')->__('Entry with ID #%s not found.', $id)); + $this->_redirect('*/*/index'); + return; + } + + $model->addData($this->_filter($this->getRequest()->getParams())); + Mage::register('current_consumer', $model); + + $this->loadLayout(); + $this->renderLayout(); + } + + /** + * Render edit page + */ + public function saveAction() + { + $id = $this->getRequest()->getParam('id'); + if (!$this->_validateFormKey()) { + if ($id) { + $this->_redirect('*/*/edit', array('id' => $id)); + } else { + $this->_redirect('*/*/new', array('id' => $id)); + } + return; + } + + $data = $this->_filter($this->getRequest()->getParams()); + + /** @var $model Mage_OAuth_Model_Consumer */ + $model = Mage::getModel('oauth/consumer'); + + if ($id) { + if (!(int) $id) { + $this->_getSession()->addError( + $this->__('Invalid ID parameter.')); + $this->_redirect('*/*/index'); + return; + } + $model->load($id); + + if (!$model->getId()) { + $this->_getSession()->addError( + $this->__('Entry with ID #%s not found.', $id)); + $this->_redirect('*/*/index'); + return; + } + } else { + $dataForm = $this->_getFormData(); + if ($dataForm) { + $data['key'] = $dataForm['key']; + $data['secret'] = $dataForm['secret']; + } else { + // If an admin was started create a new consumer and at this moment he has been edited an existing + // consumer, we save the new consumer with a new key-secret pair + /** @var $helper Mage_OAuth_Helper_Data */ + $helper = Mage::helper('oauth'); + + $data['key'] = $helper->generateConsumerKey(); + $data['secret'] = $helper->generateConsumerSecret(); + } + } + + try { + $model->addData($data); + $model->save(); + $this->_getSession()->addSuccess($this->__('The consumer has been saved.')); + $this->_setFormData(null); + } catch (Mage_Core_Exception $e) { + $this->_setFormData($data); + $this->_getSession()->addError(Mage::helper('core')->escapeHtml($e->getMessage())); + $this->getRequest()->setParam('back', 'edit'); + } catch (Exception $e) { + $this->_setFormData(null); + Mage::logException($e); + $this->_getSession()->addError($this->__('An error occurred on saving consumer data.')); + } + + if ($this->getRequest()->getParam('back')) { + if ($id || $model->getId()) { + $this->_redirect('*/*/edit', array('id' => $model->getId())); + } else { + $this->_redirect('*/*/new'); + } + } else { + $this->_redirect('*/*/index'); + } + } + + /** + * Check admin permissions for this controller + * + * @return boolean + */ + protected function _isAllowed() + { + $action = $this->getRequest()->getActionName(); + if ('index' == $action) { + $action = null; + } else { + if ('new' == $action || 'save' == $action) { + $action = 'edit'; + } + $action = '/' . $action; + } + /** @var $session Mage_Admin_Model_Session */ + $session = Mage::getSingleton('admin/session'); + return $session->isAllowed('system/oauth/consumer' . $action); + } + + /** + * Get form data + * + * @return array + */ + protected function _getFormData() + { + return $this->_getSession()->getData('consumer_data', true); + } + + /** + * Set form data + * + * @param $data + * @return Mage_OAuth_Adminhtml_OAuth_ConsumerController + */ + protected function _setFormData($data) + { + $this->_getSession()->setData('consumer_data', $data); + return $this; + } + + /** + * Delete consumer action + */ + public function deleteAction() + { + $consumerId = (int) $this->getRequest()->getParam('id'); + if ($consumerId) { + try { + /** @var $consumer Mage_OAuth_Model_Consumer */ + $consumer = Mage::getModel('oauth/consumer')->load($consumerId); + if (!$consumer->getId()) { + Mage::throwException(Mage::helper('oauth')->__('Unable to find a consumer.')); + } + + $consumer->delete(); + + $this->_getSession()->addSuccess(Mage::helper('oauth')->__('The consumer has been deleted.')); + } catch (Mage_Core_Exception $e) { + $this->_getSession()->addError($e->getMessage()); + } catch (Exception $e) { + $this->_getSession()->addException( + $e, Mage::helper('oauth')->__('An error occurred while deleting the consumer.') + ); + } + } + $this->_redirect('*/*/index'); + } +} diff --git a/app/code/core/Mage/OAuth/controllers/AuthorizeController.php b/app/code/core/Mage/OAuth/controllers/AuthorizeController.php new file mode 100644 index 0000000000..955a8f5f40 --- /dev/null +++ b/app/code/core/Mage/OAuth/controllers/AuthorizeController.php @@ -0,0 +1,243 @@ + + */ +class Mage_OAuth_AuthorizeController extends Mage_Core_Controller_Front_Action +{ + /** + * Session name + * + * @var string + */ + protected $_sessionName = 'customer/session'; + + /** + * Init authorize page + * + * @param bool $simple Is simple page? + * @return Mage_OAuth_AuthorizeController + */ + protected function _initForm($simple = false) + { + /** @var $server Mage_OAuth_Model_Server */ + $server = Mage::getModel('oauth/server'); + /** @var $session Mage_Customer_Model_Session */ + $session = Mage::getSingleton($this->_sessionName); + + $isException = false; + try { + $server->checkAuthorizeRequest(); + } catch (Mage_Core_Exception $e) { + $session->addError($e->getMessage()); + } catch (Mage_OAuth_Exception $e) { + $isException = true; + $session->addException($e, $this->__('An error occurred. Your authorization request is invalid.')); + } catch (Exception $e) { + $isException = true; + $session->addException($e, $this->__('An error occurred.')); + } + + $this->loadLayout(); + $layout = $this->getLayout(); + $logged = $session->isLoggedIn(); + + $contentBlock = $layout->getBlock('content'); + if ($logged) { + $contentBlock->unsetChild('oauth.authorize.form'); + /** @var $block Mage_OAuth_Block_Authorize_Button */ + $block = $contentBlock->getChild('oauth.authorize.button'); + } else { + $contentBlock->unsetChild('oauth.authorize.button'); + /** @var $block Mage_OAuth_Block_Authorize */ + $block = $contentBlock->getChild('oauth.authorize.form'); + } + + if ($simple) { + $layout->getBlock('oauth.authorize.style')->setData('is_logged', $logged); + } + + /** @var $helper Mage_Core_Helper_Url */ + $helper = Mage::helper('core/url'); + $session->setAfterAuthUrl(Mage::getUrl('customer/account/login', array('_nosid' => true))) + ->setBeforeAuthUrl($helper->getCurrentUrl()); + + $block->setIsSimple($simple) + ->setToken($this->getRequest()->getQuery('oauth_token')) + ->setHasException($isException); + return $this; + } + + /** + * Init confirm page + * + * @param bool $simple Is simple page? + * @return Mage_OAuth_AuthorizeController + */ + protected function _initConfirmPage($simple = false) + { + $this->loadLayout(); + try { + /** @var $session Mage_Customer_Model_Session */ + $session = Mage::getSingleton($this->_sessionName); + /** @var $server Mage_OAuth_Model_Server */ + $server = Mage::getModel('oauth/server'); + + /** @var $block Mage_OAuth_Block_Authorize */ + $block = $this->getLayout()->getBlock('oauth.authorize.confirm'); + $block->setIsSimple($simple); + + /** @var $token Mage_OAuth_Model_Token */ + $token = $server->authorizeToken($session->getCustomerId(), Mage_OAuth_Model_Token::USER_TYPE_CUSTOMER); + + /** @var $helper Mage_OAuth_Helper_Data */ + $helper = Mage::helper('oauth'); + + if (($callback = $helper->getFullCallbackUrl($token))) { //false in case of OOB + $this->_redirectUrl($callback . ($simple ? '&simple=1' : '')); + return $this; + } else { + $block->setVerifier($token->getVerifier()); + $session->addSuccess($this->__('Authorization confirmed.')); + } + } catch (Mage_Core_Exception $e) { + $session->addError($e->getMessage()); + } catch (Mage_OAuth_Exception $e) { + $session->addException($e, $this->__('An error occurred. Your authorization request is invalid.')); + } catch (Exception $e) { + $session->addException($e, $this->__('An error occurred on confirm authorize.')); + } + + $this->_initLayoutMessages($this->_sessionName); + $this->renderLayout(); + + return $this; + } + + /** + * Init reject page + * + * @param bool $simple Is simple page? + * @return Mage_OAuth_AuthorizeController + */ + protected function _initRejectPage($simple = false) + { + $this->loadLayout(); + + /** @var $session Mage_Customer_Model_Session */ + $session = Mage::getSingleton($this->_sessionName); + try { + /** @var $server Mage_OAuth_Model_Server */ + $server = Mage::getModel('oauth/server'); + + /** @var $block Mage_OAuth_Block_Authorize */ + $block = $this->getLayout()->getBlock('oauth.authorize.reject'); + $block->setIsSimple($simple . ($simple ? '&simple=1' : '')); + + /** @var $token Mage_OAuth_Model_Token */ + $token = $server->checkAuthorizeRequest(); + /** @var $helper Mage_OAuth_Helper_Data */ + $helper = Mage::helper('oauth'); + + if (($callback = $helper->getFullCallbackUrl($token, true))) { + $this->_redirectUrl($callback . ($simple ? '&simple=1' : '')); + return $this; + } else { + $session->addNotice($this->__('The application access request is rejected.')); + } + } catch (Mage_Core_Exception $e) { + $session->addError($e->getMessage()); + } catch (Exception $e) { + $session->addException($e, $this->__('An error occurred on reject authorize.')); + } + + $this->_initLayoutMessages($this->_sessionName); + $this->renderLayout(); + + return $this; + } + + /** + * Index action. + * + * @return void + */ + public function indexAction() + { + $this->_initForm(); + $this->_initLayoutMessages($this->_sessionName); + $this->renderLayout(); + } + + /** + * OAuth authorize or allow decline access simple page + * + * @return void + */ + public function simpleAction() + { + $this->_initForm(true); + $this->_initLayoutMessages($this->_sessionName); + $this->renderLayout(); + } + + /** + * Confirm token authorization action + */ + public function confirmAction() + { + $this->_initConfirmPage(); + } + + /** + * Confirm token authorization simple page + */ + public function confirmSimpleAction() + { + $this->_initConfirmPage(true); + } + + /** + * Reject token authorization action + */ + public function rejectAction() + { + $this->_initRejectPage(); + } + + /** + * Reject token authorization simple page + */ + public function rejectSimpleAction() + { + $this->_initRejectPage(true); + } +} diff --git a/app/code/core/Mage/OAuth/controllers/Customer/TokenController.php b/app/code/core/Mage/OAuth/controllers/Customer/TokenController.php new file mode 100644 index 0000000000..bf12802a6c --- /dev/null +++ b/app/code/core/Mage/OAuth/controllers/Customer/TokenController.php @@ -0,0 +1,188 @@ + + */ +class Mage_OAuth_Customer_TokenController extends Mage_Core_Controller_Front_Action +{ + /** + * Customer session model + * + * @var Mage_Customer_Model_Session + */ + protected $_session; + + /** + * Customer session model + * + * @var Mage_Customer_Model_Session + */ + protected $_sessionName = 'customer/session'; + + /** + * Check authentication + * + * Check customer authentication for some actions + */ + public function preDispatch() + { + parent::preDispatch(); + $this->_session = Mage::getSingleton($this->_sessionName); + if (!$this->_session->authenticate($this)) { + $this->setFlag('', self::FLAG_NO_DISPATCH, true); + } + + } + + /** + * Render grid page + */ + public function indexAction() + { + $this->loadLayout(); + $this->_initLayoutMessages($this->_sessionName); + $this->renderLayout(); + } + + /** + * Redirect to referrer URL or otherwise to index page without params + * + * @return Mage_OAuth_Customer_TokenController + */ + protected function _redirectBack() + { + $url = $this->_getRefererUrl(); + if (Mage::app()->getStore()->getBaseUrl() == $url) { + $url = Mage::getUrl('*/*/index'); + } + $this->_redirectUrl($url); + return $this; + } + + /** + * Update revoke status action + */ + public function revokeAction() + { + $id = $this->getRequest()->getParam('id'); + $status = $this->getRequest()->getParam('status'); + + if (0 === (int) $id) { + // No ID + $this->_session->addError($this->__('Invalid entry ID.')); + $this->_redirectBack(); + return; + } + + if (null === $status) { + // No status selected + $this->_session->addError($this->__('Invalid revoke status.')); + $this->_redirectBack(); + return; + } + + try { + /** @var $collection Mage_OAuth_Model_Resource_Token_Collection */ + $collection = Mage::getModel('oauth/token')->getCollection(); + $collection->joinConsumerAsApplication() + ->addFilterByCustomerId($this->_session->getCustomerId()) + ->addFilterById($id) + ->addFilterByType(Mage_OAuth_Model_Token::TYPE_ACCESS) + ->addFilterByRevoked(!$status); + //here is can be load from model, but used from collection for get consumer name + + /** @var $model Mage_OAuth_Model_Token */ + $model = $collection->getFirstItem(); + if ($model->getId()) { + $name = $model->getName(); + $model->load($model->getId()); + $model->setRevoked($status)->save(); + if ($status) { + $message = $this->__('Application "%s" has been revoked.', $name); + } else { + $message = $this->__('Application "%s" has been enabled.', $name); + } + $this->_session->addSuccess($message); + } else { + $this->_session->addError($this->__('Application not found.')); + } + } catch (Mage_Core_Exception $e) { + $this->_session->addError($e->getMessage()); + } catch (Exception $e) { + $this->_session->addError($this->__('An error occurred on update revoke status.')); + Mage::logException($e); + } + $this->_redirectBack(); + } + + /** + * Delete action + */ + public function deleteAction() + { + $id = $this->getRequest()->getParam('id'); + + if (0 === (int) $id) { + // No ID + $this->_session->addError($this->__('Invalid entry ID.')); + $this->_redirectBack(); + return; + } + + try { + /** @var $collection Mage_OAuth_Model_Resource_Token_Collection */ + $collection = Mage::getModel('oauth/token')->getCollection(); + $collection->joinConsumerAsApplication() + ->addFilterByCustomerId($this->_session->getCustomerId()) + ->addFilterByType(Mage_OAuth_Model_Token::TYPE_ACCESS) + ->addFilterById($id); + + /** @var $model Mage_OAuth_Model_Token */ + $model = $collection->getFirstItem(); + if ($model->getId()) { + $name = $model->getName(); + $model->delete(); + $this->_session->addSuccess( + $this->__('Application "%s" has been deleted.', $name)); + } else { + $this->_session->addError($this->__('Application not found.')); + } + } catch (Mage_Core_Exception $e) { + $this->_session->addError($e->getMessage()); + } catch (Exception $e) { + $this->_session->addError($this->__('An error occurred on delete application.')); + Mage::logException($e); + } + $this->_redirectBack(); + } +} diff --git a/app/code/core/Mage/OAuth/controllers/InitiateController.php b/app/code/core/Mage/OAuth/controllers/InitiateController.php new file mode 100644 index 0000000000..58b6700eb7 --- /dev/null +++ b/app/code/core/Mage/OAuth/controllers/InitiateController.php @@ -0,0 +1,61 @@ + + */ +class Mage_OAuth_InitiateController extends Mage_Core_Controller_Front_Action +{ + /** + * Dispatch event before action + * + * @return void + */ + public function preDispatch() + { + $this->setFlag('', self::FLAG_NO_START_SESSION, 1); + $this->setFlag('', self::FLAG_NO_CHECK_INSTALLATION, 1); + $this->setFlag('', self::FLAG_NO_COOKIES_REDIRECT, 0); + $this->setFlag('', self::FLAG_NO_PRE_DISPATCH, 1); + + parent::preDispatch(); + } + + /** + * Index action. Receive initiate request and response OAuth token + */ + public function indexAction() + { + /** @var $server Mage_OAuth_Model_Server */ + $server = Mage::getModel('oauth/server'); + + $server->initiateToken(); + } +} diff --git a/app/code/core/Mage/OAuth/controllers/TokenController.php b/app/code/core/Mage/OAuth/controllers/TokenController.php new file mode 100644 index 0000000000..c85f979b2a --- /dev/null +++ b/app/code/core/Mage/OAuth/controllers/TokenController.php @@ -0,0 +1,61 @@ + + */ +class Mage_OAuth_TokenController extends Mage_Core_Controller_Front_Action +{ + /** + * Dispatch event before action + * + * @return void + */ + public function preDispatch() + { + $this->setFlag('', self::FLAG_NO_START_SESSION, 1); + $this->setFlag('', self::FLAG_NO_CHECK_INSTALLATION, 1); + $this->setFlag('', self::FLAG_NO_COOKIES_REDIRECT, 0); + $this->setFlag('', self::FLAG_NO_PRE_DISPATCH, 1); + + parent::preDispatch(); + } + + /** + * Index action. Process request and response permanent token + */ + public function indexAction() + { + /** @var $server Mage_OAuth_Model_Server */ + $server = Mage::getModel('oauth/server'); + + $server->accessToken(); + } +} diff --git a/app/code/core/Mage/OAuth/etc/adminhtml.xml b/app/code/core/Mage/OAuth/etc/adminhtml.xml new file mode 100644 index 0000000000..3528286afc --- /dev/null +++ b/app/code/core/Mage/OAuth/etc/adminhtml.xml @@ -0,0 +1,100 @@ + + + + + + + + + + + + + OAuth Consumers + 20 + + + Edit + 30 + + + Delete + 40 + + + + + OAuth Authorized Tokens + 30 + + + OAuth Admin My Apps + 40 + + + + + + + OAuth + 20 + + + + + + + + + + + + + + + + REST - OAuth Consumers + 50 + adminhtml/oAuth_consumer + + + REST - OAuth Authorized Tokens + 60 + adminhtml/oAuth_authorizedTokens + + + REST - My Apps + 70 + adminhtml/oAuth_admin_token + + + + + + + diff --git a/app/code/core/Mage/OAuth/etc/config.xml b/app/code/core/Mage/OAuth/etc/config.xml new file mode 100644 index 0000000000..28f3a54870 --- /dev/null +++ b/app/code/core/Mage/OAuth/etc/config.xml @@ -0,0 +1,177 @@ + + + + + + 1.0.0.0 + + + + + + Mage_OAuth_Model + oauth_resource + + + Mage_OAuth_Model_Resource + + + oauth_consumer
    +
    + + oauth_token
    +
    + + oauth_nonce
    +
    +
    +
    +
    + + + Mage_OAuth_Block + + + + + Mage_OAuth_Helper + + + + + + Mage_OAuth + Mage_OAuth_Model_Resource_Setup + + + + + + + + oauth/observer + afterCustomerLogin + + + + + + + oauth/observer + afterAdminLogin + + + + + + + oauth/observer + afterAdminLoginFailed + + + + + +
    + + + + + + Mage_OAuth_Adminhtml + + + + + + + + + + + Mage_OAuth.csv + + + + + + + + oauth.xml + + + + + + + + standard + + Mage_OAuth + oauth + + + + + + + oauth.xml + + + + + + + + Mage_OAuth.csv + + + + + + + + + 100 + 120 + + + general + + + + +
    diff --git a/app/code/core/Mage/OAuth/etc/system.xml b/app/code/core/Mage/OAuth/etc/system.xml new file mode 100644 index 0000000000..66e678d067 --- /dev/null +++ b/app/code/core/Mage/OAuth/etc/system.xml @@ -0,0 +1,89 @@ + + + + + + + service + text + 300 + 1 + 1 + 1 + + + + text + 300 + 1 + 0 + 0 + + + + text + Integer. Launch cleanup in X OAuth requests. 0 (not recommended) - to disable cleanup + 10 + 1 + 0 + 0 + + + + Cleanup entries older than X minutes. + text + 20 + 1 + 0 + 0 + + + + + + text + 300 + 1 + 1 + 1 + + + + + + + + diff --git a/app/code/core/Mage/OAuth/sql/oauth_setup/install-1.0.0.0.php b/app/code/core/Mage/OAuth/sql/oauth_setup/install-1.0.0.0.php new file mode 100644 index 0000000000..508beb3e4b --- /dev/null +++ b/app/code/core/Mage/OAuth/sql/oauth_setup/install-1.0.0.0.php @@ -0,0 +1,198 @@ +startSetup(); + +/** @var $adapter Varien_Db_Adapter_Pdo_Mysql */ +$adapter = $installer->getConnection(); + +/** + * Create table 'oauth/consumer' + */ +$table = $adapter->newTable($installer->getTable('oauth/consumer')) + ->addColumn('entity_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Entity Id') + ->addColumn('created_at', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array( + 'nullable' => false, + 'default' => Varien_Db_Ddl_Table::TIMESTAMP_INIT + ), 'Created At') + ->addColumn('updated_at', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array( + 'nullable' => true + ), 'Updated At') + ->addColumn('name', Varien_Db_Ddl_Table::TYPE_VARCHAR, 255, array( + 'nullable' => false + ), 'Name of consumer') + ->addColumn('key', Varien_Db_Ddl_Table::TYPE_VARCHAR, Mage_OAuth_Model_Consumer::KEY_LENGTH, array( + 'nullable' => false + ), 'Key code') + ->addColumn('secret', Varien_Db_Ddl_Table::TYPE_VARCHAR, Mage_OAuth_Model_Consumer::SECRET_LENGTH, array( + 'nullable' => false + ), 'Secret code') + ->addColumn('callback_url', Varien_Db_Ddl_Table::TYPE_VARCHAR, 255, array(), 'Callback URL') + ->addColumn('rejected_callback_url', Varien_Db_Ddl_Table::TYPE_VARCHAR, 255, array( + 'nullable' => false + ), 'Rejected callback URL') + ->addIndex( + $installer->getIdxName( + $installer->getTable('oauth/consumer'), + array('key'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('key'), + array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE)) + ->addIndex( + $installer->getIdxName( + $installer->getTable('oauth/consumer'), + array('secret'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('secret'), + array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE)) + ->addIndex($installer->getIdxName('oauth/consumer', array('created_at')), array('created_at')) + ->addIndex($installer->getIdxName('oauth/consumer', array('updated_at')), array('updated_at')) + ->setComment('OAuth Consumers'); +$adapter->createTable($table); + +/** + * Create table 'oauth/token' + */ +$table = $adapter->newTable($installer->getTable('oauth/token')) + ->addColumn('entity_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true, + ), 'Entity ID') + ->addColumn('consumer_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'unsigned' => true, + 'nullable' => false + ), 'Consumer ID') + ->addColumn('admin_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'unsigned' => true, + 'nullable' => true + ), 'Admin user ID') + ->addColumn('customer_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'unsigned' => true, + 'nullable' => true + ), 'Customer user ID') + ->addColumn('type', Varien_Db_Ddl_Table::TYPE_TEXT, 16, array( + 'nullable' => false + ), 'Token Type') + ->addColumn('token', Varien_Db_Ddl_Table::TYPE_TEXT, Mage_OAuth_Model_Token::LENGTH_TOKEN, array( + 'nullable' => false + ), 'Token') + ->addColumn('secret', Varien_Db_Ddl_Table::TYPE_TEXT, Mage_OAuth_Model_Token::LENGTH_SECRET, array( + 'nullable' => false + ), 'Token Secret') + ->addColumn('verifier', Varien_Db_Ddl_Table::TYPE_TEXT, Mage_OAuth_Model_Token::LENGTH_VERIFIER, array( + 'nullable' => true + ), 'Token Verifier') + ->addColumn('callback_url', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + 'nullable' => false + ), 'Token Callback URL') + ->addColumn('revoked', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => 0, + ), 'Is Token revoked') + ->addColumn('authorized', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => 0, + ), 'Is Token authorized') + ->addColumn('created_at', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array( + 'nullable' => false, + 'default' => Varien_Db_Ddl_Table::TIMESTAMP_INIT + ), 'Token creation timestamp') + ->addIndex( + $installer->getIdxName( + $installer->getTable('oauth/token'), + array('consumer_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_INDEX + ), + array('consumer_id'), + array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_INDEX)) + ->addIndex( + $installer->getIdxName( + $installer->getTable('oauth/token'), + array('token'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('token'), + array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE)) + ->addForeignKey( + $installer->getFkName('oauth/token', 'admin_id', $installer->getTable('admin/user'), 'user_id'), + 'admin_id', + $installer->getTable('admin/user'), + 'user_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, + Varien_Db_Ddl_Table::ACTION_CASCADE) + ->addForeignKey( + $installer->getFkName('oauth/token', 'consumer_id', 'oauth/consumer', 'entity_id'), + 'consumer_id', + $installer->getTable('oauth/consumer'), + 'entity_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, + Varien_Db_Ddl_Table::ACTION_CASCADE) + ->addForeignKey( + $installer->getFkName('oauth/token', 'customer_id', $installer->getTable('customer/entity'), 'entity_id'), + 'customer_id', + $installer->getTable('customer/entity'), + 'entity_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, + Varien_Db_Ddl_Table::ACTION_CASCADE) + ->setComment('OAuth Tokens'); +$adapter->createTable($table); + +/** + * Create table 'oauth/nonce + */ +$table = $adapter->newTable($installer->getTable('oauth/nonce')) + ->addColumn('nonce', Varien_Db_Ddl_Table::TYPE_VARCHAR, 32, array( + 'nullable' => false + ), 'Nonce String') + ->addColumn('timestamp', Varien_Db_Ddl_Table::TYPE_INTEGER, 10, array( + 'unsigned' => true, + 'nullable' => false + ), 'Nonce Timestamp') + ->addIndex( + $installer->getIdxName( + $installer->getTable('oauth/nonce'), + array('nonce'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('nonce'), + array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE)) + ->setOption('type', 'MyISAM'); +$adapter->createTable($table); + +$installer->endSetup(); diff --git a/app/code/core/Mage/Page/Block/Html/Notices.php b/app/code/core/Mage/Page/Block/Html/Notices.php index f54c1bac6f..43207bbb4a 100644 --- a/app/code/core/Mage/Page/Block/Html/Notices.php +++ b/app/code/core/Mage/Page/Block/Html/Notices.php @@ -33,10 +33,12 @@ */ class Mage_Page_Block_Html_Notices extends Mage_Core_Block_Template { + /** - * Path to configuration, check is enable cookie restriction mode + * Cookie restriction lifetime configuration path */ - const XML_PATH_COOKIE_RESTRICTION = 'web/cookie/cookie_restriction'; + const XML_PATH_COOKIE_RESTRICTION_LIFETIME = 'web/cookie/cookie_restriction_lifetime'; + /** * Check if noscript notice should be displayed @@ -58,6 +60,16 @@ public function displayDemoNotice() return Mage::getStoreConfig('design/head/demonotice'); } + /** + * Get cookie restriction lifetime (in seconds) + * + * @return int + */ + public function getCookieRestrictionLifetime() + { + return (int)Mage::getStoreConfig(self::XML_PATH_COOKIE_RESTRICTION_LIFETIME); + } + /** * Check if cookie restriction notice should be displayed * @@ -79,28 +91,4 @@ public function getPrivacyPolicyLink() { return Mage::getUrl('privacy-policy-cookie-restriction-mode'); } - - /** - * Return serialzed list of accepted save cookie website - * - * @return string - */ - public function getAcceptedSaveCookiesWebsiteIds() - { - $acceptedSaveCookiesWebsites = $this->_getAcceptedSaveCookiesWebsites(); - $acceptedSaveCookiesWebsites[Mage::app()->getWebsite()->getId()] = 1; - return serialize($acceptedSaveCookiesWebsites); - } - - /** - * Get accepted save cookies websites - * - * @return array - */ - protected function _getAcceptedSaveCookiesWebsites() - { - $serializedList = Mage::getSingleton('core/cookie')->get(Mage_Page_Helper_Data::IS_USER_ALLOWED_SAVE_COOKIE); - $unSerializedList = unserialize($serializedList); - return is_array($unSerializedList) ? $unSerializedList : array(); - } } diff --git a/app/code/core/Mage/Page/Helper/Data.php b/app/code/core/Mage/Page/Helper/Data.php index 9dd649ccaa..d7daefb551 100644 --- a/app/code/core/Mage/Page/Helper/Data.php +++ b/app/code/core/Mage/Page/Helper/Data.php @@ -29,8 +29,5 @@ */ class Mage_Page_Helper_Data extends Mage_Core_Helper_Abstract { - /** - * Cookie name for users who allowed cookie save - */ - const IS_USER_ALLOWED_SAVE_COOKIE = 'user_allowed_save_cookie'; + } diff --git a/app/code/core/Mage/Paygate/etc/system.xml b/app/code/core/Mage/Paygate/etc/system.xml index 8182a54381..e2454d0fb1 100644 --- a/app/code/core/Mage/Paygate/etc/system.xml +++ b/app/code/core/Mage/Paygate/etc/system.xml @@ -86,6 +86,7 @@ text + validate-email 11 1 1 diff --git a/app/code/core/Mage/Payment/Block/Form/Banktransfer.php b/app/code/core/Mage/Payment/Block/Form/Banktransfer.php new file mode 100644 index 0000000000..d506b49db8 --- /dev/null +++ b/app/code/core/Mage/Payment/Block/Form/Banktransfer.php @@ -0,0 +1,62 @@ +setTemplate('payment/form/banktransfer.phtml'); + } + + /** + * Get instructions text from config + * + * @return string + */ + public function getInstructions() + { + if (is_null($this->_instructions)) { + $this->_instructions = $this->getMethod()->getInstructions(); + } + return $this->_instructions; + } + +} diff --git a/app/code/core/Mage/Payment/Block/Form/Cashondelivery.php b/app/code/core/Mage/Payment/Block/Form/Cashondelivery.php new file mode 100644 index 0000000000..85823ac62e --- /dev/null +++ b/app/code/core/Mage/Payment/Block/Form/Cashondelivery.php @@ -0,0 +1,62 @@ +setTemplate('payment/form/cashondelivery.phtml'); + } + + /** + * Get instructions text from config + * + * @return string + */ + public function getInstructions() + { + if (is_null($this->_instructions)) { + $this->_instructions = $this->getMethod()->getInstructions(); + } + return $this->_instructions; + } + +} diff --git a/app/code/core/Mage/Payment/Block/Info/Banktransfer.php b/app/code/core/Mage/Payment/Block/Info/Banktransfer.php new file mode 100644 index 0000000000..324b20d053 --- /dev/null +++ b/app/code/core/Mage/Payment/Block/Info/Banktransfer.php @@ -0,0 +1,61 @@ +setTemplate('payment/info/banktransfer.phtml'); + } + + /** + * Get instructions text from order payment + * (or from config, if instructions are missed in payment) + * + * @return string + */ + public function getInstructions() + { + if (is_null($this->_instructions)) { + $this->_instructions = $this->getInfo()->getAdditionalInformation('instructions'); + if(empty($this->_instructions)) { + $this->_instructions = $this->getMethod()->getInstructions(); + } + } + return $this->_instructions; + } +} diff --git a/app/code/core/Mage/Payment/Model/Info.php b/app/code/core/Mage/Payment/Model/Info.php index 8a1e33594c..97648dc154 100644 --- a/app/code/core/Mage/Payment/Model/Info.php +++ b/app/code/core/Mage/Payment/Model/Info.php @@ -66,21 +66,23 @@ public function getData($key='', $index=null) * Retrieve payment method model object * * @return Mage_Payment_Model_Method_Abstract + * @throws Mage_Core_Exception */ public function getMethodInstance() { if (!$this->hasMethodInstance()) { - if ($method = $this->getMethod()) { - if ($instance = Mage::helper('payment')->getMethodInstance($this->getMethod())) { + if ($this->getMethod()) { + $instance = Mage::helper('payment')->getMethodInstance($this->getMethod()); + if ($instance) { $instance->setInfoInstance($this); $this->setMethodInstance($instance); return $instance; } } - } else { - return $this->_getData('method_instance'); + Mage::throwException(Mage::helper('payment')->__('The requested Payment Method is not available.')); } - Mage::throwException(Mage::helper('payment')->__('Cannot retrieve payment method instance.')); + + return $this->_getData('method_instance'); } /** diff --git a/app/code/core/Mage/Payment/Model/Method/Abstract.php b/app/code/core/Mage/Payment/Model/Method/Abstract.php index 49d46906e7..809c42d991 100644 --- a/app/code/core/Mage/Payment/Model/Method/Abstract.php +++ b/app/code/core/Mage/Payment/Model/Method/Abstract.php @@ -629,14 +629,16 @@ public function prepareSave() * * TODO: payment method instance is not supposed to know about quote * - * @param Mage_Sales_Model_Quote $quote + * @param Mage_Sales_Model_Quote|null $quote * * @return bool */ public function isAvailable($quote = null) { $checkResult = new StdClass; - $checkResult->isAvailable = (bool)(int)$this->getConfigData('active', ($quote ? $quote->getStoreId() : null)); + $isActive = (bool)(int)$this->getConfigData('active', $quote ? $quote->getStoreId() : null); + $checkResult->isAvailable = $isActive; + $checkResult->isDeniedInConfig = !$isActive; // for future use in observers Mage::dispatchEvent('payment_method_is_active', array( 'result' => $checkResult, 'method_instance' => $this, @@ -647,7 +649,7 @@ public function isAvailable($quote = null) if ($checkResult->isAvailable) { $implementsRecurring = $this->canManageRecurringProfiles(); // the $quote->hasRecurringItems() causes big performance impact, thus it has to be called last - if ($quote && (!$implementsRecurring) && $quote->hasRecurringItems()) { + if ($quote && !$implementsRecurring && $quote->hasRecurringItems()) { $checkResult->isAvailable = false; } } @@ -704,7 +706,7 @@ public function getDebugFlag() } /** - * Used to call debug method from not Paymant Method context + * Used to call debug method from not Payment Method context * * @param mixed $debugData */ diff --git a/app/code/core/Mage/Payment/Model/Method/Banktransfer.php b/app/code/core/Mage/Payment/Model/Method/Banktransfer.php new file mode 100644 index 0000000000..b95d054d86 --- /dev/null +++ b/app/code/core/Mage/Payment/Model/Method/Banktransfer.php @@ -0,0 +1,59 @@ +getConfigData('instructions')); + } + +} diff --git a/app/code/core/Mage/Payment/Model/Method/Cashondelivery.php b/app/code/core/Mage/Payment/Model/Method/Cashondelivery.php new file mode 100644 index 0000000000..2e238489a9 --- /dev/null +++ b/app/code/core/Mage/Payment/Model/Method/Cashondelivery.php @@ -0,0 +1,58 @@ +getConfigData('instructions')); + } + +} diff --git a/app/code/core/Mage/Payment/Model/Method/Cc.php b/app/code/core/Mage/Payment/Model/Method/Cc.php index cae20a81bd..f605ae823a 100644 --- a/app/code/core/Mage/Payment/Model/Method/Cc.php +++ b/app/code/core/Mage/Payment/Model/Method/Cc.php @@ -197,7 +197,7 @@ public function getVerificationRegEx() 'SM' => '/^[0-9]{3,4}$/', // Switch or Maestro 'SO' => '/^[0-9]{3,4}$/', // Solo 'OT' => '/^[0-9]{3,4}$/', - 'JCB' => '/^[0-9]{4}$/' //JCB + 'JCB' => '/^[0-9]{3,4}$/' //JCB ); return $verificationExpList; } @@ -271,6 +271,7 @@ public function validateCcNumOther($ccNumber) /** * Check whether there are CC types set in configuration * + * @param Mage_Sales_Model_Quote|null $quote * @return bool */ public function isAvailable($quote = null) diff --git a/app/code/core/Mage/Payment/Model/Method/Free.php b/app/code/core/Mage/Payment/Model/Method/Free.php index a1aada4d73..a55dbef867 100644 --- a/app/code/core/Mage/Payment/Model/Method/Free.php +++ b/app/code/core/Mage/Payment/Model/Method/Free.php @@ -35,7 +35,7 @@ class Mage_Payment_Model_Method_Free extends Mage_Payment_Model_Method_Abstract { /** - * XML Pathes for configuration constants + * XML Paths for configuration constants */ const XML_PATH_PAYMENT_FREE_ACTIVE = 'payment/free/active'; const XML_PATH_PAYMENT_FREE_ORDER_STATUS = 'payment/free/order_status'; @@ -45,7 +45,7 @@ class Mage_Payment_Model_Method_Free extends Mage_Payment_Model_Method_Abstract * Payment Method features * @var bool */ - protected $_canAuthorize = true; + protected $_canAuthorize = true; /** * Payment code name @@ -57,25 +57,22 @@ class Mage_Payment_Model_Method_Free extends Mage_Payment_Model_Method_Abstract /** * Check whether method is available * - * @param Mage_Sales_Model_Quote $quote + * @param Mage_Sales_Model_Quote|null $quote * @return bool */ public function isAvailable($quote = null) { - return parent::isAvailable($quote) && (!empty($quote)) - && (Mage::app()->getStore()->roundPrice($quote->getGrandTotal()) == 0); + return parent::isAvailable($quote) && !empty($quote) + && Mage::app()->getStore()->roundPrice($quote->getGrandTotal()) == 0; } /** - * Get config peyment action + * Get config payment action, do nothing if status is pending * - * @return string + * @return string|null */ public function getConfigPaymentAction() { - if ('pending' == $this->getConfigData('order_status')) { - return null; // do nothing if status pending - } - return parent::getConfigPaymentAction(); + return $this->getConfigData('order_status') == 'pending' ? null : parent::getConfigPaymentAction(); } } diff --git a/app/code/core/Mage/Payment/Model/Observer.php b/app/code/core/Mage/Payment/Model/Observer.php index e95381b3c4..55f119deda 100644 --- a/app/code/core/Mage/Payment/Model/Observer.php +++ b/app/code/core/Mage/Payment/Model/Observer.php @@ -51,8 +51,7 @@ public function salesOrderBeforeSave($observer) return $this; } - if ($order->isCanceled() || - $order->getState() === Mage_Sales_Model_Order::STATE_CLOSED ) { + if ($order->isCanceled() || $order->getState() === Mage_Sales_Model_Order::STATE_CLOSED) { return $this; } /** @@ -108,4 +107,20 @@ public function prepareProductRecurringProfileOptions($observer) } $product->addCustomOption('additional_options', serialize($infoOptions)); } + + /** + * Sets current instructions for bank transfer account + * + * @param Varien_Event_Observer $observer + * @return void + */ + public function beforeOrderPaymentSave(Varien_Event_Observer $observer) + { + /** @var Mage_Sales_Model_Order_Payment $payment */ + $payment = $observer->getEvent()->getPayment(); + if($payment->getMethod() === Mage_Payment_Model_Method_Banktransfer::PAYMENT_METHOD_BANKTRANSFER_CODE) { + $payment->setAdditionalInformation('instructions', + $payment->getMethodInstance()->getInstructions()); + } + } } diff --git a/app/code/core/Mage/Payment/etc/config.xml b/app/code/core/Mage/Payment/etc/config.xml index 28890d5ee7..6d27a94de6 100644 --- a/app/code/core/Mage/Payment/etc/config.xml +++ b/app/code/core/Mage/Payment/etc/config.xml @@ -107,6 +107,14 @@ + + + + payment/observer + beforeOrderPaymentSave + + + @@ -184,6 +192,22 @@ 0 offline + + 0 + payment/method_banktransfer + pending + Bank Transfer Payment + 0 + offline + + + 0 + payment/method_cashondelivery + pending + Cash On Delivery + 0 + offline + diff --git a/app/code/core/Mage/Payment/etc/system.xml b/app/code/core/Mage/Payment/etc/system.xml index 92a059f376..12801c8fca 100644 --- a/app/code/core/Mage/Payment/etc/system.xml +++ b/app/code/core/Mage/Payment/etc/system.xml @@ -445,6 +445,180 @@ + + + text + 5 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + 1 + 1 + 1 + 0 + + + <label>Title</label> + <frontend_type>text</frontend_type> + <sort_order>10</sort_order> + <show_in_default>1</show_in_default> + <show_in_website>1</show_in_website> + <show_in_store>1</show_in_store> + + + + select + adminhtml/system_config_source_order_status_new + 20 + 1 + 1 + 0 + + + + allowspecific + 50 + adminhtml/system_config_source_payment_allspecificcountries + 1 + 1 + 0 + + + + multiselect + 51 + adminhtml/system_config_source_country + 1 + 1 + 0 + 1 + + + + textarea + 62 + 1 + 1 + 1 + + + + text + 98 + 1 + 1 + 0 + + + + text + 99 + 1 + 1 + 0 + + + + text + 100 + 1 + 1 + 0 + + + + + + text + 5 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + 1 + 1 + 1 + 0 + + + <label>Title</label> + <frontend_type>text</frontend_type> + <sort_order>10</sort_order> + <show_in_default>1</show_in_default> + <show_in_website>1</show_in_website> + <show_in_store>1</show_in_store> + + + + select + adminhtml/system_config_source_order_status_new + 20 + 1 + 1 + 0 + + + + allowspecific + 50 + adminhtml/system_config_source_payment_allspecificcountries + 1 + 1 + 0 + + + + multiselect + 51 + adminhtml/system_config_source_country + 1 + 1 + 0 + 1 + + + + textarea + 62 + 1 + 1 + 1 + + + + text + 98 + 1 + 1 + 0 + + + + text + 99 + 1 + 1 + 0 + + + + text + 100 + 1 + 1 + 0 + + + diff --git a/app/code/core/Mage/Paypal/Model/Express/Checkout.php b/app/code/core/Mage/Paypal/Model/Express/Checkout.php index ebe6c27dc9..5f02c592ef 100644 --- a/app/code/core/Mage/Paypal/Model/Express/Checkout.php +++ b/app/code/core/Mage/Paypal/Model/Express/Checkout.php @@ -370,6 +370,8 @@ public function returnFromPaypal($token) ->callGetExpressCheckoutDetails(); $quote = $this->_quote; + $this->_ignoreAddressValidation(); + // import billing address $billingAddress = $quote->getBillingAddress(); $exportedBillingAddress = $this->_api->getExportedBillingAddress(); @@ -380,22 +382,7 @@ public function returnFromPaypal($token) $quote->setCustomerLastname($billingAddress->getLastname()); $quote->setCustomerSuffix($billingAddress->getSuffix()); $quote->setCustomerNote($exportedBillingAddress->getData('note')); - foreach ($exportedBillingAddress->getExportedKeys() as $key) { - $oldData = $billingAddress->getDataUsingMethod($key); - $isEmpty = null; - if (is_array($oldData)) { - foreach($oldData as $val) { - if(!empty($val)) { - $isEmpty = false; - break; - } - $isEmpty = true; - } - } - if (empty($oldData) || $isEmpty === true) { - $billingAddress->setDataUsingMethod($key, $exportedBillingAddress->getData($key)); - } - } + $this->_setExportedAddressData($billingAddress, $exportedBillingAddress); // import shipping address $exportedShippingAddress = $this->_api->getExportedShippingAddress(); @@ -403,9 +390,7 @@ public function returnFromPaypal($token) $shippingAddress = $quote->getShippingAddress(); if ($shippingAddress) { if ($exportedShippingAddress) { - foreach ($exportedShippingAddress->getExportedKeys() as $key) { - $shippingAddress->setDataUsingMethod($key, $exportedShippingAddress->getData($key)); - } + $this->_setExportedAddressData($shippingAddress, $exportedShippingAddress); $shippingAddress->setCollectShippingRates(true); $shippingAddress->setSameAsBilling(0); } @@ -424,7 +409,6 @@ public function returnFromPaypal($token) ); } } - $this->_ignoreAddressValidation(); // import payment info $payment = $quote->getPayment(); @@ -623,8 +607,8 @@ private function _ignoreAddressValidation() $this->_quote->getBillingAddress()->setShouldIgnoreValidation(true); if (!$this->_quote->getIsVirtual()) { $this->_quote->getShippingAddress()->setShouldIgnoreValidation(true); - if (!$this->_config->requireBillingAddress && !$this->getCustomerSession()->isLoggedIn()) { - $this->_quote->getBillingAddress()->setSameAsShipping(1); + if (!$this->_config->requireBillingAddress && !$this->_quote->getBillingAddress()->getEmail()) { + $this->_quote->getBillingAddress()->setSameAsBilling(1); } } } @@ -689,6 +673,32 @@ public function getCheckoutMethod() return $this->_quote->getCheckoutMethod(); } + /** + * Sets address data from exported address + * + * @param Mage_Sales_Model_Quote_Address $address + * @param array $exportedAddress + */ + protected function _setExportedAddressData($address, $exportedAddress) + { + foreach ($exportedAddress->getExportedKeys() as $key) { + $oldData = $address->getDataUsingMethod($key); + $isEmpty = null; + if (is_array($oldData)) { + foreach($oldData as $val) { + if(!empty($val)) { + $isEmpty = false; + break; + } + $isEmpty = true; + } + } + if (empty($oldData) || $isEmpty === true) { + $address->setDataUsingMethod($key, $exportedAddress->getData($key)); + } + } + } + /** * Set create billing agreement flag to api call * diff --git a/app/code/core/Mage/Paypal/Model/Hostedpro/Request.php b/app/code/core/Mage/Paypal/Model/Hostedpro/Request.php index 713d943b4c..7f04428006 100644 --- a/app/code/core/Mage/Paypal/Model/Hostedpro/Request.php +++ b/app/code/core/Mage/Paypal/Model/Hostedpro/Request.php @@ -56,7 +56,7 @@ class Mage_Paypal_Model_Hostedpro_Request extends Varien_Object protected $_buttonVarFormat = 'L_BUTTONVAR%d'; /** - * Request Parameters wich dont have to wrap as button vars + * Request Parameters which dont have to wrap as button vars * * @var array */ diff --git a/app/code/core/Mage/Paypal/etc/system.xml b/app/code/core/Mage/Paypal/etc/system.xml index 07fbf8852c..d6333831a6 100644 --- a/app/code/core/Mage/Paypal/etc/system.xml +++ b/app/code/core/Mage/Paypal/etc/system.xml @@ -77,7 +77,7 @@ 1 1 10 - validate-email + validate-email diff --git a/app/code/core/Mage/ProductAlert/Model/Observer.php b/app/code/core/Mage/ProductAlert/Model/Observer.php index 44570d3999..f1882d295d 100644 --- a/app/code/core/Mage/ProductAlert/Model/Observer.php +++ b/app/code/core/Mage/ProductAlert/Model/Observer.php @@ -97,7 +97,7 @@ protected function _getWebsites() * Process price emails * * @param Mage_ProductAlert_Model_Email $email - * @return Mage_productAlert_Model_Observer + * @return Mage_ProductAlert_Model_Observer */ protected function _processPrice(Mage_ProductAlert_Model_Email $email) { @@ -108,7 +108,10 @@ protected function _processPrice(Mage_ProductAlert_Model_Email $email) if (!$website->getDefaultGroup() || !$website->getDefaultGroup()->getDefaultStore()) { continue; } - if (!Mage::getStoreConfig(self::XML_PATH_PRICE_ALLOW, $website->getDefaultGroup()->getDefaultStore()->getId())) { + if (!Mage::getStoreConfig( + self::XML_PATH_PRICE_ALLOW, + $website->getDefaultGroup()->getDefaultStore()->getId() + )) { continue; } try { @@ -182,7 +185,7 @@ protected function _processPrice(Mage_ProductAlert_Model_Email $email) * Process stock emails * * @param Mage_ProductAlert_Model_Email $email - * @return Mage_productAlert_Model_Observer + * @return Mage_ProductAlert_Model_Observer */ protected function _processStock(Mage_ProductAlert_Model_Email $email) { @@ -194,7 +197,10 @@ protected function _processStock(Mage_ProductAlert_Model_Email $email) if (!$website->getDefaultGroup() || !$website->getDefaultGroup()->getDefaultStore()) { continue; } - if (!Mage::getStoreConfig(self::XML_PATH_STOCK_ALLOW, $website->getDefaultGroup()->getDefaultStore()->getId())) { + if (!Mage::getStoreConfig( + self::XML_PATH_STOCK_ALLOW, + $website->getDefaultGroup()->getDefaultStore()->getId() + )) { continue; } try { @@ -232,7 +238,7 @@ protected function _processStock(Mage_ProductAlert_Model_Email $email) $product = Mage::getModel('catalog/product') ->setStoreId($website->getDefaultStore()->getId()) ->load($alert->getProductId()); - /* @var $product Mage_catalog_Model_Product */ + /* @var $product Mage_Catalog_Model_Product */ if (!$product) { continue; } @@ -269,7 +275,7 @@ protected function _processStock(Mage_ProductAlert_Model_Email $email) /** * Send email to administrator if error * - * @return Mage_productAlert_Model_Observer + * @return Mage_ProductAlert_Model_Observer */ protected function _sendErrorEmail() { @@ -302,7 +308,7 @@ protected function _sendErrorEmail() /** * Run process send product alerts * - * @return Mage_productAlert_Model_Observer + * @return Mage_ProductAlert_Model_Observer */ public function process() { diff --git a/app/code/core/Mage/ProductAlert/etc/system.xml b/app/code/core/Mage/ProductAlert/etc/system.xml index 7b327100d5..22cf059094 100644 --- a/app/code/core/Mage/ProductAlert/etc/system.xml +++ b/app/code/core/Mage/ProductAlert/etc/system.xml @@ -114,6 +114,7 @@ text + validate-email 3 1 0 diff --git a/app/code/core/Mage/Reports/Model/Resource/Report/Abstract.php b/app/code/core/Mage/Reports/Model/Resource/Report/Abstract.php index 853ab70c9a..d67ed06853 100755 --- a/app/code/core/Mage/Reports/Model/Resource/Report/Abstract.php +++ b/app/code/core/Mage/Reports/Model/Resource/Report/Abstract.php @@ -105,7 +105,11 @@ protected function _getFlagData($code) */ protected function _truncateTable($table) { - $this->_getWriteAdapter()->truncateTable($table); + if ($this->_getWriteAdapter()->getTransactionLevel() > 0) { + $this->_getWriteAdapter()->delete($table); + } else { + $this->_getWriteAdapter()->truncateTable($table); + } return $this; } diff --git a/app/code/core/Mage/Review/Model/Resource/Review/Status/Collection.php b/app/code/core/Mage/Review/Model/Resource/Review/Status/Collection.php index ebcdd3b711..9088024e0e 100755 --- a/app/code/core/Mage/Review/Model/Resource/Review/Status/Collection.php +++ b/app/code/core/Mage/Review/Model/Resource/Review/Status/Collection.php @@ -24,7 +24,6 @@ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ - /** * Review statuses collection * @@ -49,7 +48,7 @@ protected function _construct() { $this->_init('review/review_status'); } - + /** * Convert items array to array for select options * diff --git a/app/code/core/Mage/Review/Model/Review/Status.php b/app/code/core/Mage/Review/Model/Review/Status.php index c3f794eb51..d675e701e0 100644 --- a/app/code/core/Mage/Review/Model/Review/Status.php +++ b/app/code/core/Mage/Review/Model/Review/Status.php @@ -39,5 +39,4 @@ public function __construct() { $this->_init('review/review_status'); } - } diff --git a/app/code/core/Mage/Rss/Block/Catalog/Tag.php b/app/code/core/Mage/Rss/Block/Catalog/Tag.php index 12d90d1512..03cb07bc40 100644 --- a/app/code/core/Mage/Rss/Block/Catalog/Tag.php +++ b/app/code/core/Mage/Rss/Block/Catalog/Tag.php @@ -38,7 +38,8 @@ protected function _construct() /* * setting cache to save the rss for 10 minutes */ - $this->setCacheKey('rss_catalog_tag_'.$this->getStoreId()); + $tagModel = Mage::registry('tag_model'); + $this->setCacheKey('rss_catalog_tag_' . $this->getStoreId() . '_' . $tagModel->getName()); $this->setCacheLifetime(600); } @@ -99,9 +100,10 @@ public function addTaggedItemXml($args) $allowedPriceInRss = $product->getAllowedPriceInRss(); $product->unsetData()->load($args['row']['entity_id']); - $description = ''. - ''. - ' + + + + + + + diff --git a/app/design/frontend/default/iphone/template/checkout/onepage/review/totals.phtml b/app/design/frontend/default/iphone/template/checkout/onepage/review/totals.phtml new file mode 100644 index 0000000000..8d3a0a2992 --- /dev/null +++ b/app/design/frontend/default/iphone/template/checkout/onepage/review/totals.phtml @@ -0,0 +1,47 @@ + +getTotals()): ?> + + + renderTotals(null, $_colspan); ?> + renderTotals('footer', $_colspan); ?> + needDisplayBaseGrandtotal()):?> + + + + + + + diff --git a/app/design/frontend/default/iphone/template/customer/account/navigation.phtml b/app/design/frontend/default/iphone/template/customer/account/navigation.phtml new file mode 100644 index 0000000000..dcd74046ae --- /dev/null +++ b/app/design/frontend/default/iphone/template/customer/account/navigation.phtml @@ -0,0 +1,62 @@ + + +Show() + diff --git a/app/design/frontend/default/iphone/template/customer/form/edit.phtml b/app/design/frontend/default/iphone/template/customer/form/edit.phtml index 544617230b..2075565b65 100644 --- a/app/design/frontend/default/iphone/template/customer/form/edit.phtml +++ b/app/design/frontend/default/iphone/template/customer/form/edit.phtml @@ -24,21 +24,9 @@ * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ ?> - - diff --git a/app/design/frontend/default/iphone/template/customer/form/login.phtml b/app/design/frontend/default/iphone/template/customer/form/login.phtml index aac0e2ac4a..ea69231867 100644 --- a/app/design/frontend/default/iphone/template/customer/form/login.phtml +++ b/app/design/frontend/default/iphone/template/customer/form/login.phtml @@ -40,13 +40,15 @@
    • - +
    • - getChildHtml('form.additional.info'); ?> + isRequired('user_login')): ?> + getChildHtml('captcha') ?> +
    __('Forgot your password?') ?> @@ -118,7 +118,7 @@
    - +
    @@ -160,7 +160,9 @@ - getChildHtml('form.additional.info'); ?> + isRequired('user_create')): ?> + getChildHtml('captcha') ?> + __('Back') ?> diff --git a/app/design/frontend/default/iphone/template/page/html/head.phtml b/app/design/frontend/default/iphone/template/page/html/head.phtml index 7cbdec3679..5baed41a54 100644 --- a/app/design/frontend/default/iphone/template/page/html/head.phtml +++ b/app/design/frontend/default/iphone/template/page/html/head.phtml @@ -34,11 +34,13 @@ - getCssJsHtml() ?> diff --git a/app/design/frontend/default/iphone/template/page/switch/languages.phtml b/app/design/frontend/default/iphone/template/page/switch/languages.phtml index b28ee0ac79..f8a1e9c123 100644 --- a/app/design/frontend/default/iphone/template/page/switch/languages.phtml +++ b/app/design/frontend/default/iphone/template/page/switch/languages.phtml @@ -38,7 +38,7 @@ getStores() as $_lang): ?> getId() == $this->getCurrentStoreId()) ? ' class="selected"' : '' ?> > - htmlEscape($_lang->getName()) ?> + escapeHtml($_lang->getName()) ?> diff --git a/app/design/frontend/default/iphone/template/page/switch/stores.phtml b/app/design/frontend/default/iphone/template/page/switch/stores.phtml index aa5b449fc7..1b2452fc93 100644 --- a/app/design/frontend/default/iphone/template/page/switch/stores.phtml +++ b/app/design/frontend/default/iphone/template/page/switch/stores.phtml @@ -38,7 +38,7 @@ getGroups() as $_group): ?> getId() == $this->getCurrentGroupId()) ? ' class="selected"' : '' ?> > - htmlEscape($_group->getName()) ?> + escapeHtml($_group->getName()) ?> diff --git a/app/design/frontend/default/iphone/template/persistent/checkout/onepage/login.phtml b/app/design/frontend/default/iphone/template/persistent/checkout/onepage/login.phtml index 22da54237f..a5707221f6 100644 --- a/app/design/frontend/default/iphone/template/persistent/checkout/onepage/login.phtml +++ b/app/design/frontend/default/iphone/template/persistent/checkout/onepage/login.phtml @@ -44,7 +44,7 @@
  • - +
  • @@ -53,7 +53,9 @@
  • - getChildHtml('form.additional.info'); ?> + isRequired('user_login')): ?> + getChildHtml('captcha') ?> + getChildHtml('persistent.remember.me'); ?> diff --git a/app/design/frontend/default/iphone/template/persistent/customer/form/login.phtml b/app/design/frontend/default/iphone/template/persistent/customer/form/login.phtml index 33a3c99ffc..45dbf0572c 100644 --- a/app/design/frontend/default/iphone/template/persistent/customer/form/login.phtml +++ b/app/design/frontend/default/iphone/template/persistent/customer/form/login.phtml @@ -49,13 +49,15 @@
    • - +
    • - getChildHtml('form.additional.info'); ?> + isRequired('user_login')): ?> + getChildHtml('captcha') ?> + getChildHtml('persistent.remember.me'); ?>
    getChildHtml('persistent.remember.me.tooltip'); ?>__('Forgot your password?') ?> diff --git a/app/design/frontend/default/iphone/template/persistent/customer/form/register.phtml b/app/design/frontend/default/iphone/template/persistent/customer/form/register.phtml index 47de3674c9..c58de1d999 100644 --- a/app/design/frontend/default/iphone/template/persistent/customer/form/register.phtml +++ b/app/design/frontend/default/iphone/template/persistent/customer/form/register.phtml @@ -56,7 +56,7 @@
  • - +
  • isNewsletterEnabled()): ?> @@ -89,26 +89,26 @@
    - +
    - +
  • - +
  • helper('customer/address')->getStreetLines(); $_i<=$_n; $_i++): ?>
  • - +
  • @@ -116,7 +116,7 @@
    - +
    @@ -130,7 +130,7 @@ $('region_id').setAttribute('defaultValue', "getFormData()->getRegionId() ?>"); //]]> - +
    @@ -138,7 +138,7 @@
    - +
    @@ -164,7 +164,9 @@ - getChildHtml('form.additional.info'); ?> + isRequired('user_create')): ?> + getChildHtml('captcha') ?> + getChildHtml('persistent.remember.me'); ?> diff --git a/app/design/frontend/default/iphone/template/sales/order/items.phtml b/app/design/frontend/default/iphone/template/sales/order/items.phtml index 6b4c5b0bf9..cf5739d6f9 100644 --- a/app/design/frontend/default/iphone/template/sales/order/items.phtml +++ b/app/design/frontend/default/iphone/template/sales/order/items.phtml @@ -42,8 +42,8 @@
    diff --git a/app/design/frontend/default/iphone/template/sendfriend/send.phtml b/app/design/frontend/default/iphone/template/sendfriend/send.phtml index 1d1e2de60a..442ee7e32c 100644 --- a/app/design/frontend/default/iphone/template/sendfriend/send.phtml +++ b/app/design/frontend/default/iphone/template/sendfriend/send.phtml @@ -113,20 +113,20 @@
    - +
    - +
  • - +
  • diff --git a/app/design/frontend/default/iphone/template/shipping/tracking/popup.phtml b/app/design/frontend/default/iphone/template/shipping/tracking/popup.phtml index bde1dadbb4..892cb69a05 100644 --- a/app/design/frontend/default/iphone/template/shipping/tracking/popup.phtml +++ b/app/design/frontend/default/iphone/template/shipping/tracking/popup.phtml @@ -24,8 +24,7 @@ * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ ?> - -getTrackingInfo() ?> +getTrackingInfo() ?>
    @@ -42,12 +41,12 @@
    - + getCarrierTitle()): ?> - + getErrorMessage()): ?> @@ -63,7 +62,7 @@ getUrl()): ?> - + getStatus()): ?> diff --git a/app/design/frontend/default/iphone/template/wishlist/view.phtml b/app/design/frontend/default/iphone/template/wishlist/view.phtml index 6318579b00..63a265a64c 100644 --- a/app/design/frontend/default/iphone/template/wishlist/view.phtml +++ b/app/design/frontend/default/iphone/template/wishlist/view.phtml @@ -41,7 +41,7 @@ $product = $item->getProduct(); $isVisibleProduct = $product->isVisibleInSiteVisibility(); ?> -
  • +
  • canHaveQty()) echo 'class="no-qty"' ?>> <?php echo $this->escapeHtml($product->getName()) ?>

    @@ -53,13 +53,15 @@

    getPriceHtml($product) ?> getDetailsHtml($item) ?> - canHaveQty() && $isVisibleProduct): ?> - __('Qty') ?>: - + + + __('Qty') ?>: + + isSaleable()): ?> - + __('Out of stock') ?> @@ -73,6 +75,9 @@
  • +
  • + __('Remove All')?> +
  • isSaleable()):?>
  • __('Add All to Cart') ?>
  • diff --git a/app/design/frontend/default/modern/template/catalog/product/view.phtml b/app/design/frontend/default/modern/template/catalog/product/view.phtml index c44acf1cd8..832a615d89 100644 --- a/app/design/frontend/default/modern/template/catalog/product/view.phtml +++ b/app/design/frontend/default/modern/template/catalog/product/view.phtml @@ -128,6 +128,13 @@ delete Validation.methods['required-entry']; delete Validation.methods['validate-one-required']; delete Validation.methods['validate-one-required-by-name']; + // Remove custom datetime validators + for (var methodName in Validation.methods) { + if (methodName.match(/^validate-datetime-.*/i)) { + delete Validation.methods[methodName]; + } + } + if (this.validator.validate()) { if (url) { this.form.action = url; diff --git a/app/design/frontend/default/modern/template/checkout/cart.phtml b/app/design/frontend/default/modern/template/checkout/cart.phtml index 342ea8681a..32f7559d04 100644 --- a/app/design/frontend/default/modern/template/checkout/cart.phtml +++ b/app/design/frontend/default/modern/template/checkout/cart.phtml @@ -99,8 +99,30 @@ getContinueShoppingUrl()): ?> - + + @@ -126,6 +148,8 @@
    + + getChildHtml('checkout.cart.extra') ?> getChildHtml('coupon') ?> getIsVirtual()): echo $this->getChildHtml('shipping'); endif; ?>
    diff --git a/app/etc/modules/Mage_Api2.xml b/app/etc/modules/Mage_Api2.xml new file mode 100644 index 0000000000..e329b8230f --- /dev/null +++ b/app/etc/modules/Mage_Api2.xml @@ -0,0 +1,39 @@ + + + + + + true + core + + + + + + + diff --git a/app/etc/modules/Mage_OAuth.xml b/app/etc/modules/Mage_OAuth.xml new file mode 100644 index 0000000000..f4aad0ee37 --- /dev/null +++ b/app/etc/modules/Mage_OAuth.xml @@ -0,0 +1,38 @@ + + + + + + true + core + + + + + + diff --git a/app/locale/en_US/Mage_AdminNotification.csv b/app/locale/en_US/Mage_AdminNotification.csv index b1a36d13a3..42673c2eed 100644 --- a/app/locale/en_US/Mage_AdminNotification.csv +++ b/app/locale/en_US/Mage_AdminNotification.csv @@ -24,6 +24,7 @@ "Unable to proceed. Please, try again.","Unable to proceed. Please, try again." "Update Frequency","Update Frequency" "Use HTTPS to Get Feed","Use HTTPS to Get Feed" +"Wrong message type","Wrong message type" "critical","critical" "major","major" "minor","minor" diff --git a/app/locale/en_US/Mage_Adminhtml.csv b/app/locale/en_US/Mage_Adminhtml.csv index 0d2004beda..8cc67436fe 100644 --- a/app/locale/en_US/Mage_Adminhtml.csv +++ b/app/locale/en_US/Mage_Adminhtml.csv @@ -19,6 +19,7 @@ "(You have to increase php memory_limit before changing this value)","(You have to increase php memory_limit before changing this value)" "(\\t for tab)","(\\t for tab)" "* - If indexing is in progress, it will be killed and new indexing process will start.","* - If indexing is in progress, it will be killed and new indexing process will start." +"* Required Fields","* Required Fields" "- Click on any of the time parts to increase it","- Click on any of the time parts to increase it" "- Hold mouse button on any of the above buttons for faster selection.","- Hold mouse button on any of the above buttons for faster selection." "- Use the %s buttons to select month","- Use the %s buttons to select month" @@ -42,6 +43,7 @@ "2YTD","2YTD" "6 Hours","6 Hours" "

    404 Error

    Page not found.

    ","

    404 Error

    Page not found.

    " +"%s request access to your account","%s request access to your account" "Attention: Captcha is case sensitive.","Attention: Captcha is case sensitive." "A user with the same user name or email aleady exists.","A user with the same user name or email aleady exists." "API Key","API Key" @@ -85,6 +87,7 @@ "Advanced Admin Section","Advanced Admin Section" "Advanced Profiles","Advanced Profiles" "Advanced Section","Advanced Section" +"After authorization application will have access to you account.","After authorization application will have access to you account." "All","All" "All Allowed Countries","All Allowed Countries" "All Cache","All Cache" @@ -146,7 +149,12 @@ "Associated Tags","Associated Tags" "Attribute Set Name:","Attribute Set Name:" "Attributes","Attributes" -"Automatic","Automatic" +"Authorization confirmed","Authorization confirmed" +"Authorize","Authorize" +"Authorize application","Authorize application" +"Authorized OAuth Tokens","Authorized OAuth Tokens" +"Automatic (equalize price ranges)","Automatic (equalize price ranges)" +"Automatic (equalize product counts)","Automatic (equalize product counts)" "Average Order Amount","Average Order Amount" "Average Orders","Average Orders" "BINARY","BINARY" @@ -212,10 +220,12 @@ "Complete","Complete" "Configuration","Configuration" "Confirm New Password","Confirm New Password" +"Confirm token authorization Pop Up for admin","Confirm token authorization Pop Up for admin" +"Confirm token authorization for admin","Confirm token authorization for admin" +"Confirmation Of Authorization","Confirmation Of Authorization" "Confirmed email:","Confirmed email:" "Connect with the Magento Community","Connect with the Magento Community" "Continue","Continue" -"Continuous","Continuous" "Convert to Plain Text","Convert to Plain Text" "Cookie (unsafe)","Cookie (unsafe)" "Country","Country" @@ -379,6 +389,7 @@ "General Section","General Section" "Get Image Base64","Get Image Base64" "Get help for this page","Get help for this page" +"Give the verifier code to application administrator","Give the verifier code to application administrator" "Global Attribute","Global Attribute" "Global Record Search","Global Record Search" "Global Search","Global Search" @@ -484,8 +495,10 @@ "Local Server","Local Server" "Local/Remote Server","Local/Remote Server" "Locale","Locale" +"Log In","Log In" "Log Out","Log Out" "Log in to Admin Panel","Log in to Admin Panel" +"Log in to use %s","Log in to use %s" "Log into Magento Admin Page","Log into Magento Admin Page" "Logged in as %s","Logged in as %s" "Login","Login" @@ -524,6 +537,7 @@ "Most Viewed Products","Most Viewed Products" "Multiple Select","Multiple Select" "My Account","My Account" +"My Applications","My Applications" "N/A","N/A" "NOTICE","NOTICE" "Name","Name" @@ -597,6 +611,9 @@ "Number of Uses","Number of Uses" "Number of Views","Number of Views" "Number of records:","Number of records:" +"OAuth Consumers","OAuth Consumers" +"OAuth authorization for admin","OAuth authorization for admin" +"OAuth authorization simple for admin","OAuth authorization simple for admin" "OK","OK" "Old rate:","Old rate:" "One or more media files failed to be synchronized during the media storages syncronization process. Refer to the log file for details.","One or more media files failed to be synchronized during the media storages syncronization process. Refer to the log file for details." @@ -741,6 +758,7 @@ "Quantity","Quantity" "Queue Refresh","Queue Refresh" "Queued... Cancel","Queued... Cancel" +"REST Roles","REST Roles" "Radio Buttons","Radio Buttons" "Rates","Rates" "Read details","Read details" @@ -760,6 +778,10 @@ "Refresh Statistics","Refresh Statistics" "Region/State","Region/State" "Regular Price:","Regular Price:" +"Reject","Reject" +"Reject token authorization Pop Up for admin","Reject token authorization Pop Up for admin" +"Reject token authorization for admin","Reject token authorization for admin" +"Rejection Of Authorization","Rejection Of Authorization" "Release","Release" "Release Stability","Release Stability" "Release Version","Release Version" @@ -1076,6 +1098,7 @@ "User Role","User Role" "User Roles","User Roles" "User Roles Information","User Roles Information" +"User Type Resources","User Type Resources" "User name","User name" "Users","Users" "VAT Number is Invalid","VAT Number is Invalid" @@ -1089,6 +1112,7 @@ "Variable ID","Variable ID" "Variable Name","Variable Name" "Variable Plain Value","Variable Plain Value" +"Verifier code: %s","Verifier code: %s" "View Actions XML","View Actions XML" "View Full Size","View Full Size" "View Memo","View Memo" diff --git a/app/locale/en_US/Mage_Api.csv b/app/locale/en_US/Mage_Api.csv index 5c6674a0d9..c7352a4a01 100644 --- a/app/locale/en_US/Mage_Api.csv +++ b/app/locale/en_US/Mage_Api.csv @@ -1,4 +1,5 @@ "Access denied.","Access denied." +"Can not find webservice adapter.","Can not find webservice adapter." "Client Session Timeout (sec.)","Client Session Timeout (sec.)" "Default Response Charset","Default Response Charset" "Email","Email" @@ -7,10 +8,10 @@ "Invalid webservice handler specified.","Invalid webservice handler specified." "Magento Core API","Magento Core API" "Magento Core API Section","Magento Core API Section" -"Roles","Roles" +"SOAP/XML-RPC - Roles","SOAP/XML-RPC - Roles" +"SOAP/XML-RPC - Users","SOAP/XML-RPC - Users" "Unable to login.","Unable to login." "User Name","User Name" -"Users","Users" "WS-I Compliance","WS-I Compliance" "Web Services","Web Services" "Your account has been deactivated.","Your account has been deactivated." diff --git a/app/locale/en_US/Mage_Api2.csv b/app/locale/en_US/Mage_Api2.csv new file mode 100644 index 0000000000..be092fd6db --- /dev/null +++ b/app/locale/en_US/Mage_Api2.csv @@ -0,0 +1,203 @@ +"%s","%s" +"ACL Attribute Rules","ACL Attribute Rules" +"ACL Attributes Information","ACL Attributes Information" +"Add","Add" +"Add Admin Role","Add Admin Role" +"Add New Role","Add New Role" +"Address Type","Address Type" +"Admin","Admin" +"Allow","Allow" +"An error occurred while deleting the role.","An error occurred while deleting the role." +"An error occurred while saving attribute rules.","An error occurred while saving attribute rules." +"An error occurred while saving role.","An error occurred while saving role." +"Api Rules Information","Api Rules Information" +"Assigned","Assigned" +"Associate to Website","Associate to Website" +"Attribute Rules Information","Attribute Rules Information" +"Attributes","Attributes" +"Automatically Return Credit Memo Item to Stock","Automatically Return Credit Memo Item to Stock" +"Back","Back" +"Backorders","Backorders" +"Base Currency","Base Currency" +"Base Customer Balance","Base Customer Balance" +"Base Discount","Base Discount" +"Base Discount Amount","Base Discount Amount" +"Base Item Subtotal","Base Item Subtotal" +"Base Item Subtotal Including Tax","Base Item Subtotal Including Tax" +"Base Original Price","Base Original Price" +"Base Price","Base Price" +"Base Price Including Tax","Base Price Including Tax" +"Base Shipping","Base Shipping" +"Base Shipping Discount","Base Shipping Discount" +"Base Shipping Tax","Base Shipping Tax" +"Base Subtotal","Base Subtotal" +"Base Subtotal Including Tax","Base Subtotal Including Tax" +"Base Tax Amount","Base Tax Amount" +"Base Total Due","Base Total Due" +"Base Total Paid","Base Total Paid" +"Base Total Refunded","Base Total Refunded" +"Can Be Divided into Multiple Boxes for Shipping","Can Be Divided into Multiple Boxes for Shipping" +"Canceled Qty","Canceled Qty" +"Catalog","Catalog" +"CatalogInventory","CatalogInventory" +"City","City" +"Company","Company" +"Country","Country" +"Coupon Code","Coupon Code" +"Create","Create" +"Created From","Created From" +"Customer","Customer" +"Customer Address","Customer Address" +"Customer Address ID","Customer Address ID" +"Customer Balance","Customer Balance" +"Customer First Name","Customer First Name" +"Customer ID","Customer ID" +"Customer Last Name","Customer Last Name" +"Customer Middle Name","Customer Middle Name" +"Customer Prefix","Customer Prefix" +"Customer Suffix","Customer Suffix" +"Delete","Delete" +"Deny","Deny" +"Disable automatic group change based on VAT ID","Disable automatic group change based on VAT ID" +"Discount","Discount" +"Discount Amount","Discount Amount" +"Discount Description","Discount Description" +"Edit","Edit" +"Edit %s ACL attribute rules","Edit %s ACL attribute rules" +"Edit Role","Edit Role" +"Edit attribute rules for %s Role","Edit attribute rules for %s Role" +"Email","Email" +"Enable Qty Increments","Enable Qty Increments" +"First Name","First Name" +"Gift Message","Gift Message" +"Grand Total","Grand Total" +"Grand Total to Be Charged","Grand Total to Be Charged" +"Group","Group" +"Guest","Guest" +"ID","ID" +"Invoiced Qty","Invoiced Qty" +"Is Confirmed","Is Confirmed" +"Is Default Billing Address","Is Default Billing Address" +"Is Default Shipping Address","Is Default Shipping Address" +"Item ID","Item ID" +"Item Subtotal","Item Subtotal" +"Item Subtotal Including Tax","Item Subtotal Including Tax" +"Last Logged In","Last Logged In" +"Last Name","Last Name" +"Low Stock Date","Low Stock Date" +"Manage Stock","Manage Stock" +"Maximum Qty Allowed in Shopping Cart","Maximum Qty Allowed in Shopping Cart" +"Minimum Qty Allowed in Shopping Cart","Minimum Qty Allowed in Shopping Cart" +"New Role","New Role" +"Notify for Quantity Below","Notify for Quantity Below" +"OAuth","OAuth" +"Order Addresses","Order Addresses" +"Order Comments","Order Comments" +"Order Currency","Order Currency" +"Order Date","Order Date" +"Order ID","Order ID" +"Order ID (internal)","Order ID (internal)" +"Order Item ID","Order Item ID" +"Order Items","Order Items" +"Order Status","Order Status" +"Ordered Qty","Ordered Qty" +"Orders","Orders" +"Original Price","Original Price" +"Parent Order Item ID","Parent Order Item ID" +"Payment Method","Payment Method" +"Phone Number","Phone Number" +"Placed from IP","Placed from IP" +"Please enter a valid number in ""max_sale_qty"" field","Please enter a valid number in ""max_sale_qty"" field" +"Please enter a valid number in ""min_qty"" field","Please enter a valid number in ""min_qty"" field" +"Please enter a valid number in ""min_sale_qty"" field","Please enter a valid number in ""min_sale_qty"" field" +"Please enter a valid number in ""notify_stock_qty"" field","Please enter a valid number in ""notify_stock_qty"" field" +"Please use numbers only in ""qty_increments"" field. Please avoid spaces or other characters such as dots or commas.","Please use numbers only in ""qty_increments"" field. Please avoid spaces or other characters such as dots or commas." +"Price","Price" +"Price Including Tax","Price Including Tax" +"Product","Product" +"Product ID","Product ID" +"Product and Custom Options Name","Product and Custom Options Name" +"Qty","Qty" +"Qty Increments","Qty Increments" +"Qty Uses Decimals","Qty Uses Decimals" +"Qty for Item's Status to Become Out of Stock","Qty for Item's Status to Become Out of Stock" +"REST - Attributes","REST - Attributes" +"REST - Roles","REST - Roles" +"REST ACL Attributes","REST ACL Attributes" +"REST Attributes","REST Attributes" +"REST Role","REST Role" +"REST Roles","REST Roles" +"REST Roles Information","REST Roles Information" +"Read","Read" +"Refunded Qty","Refunded Qty" +"Rest Roles","Rest Roles" +"Retrieve","Retrieve" +"Role ""%s"" no longer exists","Role ""%s"" no longer exists" +"Role ""%s"" not found.","Role ""%s"" not found." +"Role API Resources","Role API Resources" +"Role Info","Role Info" +"Role Information","Role Information" +"Role Name","Role Name" +"Role Users","Role Users" +"Role has been deleted.","Role has been deleted." +"Roles","Roles" +"SKU","SKU" +"Sales","Sales" +"Save","Save" +"Shipped Qty","Shipped Qty" +"Shipping Amount","Shipping Amount" +"Shipping Discount","Shipping Discount" +"Shipping Including Tax","Shipping Including Tax" +"Shipping Method","Shipping Method" +"Shipping Tax","Shipping Tax" +"State","State" +"Stock Availability","Stock Availability" +"Stock ID","Stock ID" +"Stock Item","Stock Item" +"Store Currency to Order Currency Rate","Store Currency to Order Currency Rate" +"Store Name","Store Name" +"Street","Street" +"Subtotal","Subtotal" +"Subtotal Including Tax","Subtotal Including Tax" +"System","System" +"Tax Amount","Tax Amount" +"Tax Name","Tax Name" +"Tax Percent","Tax Percent" +"Tax Rate","Tax Rate" +"The ""enable_qty_increments"" field must be set to 0 or 1.","The ""enable_qty_increments"" field must be set to 0 or 1." +"The ""is_decimal_divided"" field must be set to 0 or 1.","The ""is_decimal_divided"" field must be set to 0 or 1." +"The ""is_in_stock"" field must be set to 0 or 1.","The ""is_in_stock"" field must be set to 0 or 1." +"The ""is_qty_decimal"" field must be set to 0 or 1.","The ""is_qty_decimal"" field must be set to 0 or 1." +"The ""is_qty_decimal"" field must be set to 0, 1, or 2.","The ""is_qty_decimal"" field must be set to 0, 1, or 2." +"The ""manage_stock"" field must be set to 0 or 1.","The ""manage_stock"" field must be set to 0 or 1." +"The ""stock_status_changed_auto"" field must be set to 0 or 1.","The ""stock_status_changed_auto"" field must be set to 0 or 1." +"The ""use_config_backorders"" field must be set to 0 or 1.","The ""use_config_backorders"" field must be set to 0 or 1." +"The ""use_config_enable_qty_inc"" field must be set to 0 or 1.","The ""use_config_enable_qty_inc"" field must be set to 0 or 1." +"The ""use_config_manage_stock"" field must be set to 0 or 1.","The ""use_config_manage_stock"" field must be set to 0 or 1." +"The ""use_config_max_sale_qty"" field must be set to 0 or 1.","The ""use_config_max_sale_qty"" field must be set to 0 or 1." +"The ""use_config_min_qty"" field must be set to 0 or 1.","The ""use_config_min_qty"" field must be set to 0 or 1." +"The ""use_config_min_sale_qty"" field must be set to 0 or 1.","The ""use_config_min_sale_qty"" field must be set to 0 or 1." +"The ""use_config_notify_stock_qty"" field must be set to 0 or 1.","The ""use_config_notify_stock_qty"" field must be set to 0 or 1." +"The ""use_config_qty_increments"" field must be set to 0 or 1.","The ""use_config_qty_increments"" field must be set to 0 or 1." +"The attribute rules were saved.","The attribute rules were saved." +"The role has been saved.","The role has been saved." +"The role is a special one and not for assigning it to admin users.","The role is a special one and not for assigning it to admin users." +"Total Due","Total Due" +"Total Paid","Total Paid" +"Total Refunded","Total Refunded" +"Update","Update" +"Use Config Settings for Backorders","Use Config Settings for Backorders" +"Use Config Settings for Enable Qty Increments","Use Config Settings for Enable Qty Increments" +"Use Config Settings for Manage Stock","Use Config Settings for Manage Stock" +"Use Config Settings for Maximum Qty Allowed in Shopping Cart","Use Config Settings for Maximum Qty Allowed in Shopping Cart" +"Use Config Settings for Minimum Qty Allowed in Shopping Cart","Use Config Settings for Minimum Qty Allowed in Shopping Cart" +"Use Config Settings for Notify for Quantity Below","Use Config Settings for Notify for Quantity Below" +"Use Config Settings for Qty Increments","Use Config Settings for Qty Increments" +"Use Config Settings for Qty for Item's Status to Become Out of Stock","Use Config Settings for Qty for Item's Status to Become Out of Stock" +"User Type","User Type" +"User type ""%s"" no longer exists","User type ""%s"" no longer exists" +"User type ""%s"" not found.","User type ""%s"" not found." +"Web Services","Web Services" +"Web services","Web services" +"Write","Write" +"ZIP/Postal Code","ZIP/Postal Code" diff --git a/app/locale/en_US/Mage_Backup.csv b/app/locale/en_US/Mage_Backup.csv index 5d3ceb0139..6af5f4f312 100644 --- a/app/locale/en_US/Mage_Backup.csv +++ b/app/locale/en_US/Mage_Backup.csv @@ -28,8 +28,8 @@ "Not enough free space to create backup.","Not enough free space to create backup." "Not enough permissions to create backup.","Not enough permissions to create backup." "Not enough permissions to perform rollback","Not enough permissions to perform rollback" -"Please deselect the sufficient check-box, if you want to continue backup's creation","Please deselect the sufficient check-box, if you want to continue backup's creation" -"Please deselect the sufficient check-box, if you want to continue rollback processing","Please deselect the sufficient check-box, if you want to continue rollback processing" +"Please either unselect the ""Put store on the maintenance mode"" checkbox or update your permissions to proceed with the backup.""","Please either unselect the ""Put store on the maintenance mode"" checkbox or update your permissions to proceed with the backup.""" +"Please either unselect the ""Put store on the maintenance mode"" checkbox or update your permissions to proceed with the rollback.""","Please either unselect the ""Put store on the maintenance mode"" checkbox or update your permissions to proceed with the rollback.""" "Put store on the maintenance mode while backup's creation","Put store on the maintenance mode while backup's creation" "Rollback","Rollback" "Scheduled Backup Settings","Scheduled Backup Settings" @@ -41,10 +41,11 @@ "The database backup has been created.","The database backup has been created." "The file was compressed with Zlib, but this extension is not installed on server.","The file was compressed with Zlib, but this extension is not installed on server." "The selected backup(s) has been deleted.","The selected backup(s) has been deleted." +"The system (excluding Media) backup has been created.","The system (excluding Media) backup has been created." "The system backup has been created.","The system backup has been created." "Time","Time" "Type","Type" "Unable to create backup. Please, try again later.","Unable to create backup. Please, try again later." "Unable to save the cron expression.","Unable to save the cron expression." -"Warning! System couldn't put store on the maintenance mode.","Warning! System couldn't put store on the maintenance mode." "Wrong order of creation for new backup.","Wrong order of creation for new backup." +"You do not have sufficient permissions to enable Maintenance Mode during this operation.","You do not have sufficient permissions to enable Maintenance Mode during this operation." diff --git a/app/locale/en_US/Mage_CatalogSearch.csv b/app/locale/en_US/Mage_CatalogSearch.csv index 9149c0114e..4b34c898f4 100644 --- a/app/locale/en_US/Mage_CatalogSearch.csv +++ b/app/locale/en_US/Mage_CatalogSearch.csv @@ -20,7 +20,7 @@ "List","List" "Maximum Query Length","Maximum Query Length" "Maximum Query Words Count","Maximum Query Words Count" -"Maximum Search query length is %s. Your query was cut.","Maximum Search query length is %s. Your query was cut." +"Maximum Search query length is %s. Your query was cut.","Maximum Search query length is %s. Your query was cut." "Maximum words count is %1$s. In your search query was cut next part: %2$s.","Maximum words count is %1$s. In your search query was cut next part: %2$s." "Minimal Query Length","Minimal Query Length" "Minimum Search query length is %s","Minimum Search query length is %s" diff --git a/app/locale/en_US/Mage_Core.csv b/app/locale/en_US/Mage_Core.csv index 0b437ec81f..8cb81395af 100644 --- a/app/locale/en_US/Mage_Core.csv +++ b/app/locale/en_US/Mage_Core.csv @@ -138,6 +138,7 @@ "General Contact Email","General Contact Email" "General Contact Name","General Contact Name" "General Settings","General Settings" +"Get info about current Magento installation","Get info about current Magento installation" "Global","Global" "HTML Head","HTML Head" "HTML tags are not allowed","HTML tags are not allowed" @@ -148,6 +149,7 @@ "If the current frame position does not cover utmost pages, will render link to current position plus/minus this value.","If the current frame position does not cover utmost pages, will render link to current position plus/minus this value." "Incorrect credit card expiration date.","Incorrect credit card expiration date." "Input type ""%value%"" not found in the input types list.","Input type ""%value%"" not found in the input types list." +"Invalid URL '%value%'.","Invalid URL '%value%'." "Invalid base url type","Invalid base url type" "Invalid block name to set child %s: %s","Invalid block name to set child %s: %s" "Invalid block type: %s","Invalid block type: %s" @@ -171,6 +173,8 @@ "Logo Image","Logo Image" "Logo Image Alt","Logo Image Alt" "Logo Image Src","Logo Image Src" +"Magento info","Magento info" +"Magento info API","Magento info API" "Mail Sending Settings","Mail Sending Settings" "Make sure that base URL ends with '/' (slash), e.g. http://yourdomain/magento/","Make sure that base URL ends with '/' (slash), e.g. http://yourdomain/magento/" "Manage Stores","Manage Stores" @@ -180,7 +184,6 @@ "Merge JavaScript Files","Merge JavaScript Files" "Miscellaneous HTML","Miscellaneous HTML" "Miscellaneous Scripts","Miscellaneous Scripts" -"Model class does not exist: %s.","Model class does not exist: %s." "Model collection resource name is not defined.","Model collection resource name is not defined." "Module ""%1$s"" cannot depend on ""%2$s"".","Module ""%1$s"" cannot depend on ""%2$s""." "Module ""%1$s"" requires module ""%2$s"".","Module ""%1$s"" requires module ""%2$s""." @@ -256,6 +259,7 @@ "Requested file may not include parent directory traversal (""../"", ""..\\ notation)""","Requested file may not include parent directory traversal (""../"", ""..\\ notation)""" "Requested invalid store ""%s""","Requested invalid store ""%s""" "Resource is not set.","Resource is not set." +"Retrieve info about current Magento installation","Retrieve info about current Magento installation" "Retrieve store data","Retrieve store data" "Retrieve store list","Retrieve store list" "Return-Path Email","Return-Path Email" diff --git a/app/locale/en_US/Mage_Customer.csv b/app/locale/en_US/Mage_Customer.csv index 89c8042a56..341036e37f 100644 --- a/app/locale/en_US/Mage_Customer.csv +++ b/app/locale/en_US/Mage_Customer.csv @@ -352,6 +352,7 @@ "Show Prefix","Show Prefix" "Show Suffix","Show Suffix" "Show Tax/VAT Number","Show Tax/VAT Number" +"Show VAT Number on Frontend","Show VAT Number on Frontend" "Sign Up for Newsletter","Sign Up for Newsletter" "Skipping import row, required field ""%s"" is not defined.","Skipping import row, required field ""%s"" is not defined." "Skipping import row, website ""%s"" field does not exist.","Skipping import row, website ""%s"" field does not exist." @@ -360,7 +361,6 @@ "Store","Store" "Store View","Store View" "Street Address","Street Address" -"Street Address ","Street Address " "Street Address %s","Street Address %s" "Subject","Subject" "Submit","Submit" @@ -411,6 +411,7 @@ "This email will be sent instead of default welcome email, after account confirmation.","This email will be sent instead of default welcome email, after account confirmation." "This is My Default %s Address","This is My Default %s Address" "To Cart","To Cart" +"To show VAT number on frontend, set Show VAT Number on Frontend option to Yes.","To show VAT number on frontend, set Show VAT Number on Frontend option to Yes." "Total","Total" "Type","Type" "Unknown","Unknown" diff --git a/app/locale/en_US/Mage_Downloadable.csv b/app/locale/en_US/Mage_Downloadable.csv index 0a37bc44a7..aeb27bcbc8 100644 --- a/app/locale/en_US/Mage_Downloadable.csv +++ b/app/locale/en_US/Mage_Downloadable.csv @@ -2,7 +2,6 @@ "Add New Row","Add New Row" "Add links and samples to downloadable product","Add links and samples to downloadable product" "Alphanumeric, dash and underscore characters are recommended for filenames. Improper characters are replaced with \'_\'.","Alphanumeric, dash and underscore characters are recommended for filenames. Improper characters are replaced with \'_\'." -"An error occurred while getting requested content. Please contact the store owner.","An error occurred while getting requested content. Please contact the store owner." "An error occurred while getting the requested content.","An error occurred while getting the requested content." "An error occurred while getting the requested content. Please contact the store owner.","An error occurred while getting the requested content. Please contact the store owner." "An error occurred while saving the file(s).","An error occurred while saving the file(s)." @@ -69,6 +68,7 @@ "See price before order confirmation.","See price before order confirmation." "Shareable","Shareable" "Shipped","Shipped" +"Sorry, there was an error getting requested content. Please contact the store owner.","Sorry, there was an error getting requested content. Please contact the store owner." "Sort Order","Sort Order" "Start Download","Start Download" "Status","Status" diff --git a/app/locale/en_US/Mage_GoogleCheckout.csv b/app/locale/en_US/Mage_GoogleCheckout.csv index 87b363f9ec..1d0ebde617 100644 --- a/app/locale/en_US/Mage_GoogleCheckout.csv +++ b/app/locale/en_US/Mage_GoogleCheckout.csv @@ -8,7 +8,6 @@ "A virtual item to reflect the discount total","A virtual item to reflect the discount total" "A virtual item to reflect the tax total","A virtual item to reflect the tax total" "AVS Status: %s","AVS Status: %s" -"Add","Add" "Add Shipping Method","Add Shipping Method" "Allowed Methods","Allowed Methods" "Amount: %s","Amount: %s" @@ -109,8 +108,7 @@ "Rate 3 Ship To Applicable Countries","Rate 3 Ship To Applicable Countries" "Rate 3 Ship to Specific Countries","Rate 3 Ship to Specific Countries" "Rate 3 Title","Rate 3 Title" -"Remove","Remove" -"Required for live Google Checkout transactions.","Required for live Google Checkout transactions." +"Required for live Google Checkout transactions. Make sure that this option corresponds to Use Secure URLs in Frontend (""Web"" > ""Secure"").","Required for live Google Checkout transactions. Make sure that this option corresponds to Use Secure URLs in Frontend (""Web"" > ""Secure"")." "Residential","Residential" "Sandbox","Sandbox" "Secure Callback URL","Secure Callback URL" diff --git a/app/locale/en_US/Mage_ImportExport.csv b/app/locale/en_US/Mage_ImportExport.csv index 029ea5d197..cda2066910 100644 --- a/app/locale/en_US/Mage_ImportExport.csv +++ b/app/locale/en_US/Mage_ImportExport.csv @@ -1,20 +1,27 @@ -" in rows: "," in rows: " +"%s file does not exists or is not readable","%s file does not exists or is not readable" "-- Please Select --","-- Please Select --" "Adapter must be an instance of Mage_ImportExport_Model_Import_Adapter_Abstract","Adapter must be an instance of Mage_ImportExport_Model_Import_Adapter_Abstract" +"Adapter object must be an instance of %s","Adapter object must be an instance of %s" "Adapter type must be a non empty string","Adapter type must be a non empty string" +"Begin data validation","Begin data validation" +"Begin export of %s","Begin export of %s" +"Begin import of ""%s"" with ""%s"" behavior","Begin import of ""%s"" with ""%s"" behavior" +"CSV","CSV" "Can not determine attribute filter type","Can not determine attribute filter type" -"Can not find required columns: ","Can not find required columns: " -"Can not get autoincrement value","Can not get autoincrement value" +"Can not find required columns: %s","Can not find required columns: %s" +"Cannot get autoincrement value","Cannot get autoincrement value" "Check Data","Check Data" "Column names have duplicates","Column names have duplicates" "Column names is empty or is not an array","Column names is empty or is not an array" "Column names: ""%s"" are invalid","Column names: ""%s"" are invalid" -"Continue","Continue" +"Customers","Customers" "Data is invalid or file is not uploaded","Data is invalid or file is not uploaded" "Destination directory is not writable","Destination directory is not writable" "Destination file is not writable","Destination file is not writable" "Destination file path must be a string","Destination file path must be a string" -"Entity Attributes","Entity Attributes" +"Done import data validation","Done import data validation" +"Duplicate Unique Attribute for '%s'","Duplicate Unique Attribute for '%s'" +"Entity adapter obejct must be an instance of Mage_ImportExport_Model_Export_Entity_Abstract","Entity adapter obejct must be an instance of Mage_ImportExport_Model_Export_Entity_Abstract" "Entity adapter object must be an instance of Mage_ImportExport_Model_Import_Entity_Abstract","Entity adapter object must be an instance of Mage_ImportExport_Model_Import_Entity_Abstract" "Entity is unknown","Entity is unknown" "Entity type model must be an instance of Mage_ImportExport_Model_Export_Entity_Product_Type_Abstract","Entity type model must be an instance of Mage_ImportExport_Model_Export_Entity_Product_Type_Abstract" @@ -22,23 +29,24 @@ "Error in data structure: behaviors are mixed","Error in data structure: behaviors are mixed" "Error in data structure: entity codes are mixed","Error in data structure: entity codes are mixed" "Export","Export" -"Export FAQ","Export FAQ" -"FAQ","FAQ" +"Export has been done.","Export has been done." +"Exported %s rows.","Exported %s rows." +"File does not contain data.","File does not contain data." "File does not contain data. Please upload another one","File does not contain data. Please upload another one" "File format is unknown","File format is unknown" "File is partially valid, but import is not possible","File is partially valid, but import is not possible" "File is totally invalid. Please fix errors and re-upload file","File is totally invalid. Please fix errors and re-upload file" "File is valid! To start import process press ""Import"" button","File is valid! To start import process press ""Import"" button" "File is valid, but import is not possible","File is valid, but import is not possible" +"File was not uploaded","File was not uploaded" "Header column names already set","Header column names already set" "Import","Import" -"Import / Export FAQ (Frequently Asked Questions)","Import / Export FAQ (Frequently Asked Questions)" -"Import FAQ","Import FAQ" +"Import has been done successfuly.","Import has been done successfuly." "Import successfully done.","Import successfully done." "Import/Export","Import/Export" -"In/Out","In/Out" "Input entity code is not equal to entity adapter code","Input entity code is not equal to entity adapter code" "Invalid entity","Invalid entity" +"Invalid entity model","Invalid entity model" "Invalid file format","Invalid file format" "Invalid parameters","Invalid parameters" "Invalid seek position","Invalid seek position" @@ -51,13 +59,15 @@ "Not implemented yet","Not implemented yet" "Please fix errors and re-upload file","Please fix errors and re-upload file" "Please fix errors and re-upload file or simply press ""Import"" button to skip rows with errors","Please fix errors and re-upload file or simply press ""Import"" button to skip rows with errors" -"Some other FAQ","Some other FAQ" +"Products","Products" "Source file moving failed","Source file moving failed" "Source file path must be a string","Source file path must be a string" "Source is not set","Source is not set" "Status","Status" -"System busy","System busy" "There are no product types available for export","There are no product types available for export" +"There is no data for export","There is no data for export" "Total size of uploadable files must not exceed %s","Total size of uploadable files must not exceed %s" "Uploaded file has no extension","Uploaded file has no extension" -"Validation Results","Validation Results" +"Validation finished successfully","Validation finished successfully" +"in rows","in rows" +"in rows:","in rows:" diff --git a/app/locale/en_US/Mage_Index.csv b/app/locale/en_US/Mage_Index.csv index 4d02a5509c..4de4d3ae35 100644 --- a/app/locale/en_US/Mage_Index.csv +++ b/app/locale/en_US/Mage_Index.csv @@ -6,6 +6,7 @@ "Change Index Mode","Change Index Mode" "Click here to go to Cache Management and refresh cache types.","Click here to go to Cache Management and refresh cache types." "Click here to go to Index Management and rebuild required indexes.","Click here to go to Index Management and rebuild required indexes." +"Delete","Delete" "Description","Description" "Disable","Disable" "Enable","Enable" @@ -32,6 +33,7 @@ "Refresh","Refresh" "Reindex Data","Reindex Data" "Reindex Required","Reindex Required" +"Revoke","Revoke" "Status","Status" "System","System" "The index has been saved.","The index has been saved." diff --git a/app/locale/en_US/Mage_OAuth.csv b/app/locale/en_US/Mage_OAuth.csv new file mode 100644 index 0000000000..1b204f18c9 --- /dev/null +++ b/app/locale/en_US/Mage_OAuth.csv @@ -0,0 +1,111 @@ +"%name% '%value%' is too long. It must has length %min% symbols.","%name% '%value%' is too long. It must has length %min% symbols." +"%name% '%value%' is too short. It must has length %min% symbols.","%name% '%value%' is too short. It must has length %min% symbols." +"%s request access to your account","%s request access to your account" +"Admin","Admin" +"After authorization application will have access to you account.","After authorization application will have access to you account." +"An error occurred on confirm authorize.","An error occurred on confirm authorize." +"An error occurred on delete action.","An error occurred on delete action." +"An error occurred on delete application.","An error occurred on delete application." +"An error occurred on reject authorize.","An error occurred on reject authorize." +"An error occurred on saving consumer data.","An error occurred on saving consumer data." +"An error occurred on update revoke status.","An error occurred on update revoke status." +"An error occurred while deleting the consumer.","An error occurred while deleting the consumer." +"An error occurred.","An error occurred." +"An error occurred. Your authorization request is invalid.","An error occurred. Your authorization request is invalid." +"App Name","App Name" +"Application ""%s"" has been deleted.","Application ""%s"" has been deleted." +"Application ""%s"" has been enabled.","Application ""%s"" has been enabled." +"Application ""%s"" has been revoked.","Application ""%s"" has been revoked." +"Application Name","Application Name" +"Application not found.","Application not found." +"Are you sure you want to delete this application?","Are you sure you want to delete this application?" +"Are you sure you want to disable this application?","Are you sure you want to disable this application?" +"Are you sure you want to enable this application?","Are you sure you want to enable this application?" +"Authorization confirmed.","Authorization confirmed." +"Authorize","Authorize" +"Authorize application","Authorize application" +"Authorized Tokens","Authorized Tokens" +"Callback URL","Callback URL" +"Cleanup Probability","Cleanup Probability" +"Cleanup Settings","Cleanup Settings" +"Confirm OAuth token authorization","Confirm OAuth token authorization" +"Confirm token authorization Pop Up for admin","Confirm token authorization Pop Up for admin" +"Confirm token authorization for admin","Confirm token authorization for admin" +"Confirmation Of Authorization","Confirmation Of Authorization" +"Consumer Information","Consumer Information" +"Consumer Name","Consumer Name" +"Consumers","Consumers" +"Created At","Created At" +"Customer","Customer" +"Customer My Account My OAuth Applications","Customer My Account My OAuth Applications" +"Delete","Delete" +"Disable","Disable" +"Disabled","Disabled" +"Edit","Edit" +"Edit Consumer","Edit Consumer" +"Email","Email" +"Email Address","Email Address" +"Enable","Enable" +"Enabled","Enabled" +"Entry with ID #%s not found.","Entry with ID #%s not found." +"Expiration Period","Expiration Period" +"Give the verifier code to application administrator","Give the verifier code to application administrator" +"ID","ID" +"Invalid Callback URL","Invalid Callback URL" +"Invalid ID parameter.","Invalid ID parameter." +"Invalid Rejected Callback URL","Invalid Rejected Callback URL" +"Invalid entry ID.","Invalid entry ID." +"Invalid revoke status.","Invalid revoke status." +"Key","Key" +"Log in as a customer","Log in as a customer" +"Log in as admin","Log in as admin" +"Log in as customer","Log in as customer" +"Log in to use %s","Log in to use %s" +"Login","Login" +"My Applications","My Applications" +"Name","Name" +"New Consumer","New Consumer" +"OAuth","OAuth" +"OAuth Admin My Apps","OAuth Admin My Apps" +"OAuth Authorized Tokens","OAuth Authorized Tokens" +"OAuth Consumers","OAuth Consumers" +"OAuth authorization for admin","OAuth authorization for admin" +"OAuth authorization for customer","OAuth authorization for customer" +"OAuth authorization simple for admin","OAuth authorization simple for admin" +"Password","Password" +"Permissions","Permissions" +"Please select needed row(s).","Please select needed row(s)." +"Please select revoke status.","Please select revoke status." +"REST - My Apps","REST - My Apps" +"REST - OAuth Authorized Tokens","REST - OAuth Authorized Tokens" +"REST - OAuth Consumers","REST - OAuth Consumers" +"Reject","Reject" +"Reject OAuth token authorization","Reject OAuth token authorization" +"Reject authorization","Reject authorization" +"Reject token authorization Pop Up for admin","Reject token authorization Pop Up for admin" +"Reject token authorization for admin","Reject token authorization for admin" +"Rejected Callback URL","Rejected Callback URL" +"Revoked","Revoked" +"Role Name","Role Name" +"Save","Save" +"Save and Continue Edit","Save and Continue Edit" +"Secret","Secret" +"Selected entries enabled.","Selected entries enabled." +"Selected entries has been deleted.","Selected entries has been deleted." +"Selected entries revoked.","Selected entries revoked." +"Status","Status" +"System","System" +"The application access request is rejected.","The application access request is rejected." +"The consumer has been deleted.","The consumer has been deleted." +"The consumer has been saved.","The consumer has been saved." +"Token Status Change","Token Status Change" +"Token Status Change Email Template","Token Status Change Email Template" +"Unable to find a consumer.","Unable to find a consumer." +"User ID","User ID" +"User Name","User Name" +"User Type","User Type" +"Verifier code: %s","Verifier code: %s" +"You have no applications.","You have no applications." +"deleted","deleted" +"enabled","enabled" +"revoked","revoked" diff --git a/app/locale/en_US/Mage_Page.csv b/app/locale/en_US/Mage_Page.csv index 4c01f55ed9..23b46c72e7 100644 --- a/app/locale/en_US/Mage_Page.csv +++ b/app/locale/en_US/Mage_Page.csv @@ -63,7 +63,7 @@ "Select date","Select date" "Show","Show" "This is a demo store. Any orders placed through this store will not be honored or fulfilled.","This is a demo store. Any orders placed through this store will not be honored or fulfilled." -"This website requires cookies provide all of its features. For more information on what data is contained in the cookies, please see our privacy policy page To accept cookies from this site, please click accept below.","This website requires cookies provide all of its features. For more information on what data is contained in the cookies, please see our privacy policy page To accept cookies from this site, please click accept below." +"This website requires cookies to provide all of its features. For more information on what data is contained in the cookies, please see our Privacy Policy page. To accept cookies from this site, please click the Allow button below.","This website requires cookies to provide all of its features. For more information on what data is contained in the cookies, please see our Privacy Policy page. To accept cookies from this site, please click the Allow button below." "Time selection:","Time selection:" "Time:","Time:" "Welcome, %s!","Welcome, %s!" diff --git a/app/locale/en_US/Mage_Payment.csv b/app/locale/en_US/Mage_Payment.csv index 00cd16d0ea..0a6750e95e 100644 --- a/app/locale/en_US/Mage_Payment.csv +++ b/app/locale/en_US/Mage_Payment.csv @@ -10,19 +10,20 @@ "Auto Bill on Next Cycle","Auto Bill on Next Cycle" "Automatically Invoice All Items","Automatically Invoice All Items" "Automatically bill the outstanding balance amount in the next billing cycle (if there were failed payments).","Automatically bill the outstanding balance amount in the next billing cycle (if there were failed payments)." +"Bank Transfer Payment","Bank Transfer Payment" "Billing Agreement status is not set.","Billing Agreement status is not set." "Billing Amount","Billing Amount" "Billing Frequency","Billing Frequency" "Billing Period","Billing Period" "Billing Period Unit","Billing Period Unit" "Billing period unit is not defined or wrong.","Billing period unit is not defined or wrong." -"Cannot retrieve payment method instance.","Cannot retrieve payment method instance." "Cannot retrieve the payment info model object.","Cannot retrieve the payment info model object." "Cannot retrieve the payment information object instance.","Cannot retrieve the payment information object instance." "Cannot retrieve the payment method code.","Cannot retrieve the payment method code." "Cannot retrieve the payment method model object.","Cannot retrieve the payment method model object." "Capture action is not available.","Capture action is not available." "Card Verification Number","Card Verification Number" +"Cash On Delivery Payment","Cash On Delivery Payment" "Centinel API URL","Centinel API URL" "Check / Money Order","Check / Money Order" "Credit Card Number","Credit Card Number" @@ -41,6 +42,7 @@ "Incorrect credit card expiration date.","Incorrect credit card expiration date." "Initial Fee","Initial Fee" "Initial non-recurring payment amount due immediately upon profile creation.","Initial non-recurring payment amount due immediately upon profile creation." +"Instructions","Instructions" "Internal Reference ID","Internal Reference ID" "Invalid Credit Card Number","Invalid Credit Card Number" "Issue Number","Issue Number" @@ -111,6 +113,7 @@ "The number of billing cycles for payment period.","The number of billing cycles for payment period." "The number of scheduled payments that can fail before the profile is automatically suspended.","The number of scheduled payments that can fail before the profile is automatically suspended." "The payment review action is unavailable.","The payment review action is unavailable." +"The requested Payment Method is not available.","The requested Payment Method is not available." "Title","Title" "Trial Billing Amount","Trial Billing Amount" "Trial Billing Frequency","Trial Billing Frequency" diff --git a/app/locale/en_US/Mage_Sales.csv b/app/locale/en_US/Mage_Sales.csv index c4b16999ba..472fe0d943 100644 --- a/app/locale/en_US/Mage_Sales.csv +++ b/app/locale/en_US/Mage_Sales.csv @@ -201,7 +201,7 @@ "Date Shipped","Date Shipped" "Default Status","Default Status" "Default Template","Default Template" -"Default logo, will be used in PDF and HTML documents.
    (jpeg, tiff, png)","Default logo, will be used in PDF and HTML documents.
    (jpeg, tiff, png)" +"Default logo, will be used in PDF and HTML documents.
    (jpeg, tiff, png) If you see image distortion in PDF, try to use larger image","Default logo, will be used in PDF and HTML documents.
    (jpeg, tiff, png) If you see image distortion in PDF, try to use larger image" "Delete","Delete" "Delete Package","Delete Package" "Denied the payment online.","Denied the payment online." @@ -611,9 +611,7 @@ "Revenue","Revenue" "Row Subtotal","Row Subtotal" "Row Total","Row Total" -"SHIP TO:","SHIP TO:" "SKU","SKU" -"SOLD TO:","SOLD TO:" "Sales","Sales" "Sales Discount","Sales Discount" "Sales Emails","Sales Emails" @@ -655,10 +653,12 @@ "Send Shipment Email Copy Method","Send Shipment Email Copy Method" "Send Shipment Email Copy To","Send Shipment Email Copy To" "Send Tracking Information","Send Tracking Information" +"Send shipment info","Send shipment info" "Set order for existing transactions not allowed","Set order for existing transactions not allowed" "Ship","Ship" "Ship To","Ship To" "Ship to Name","Ship to Name" +"Ship to:","Ship to:" "Shipment","Shipment" "Shipment #","Shipment #" "Shipment #%1$s | %3$s (%2$s)","Shipment #%1$s | %3$s (%2$s)" @@ -692,6 +692,7 @@ "Signed Up From","Signed Up From" "Size","Size" "Sku","Sku" +"Sold to:","Sold to:" "Some of the products below do not have all the required options. Please edit them and configure all the required options.","Some of the products below do not have all the required options. Please edit them and configure all the required options." "Sorry, no quotes are available for this order at this time.","Sorry, no quotes are available for this order at this time." "Source object is not specified.","Source object is not specified." diff --git a/app/locale/en_US/Mage_Tax.csv b/app/locale/en_US/Mage_Tax.csv index a7d4ea42c8..1d78ffbcef 100644 --- a/app/locale/en_US/Mage_Tax.csv +++ b/app/locale/en_US/Mage_Tax.csv @@ -8,7 +8,6 @@ "An error occurred while deleting this rate. Incorrect rate ID.","An error occurred while deleting this rate. Incorrect rate ID." "An error occurred while deleting this tax class.","An error occurred while deleting this tax class." "An error occurred while deleting this tax rule.","An error occurred while deleting this tax rule." -"An error occurred while saving this rate.","An error occurred while saving this rate." "An error occurred while saving this tax class.","An error occurred while saving this tax class." "An error occurred while saving this tax class. A class with the same name","An error occurred while saving this tax class. A class with the same name" "An error occurred while saving this tax rule.","An error occurred while saving this tax rule." @@ -25,7 +24,6 @@ "Class Name","Class Name" "Code","Code" "Country","Country" -"County","County" "Custom price if available","Custom price if available" "Customer Tax Class","Customer Tax Class" "Customer Tax Class Information","Customer Tax Class Information" diff --git a/app/locale/en_US/Mage_Usa.csv b/app/locale/en_US/Mage_Usa.csv index ea2542a98a..c188f917e3 100644 --- a/app/locale/en_US/Mage_Usa.csv +++ b/app/locale/en_US/Mage_Usa.csv @@ -116,6 +116,7 @@ "Height","Height" "Height, width and length should be equal or greater than %s","Height, width and length should be equal or greater than %s" "Home Delivery","Home Delivery" +"Hub ID","Hub ID" "Inches","Inches" "Indirect","Indirect" "International Economy","International Economy" @@ -180,6 +181,7 @@ "Piece Id barcode is missing","Piece Id barcode is missing" "Piece number information is missing","Piece number information is missing" "Please make sure to use only digits here. No dashes are allowed.","Please make sure to use only digits here. No dashes are allowed." +"Please, specify origin country","Please, specify origin country" "Pounds","Pounds" "Priority Mail","Priority Mail" "Priority Overnight","Priority Overnight" @@ -232,6 +234,7 @@ "Station","Station" "Subtotal","Subtotal" "Subtotal With Discount","Subtotal With Discount" +"The field is applicable if the Smart Post method is selected.","The field is applicable if the Smart Post method is selected." "The response is in wrong format.","The response is in wrong format." "There is no available method for selected shipping address.","There is no available method for selected shipping address." "There is no items in this order","There is no items in this order" diff --git a/app/locale/en_US/Mage_XmlConnect.csv b/app/locale/en_US/Mage_XmlConnect.csv index 0ec93c8cc6..df89016e7d 100644 --- a/app/locale/en_US/Mage_XmlConnect.csv +++ b/app/locale/en_US/Mage_XmlConnect.csv @@ -536,7 +536,7 @@ "Store logo that is displayed on copyright page of app. Preferred size: 100px x 100px.","Store logo that is displayed on copyright page of app. Preferred size: 100px x 100px." "Store logo that is displayed on copyright page of app. Preferred size: 200px x 200px.","Store logo that is displayed on copyright page of app. Preferred size: 200px x 200px." "Street Address","Street Address" -"Street Address 2","Street Address 2" +"Street Address %s","Street Address %s" "Submission","Submission" "Submission Fields","Submission Fields" "Submission History","Submission History" diff --git a/app/locale/en_US/template/email/token.html b/app/locale/en_US/template/email/token.html new file mode 100644 index 0000000000..a615f4289a --- /dev/null +++ b/app/locale/en_US/template/email/token.html @@ -0,0 +1,17 @@ +{* This is a comment block + +Available vars in this template: + - $userName User name + - $applicationName Application name + - $status Token new status + +Use vars: {{var var_name}} +*} + + +Hello, {{htmlescape var=$userName}}

    +Your authorization to {{htmlescape var=$applicationName}} has been changed to {{htmlescape var=$status}} by Admin team.

    diff --git a/cron.php b/cron.php index 93fbe465c6..d555fca11a 100644 --- a/cron.php +++ b/cron.php @@ -38,6 +38,8 @@ Mage::app('admin')->setUseSessionInUrl(false); +umask(0); + try { Mage::getConfig()->init()->loadEventObservers('crontab'); Mage::app()->addEventArea('crontab'); diff --git a/downloader/Maged/Controller.php b/downloader/Maged/Controller.php index 50dfe1979a..233051d660 100755 --- a/downloader/Maged/Controller.php +++ b/downloader/Maged/Controller.php @@ -596,7 +596,7 @@ public function model($model = null, $singleton = false) if (is_null($model)) { $class = 'Maged_Model'; } else { - $class = 'Maged_Model_'.str_replace(' ', '_', ucwords(str_replace('_', ' ', $model))); + $class = 'Maged_Model_' . str_replace(' ', '_', ucwords(str_replace('_', ' ', $model))); if (!class_exists($class, false)) { include_once str_replace('_', DIRECTORY_SEPARATOR, $class).'.php'; } @@ -885,7 +885,20 @@ public function startInstall() } if (!empty($_GET['archive_type'])) { - $isSuccess = $this->_createBackup($_GET['archive_type'], $_GET['backup_name']); + + $backupName = $_GET['backup_name']; + $connect = $this->model('connect', true)->connect(); + $isSuccess = true; + + if (!preg_match('/^[a-zA-Z0-9\ ]{0,50}$/', $backupName)) { + $connect->runHtmlConsole('Please use only letters (a-z or A-Z), numbers (0-9) or space in ' + . 'Backup Name field. Other characters are not allowed.'); + $isSuccess = false; + } + + if ($isSuccess) { + $isSuccess = $this->_createBackup($_GET['archive_type'], $_GET['backup_name']); + } if (!$isSuccess) { $this->endInstall(); @@ -981,7 +994,7 @@ public static function getVersionInfo() 'minor' => '7', 'revision' => '0', 'patch' => '0', - 'stability' => 'beta', + 'stability' => 'rc', 'number' => '1', ); } @@ -996,7 +1009,7 @@ public static function getVersionInfo() protected function _createBackup($archiveType, $archiveName){ /** @var $connect Maged_Connect */ $connect = $this->model('connect', true)->connect(); - $connect->runHtmlConsole('Creating data backup...'); + $connect->runHtmlConsole('Creating backup...'); $isSuccess = false; @@ -1066,7 +1079,7 @@ protected function _getCreateBackupSuccessMessageByType($type) { $messagesMap = array( Mage_Backup_Helper_Data::TYPE_SYSTEM_SNAPSHOT => 'System backup has been created', - Mage_Backup_Helper_Data::TYPE_SNAPSHOT_WITHOUT_MEDIA => 'System backup has been created', + Mage_Backup_Helper_Data::TYPE_SNAPSHOT_WITHOUT_MEDIA => 'System (excluding Media) backup has been created', Mage_Backup_Helper_Data::TYPE_MEDIA => 'Database and media backup has been created', Mage_Backup_Helper_Data::TYPE_DB => 'Database backup has been created' ); diff --git a/downloader/lib/Mage/Archive.php b/downloader/lib/Mage/Archive.php index 3e5f5ecfa8..70abdb5be5 100644 --- a/downloader/lib/Mage/Archive.php +++ b/downloader/lib/Mage/Archive.php @@ -84,7 +84,7 @@ protected function _getArchiver($extension) } else { $format = self::DEFAULT_ARCHIVER; } - $class = 'Mage_Archive_'.ucfirst($format); + $class = 'Mage_Archive_' . ucfirst($format); $this->_archiver = new $class(); return $this->_archiver; } diff --git a/downloader/lib/Mage/Backup.php b/downloader/lib/Mage/Backup.php index 88f2f48b43..5dc3c19283 100755 --- a/downloader/lib/Mage/Backup.php +++ b/downloader/lib/Mage/Backup.php @@ -48,7 +48,7 @@ class Mage_Backup */ static public function getBackupInstance($type) { - $class = 'Mage_Backup_' . $type; + $class = 'Mage_Backup_' . ucfirst($type); if (!in_array($type, self::$_allowedBackupTypes) || !class_exists($class, true)){ throw new Mage_Exception('Current implementation not supported this type (' . $type . ') of backup.'); diff --git a/downloader/lib/Mage/Backup/Db.php b/downloader/lib/Mage/Backup/Db.php index 7d49849759..5585ef3eaf 100755 --- a/downloader/lib/Mage/Backup/Db.php +++ b/downloader/lib/Mage/Backup/Db.php @@ -34,7 +34,7 @@ class Mage_Backup_Db extends Mage_Backup_Abstract { /** - * Implementation Rollback functionality for Db + * Implements Rollback functionality for Db * * @return bool */ @@ -48,20 +48,10 @@ public function rollback() $archiveManager = new Mage_Archive(); $source = $archiveManager->unpack($this->getBackupPath(), $this->getBackupsDir()); - $this->getResourceModel()->beginTransaction(); - - $file = fopen($source, "r"); - $query = ''; - while(!feof($file)) { - $line = fgets($file); - $query .= $line; - if ($this->_isLineLastInCommand($line)){ - $this->getResourceModel()->runCommand($query); - $query = ''; - } + $file = new Mage_Backup_Filesystem_Iterator_File($source); + foreach ($file as $statement) { + $this->getResourceModel()->runCommand($statement); } - fclose($file); - $this->getResourceModel()->commitTransaction(); @unlink($source); $this->_lastOperationSucceed = true; @@ -70,7 +60,7 @@ public function rollback() } /** - * Check is line a last in sql command + * Checks whether the line is last in sql command * * @param $line * @return bool @@ -92,9 +82,9 @@ protected function _isLineLastInCommand($line) } /** - * Implementation Create Backup functionality for Db + * Implements Create Backup functionality for Db * - * @return boolean + * @return bool */ public function create() { @@ -124,6 +114,6 @@ public function create() */ public function getType() { - return "db"; + return 'db'; } } diff --git a/downloader/lib/Mage/Backup/Filesystem/Iterator/File.php b/downloader/lib/Mage/Backup/Filesystem/Iterator/File.php new file mode 100644 index 0000000000..cddde3607e --- /dev/null +++ b/downloader/lib/Mage/Backup/Filesystem/Iterator/File.php @@ -0,0 +1,112 @@ + + */ +class Mage_Backup_Filesystem_Iterator_File extends SplFileObject +{ + /** + * The statement that was last read during iteration + * + * @var string + */ + protected $_currentStatement = ''; + + /** + * Return current sql statement + * + * @return string + */ + public function current() + { + return $this->_currentStatement; + } + + /** + * Iterate to next sql statement in file + */ + public function next() + { + $this->_currentStatement = ''; + while (!$this->eof()) { + $line = $this->fgets(); + if (strlen(trim($line))) { + $this->_currentStatement .= $line; + if ($this->_isLineLastInCommand($line)) { + break; + } + } + } + } + + /** + * Return to first statement + */ + public function rewind() + { + parent::rewind(); + $this->next(); + } + + /** + * Check whether provided string is comment + * + * @param string $line + * @return bool + */ + protected function _isComment($line) + { + return $line[0] == '#' || substr($line, 0, 2) == '--'; + } + + /** + * Check is line a last in sql command + * + * @param string $line + * @return bool + */ + protected function _isLineLastInCommand($line) + { + $cleanLine = trim($line); + $lineLength = strlen($cleanLine); + + $returnResult = false; + if ($lineLength > 0) { + $lastSymbolIndex = $lineLength - 1; + if ($cleanLine[$lastSymbolIndex] == ';') { + $returnResult = true; + } + } + + return $returnResult; + } +} diff --git a/downloader/lib/Mage/Backup/Filesystem/Iterator/Filter.php b/downloader/lib/Mage/Backup/Filesystem/Iterator/Filter.php index 40ae6f053b..4b1b413db9 100755 --- a/downloader/lib/Mage/Backup/Filesystem/Iterator/Filter.php +++ b/downloader/lib/Mage/Backup/Filesystem/Iterator/Filter.php @@ -44,7 +44,7 @@ class Mage_Backup_Filesystem_Iterator_Filter extends FilterIterator * Constructor * * @param Iterator $iterator - * @param array $skipFiles + * @param array $filters list of files to skip */ public function __construct(Iterator $iterator, array $filters) { diff --git a/downloader/template/connect/packages.phtml b/downloader/template/connect/packages.phtml index a0a3d812d9..92e6ea8ba5 100644 --- a/downloader/template/connect/packages.phtml +++ b/downloader/template/connect/packages.phtml @@ -32,7 +32,7 @@
    •   - +
    '.$product->getDescription(); + $description = '' + . ' - + - - - - - - helper('tax')->displayCartPriceInclTax() || $this->helper('tax')->displayCartBothPrices()): ?> - - - + + - helper('tax')->displayCartPriceExclTax() || $this->helper('tax')->displayCartBothPrices()) && !$_item->getNoSubtotal()): ?> - - - helper('tax')->displayCartPriceInclTax() || $this->helper('tax')->displayCartBothPrices()) && !$_item->getNoSubtotal()): ?> - - diff --git a/app/design/frontend/default/iphone/template/checkout/cart/noItemsHeader.phtml b/app/design/frontend/default/iphone/template/checkout/cart/noItemsHeader.phtml index 5eb698c227..aca0be04d2 100644 --- a/app/design/frontend/default/iphone/template/checkout/cart/noItemsHeader.phtml +++ b/app/design/frontend/default/iphone/template/checkout/cart/noItemsHeader.phtml @@ -29,7 +29,6 @@
    getMessagesBlock()->getGroupedHtml() ?>

    __('You have no items in your shopping cart.') ?>

    -

    __('Click here to continue shopping.', $this->getContinueShoppingUrl()) ?>

    diff --git a/app/design/frontend/default/iphone/template/checkout/cartheader.phtml b/app/design/frontend/default/iphone/template/checkout/cartheader.phtml index 683bb85a4a..61af0e3b00 100644 --- a/app/design/frontend/default/iphone/template/checkout/cartheader.phtml +++ b/app/design/frontend/default/iphone/template/checkout/cartheader.phtml @@ -55,17 +55,6 @@ - helper('tax')->displayCartBothPrices() ? 2 : 1); ?> - - helper('tax')->displayCartBothPrices()): ?> - - - - - - - - getItems() as $_item): ?> getItemHtml($_item) ?> @@ -74,7 +63,15 @@
    ' + . ''.$product->getDescription(); if ($allowedPriceInRss) { $description .= $this->getPriceHtml($product,true); @@ -111,10 +113,10 @@ public function addTaggedItemXml($args) $rssObj = $args['rssObj']; $data = array( - 'title' => $product->getName(), - 'link' => $product->getProductUrl(), - 'description' => $description, - ); + 'title' => $product->getName(), + 'link' => $product->getProductUrl(), + 'description' => $description, + ); $rssObj->_addEntry($data); } } diff --git a/app/code/core/Mage/Rule/Model/Resource/Abstract.php b/app/code/core/Mage/Rule/Model/Resource/Abstract.php index e0a0ffdced..6899edc40f 100644 --- a/app/code/core/Mage/Rule/Model/Resource/Abstract.php +++ b/app/code/core/Mage/Rule/Model/Resource/Abstract.php @@ -64,15 +64,17 @@ abstract class Mage_Rule_Model_Resource_Abstract extends Mage_Core_Model_Resourc */ public function _beforeSave(Mage_Core_Model_Abstract $object) { - if ($object->getFromDate() instanceof Zend_Date) { - $object->setFromDate($object->getFromDate()->toString(Varien_Date::DATETIME_INTERNAL_FORMAT)); - } else { + $fromDate = $object->getFromDate(); + if ($fromDate instanceof Zend_Date) { + $object->setFromDate($fromDate->toString(Varien_Date::DATETIME_INTERNAL_FORMAT)); + } elseif (!is_string($fromDate) || empty($fromDate)) { $object->setFromDate(null); } - if ($object->getToDate() instanceof Zend_Date) { - $object->setToDate($object->getToDate()->toString(Varien_Date::DATETIME_INTERNAL_FORMAT)); - } else { + $toDate = $object->getToDate(); + if ($toDate instanceof Zend_Date) { + $object->setToDate($toDate->toString(Varien_Date::DATETIME_INTERNAL_FORMAT)); + } elseif (!is_string($toDate) || empty($toDate)) { $object->setToDate(null); } diff --git a/app/code/core/Mage/Sales/Model/Api2/Order.php b/app/code/core/Mage/Sales/Model/Api2/Order.php new file mode 100644 index 0000000000..6fe913a042 --- /dev/null +++ b/app/code/core/Mage/Sales/Model/Api2/Order.php @@ -0,0 +1,318 @@ + + */ +class Mage_Sales_Model_Api2_Order extends Mage_Api2_Model_Resource +{ + /**#@+ + * Parameters' names in config with special ACL meaning + */ + const PARAM_GIFT_MESSAGE = '_gift_message'; + const PARAM_ORDER_COMMENTS = '_order_comments'; + const PARAM_PAYMENT_METHOD = '_payment_method'; + const PARAM_TAX_NAME = '_tax_name'; + const PARAM_TAX_RATE = '_tax_rate'; + /**#@-*/ + + /** + * Add gift message info to select + * + * @param Mage_Sales_Model_Resource_Order_Collection $collection + * @return Mage_Sales_Model_Api2_Order + */ + protected function _addGiftMessageInfo(Mage_Sales_Model_Resource_Order_Collection $collection) + { + $collection->getSelect()->joinLeft( + array('gift_message' => $collection->getTable('giftmessage/message')), + 'main_table.gift_message_id = gift_message.gift_message_id', + array( + 'gift_message_from' => 'gift_message.sender', + 'gift_message_to' => 'gift_message.recipient', + 'gift_message_body' => 'gift_message.message' + ) + ); + + return $this; + } + + /** + * Add order payment method field to select + * + * @param Mage_Sales_Model_Resource_Order_Collection $collection + * @return Mage_Sales_Model_Api2_Order + */ + protected function _addPaymentMethodInfo(Mage_Sales_Model_Resource_Order_Collection $collection) + { + $collection->getSelect()->joinLeft( + array('payment_method' => $collection->getTable('sales/order_payment')), + 'main_table.entity_id = payment_method.parent_id', + array('payment_method' => 'payment_method.method') + ); + + return $this; + } + + /** + * Add order tax information to select + * + * @param Mage_Sales_Model_Resource_Order_Collection $collection + * @return Mage_Sales_Model_Api2_Order + */ + protected function _addTaxInfo(Mage_Sales_Model_Resource_Order_Collection $collection) + { + $taxInfoFields = array(); + + if ($this->_isTaxNameAllowed()) { + $taxInfoFields['tax_name'] = 'order_tax.title'; + } + if ($this->_isTaxRateAllowed()) { + $taxInfoFields['tax_rate'] = 'order_tax.percent'; + } + if ($taxInfoFields) { + $collection->getSelect()->joinLeft( + array('order_tax' => $collection->getTable('sales/order_tax')), + 'main_table.entity_id = order_tax.order_id', + $taxInfoFields + ); + } + return $this; + } + + /** + * Retrieve a list or orders' addresses in a form of [order ID => array of addresses, ...] + * + * @param array $orderIds Orders identifiers + * @return array + */ + protected function _getAddresses(array $orderIds) + { + $addresses = array(); + + if ($this->_isSubCallAllowed('order_addresses')) { + /** @var $addressesFilter Mage_Api2_Model_Acl_Filter */ + $addressesFilter = $this->_getSubModel('order_addresses', array())->getFilter(); + // do addresses request if at least one attribute allowed + if ($addressesFilter->getAllowedAttributes()) { + /* @var $collection Mage_Sales_Model_Resource_Order_Address_Collection */ + $collection = Mage::getResourceModel('sales/order_address_collection'); + + $collection->addAttributeToFilter('parent_id', $orderIds); + + foreach ($collection->getItems() as $item) { + $addresses[$item->getParentId()][] = $addressesFilter->out($item->toArray()); + } + } + } + return $addresses; + } + + /** + * Retrieve collection instance for orders list + * + * @return Mage_Sales_Model_Resource_Order_Collection + */ + protected function _getCollectionForRetrieve() + { + /** @var $collection Mage_Sales_Model_Resource_Order_Collection */ + $collection = Mage::getResourceModel('sales/order_collection'); + + $this->_applyCollectionModifiers($collection); + + return $collection; + } + + /** + * Retrieve collection instance for single order + * + * @param int $orderId Order identifier + * @return Mage_Sales_Model_Resource_Order_Collection + */ + protected function _getCollectionForSingleRetrieve($orderId) + { + /** @var $collection Mage_Sales_Model_Resource_Order_Collection */ + $collection = Mage::getResourceModel('sales/order_collection'); + + return $collection->addFieldToFilter($collection->getResource()->getIdFieldName(), $orderId); + } + + /** + * Retrieve a list or orders' comments in a form of [order ID => array of comments, ...] + * + * @param array $orderIds Orders' identifiers + * @return array + */ + protected function _getComments(array $orderIds) + { + $comments = array(); + + if ($this->_isOrderCommentsAllowed() && $this->_isSubCallAllowed('order_comments')) { + /** @var $commentsFilter Mage_Api2_Model_Acl_Filter */ + $commentsFilter = $this->_getSubModel('order_comments', array())->getFilter(); + // do comments request if at least one attribute allowed + if ($commentsFilter->getAllowedAttributes()) { + foreach ($this->_getCommentsCollection($orderIds)->getItems() as $item) { + $comments[$item->getParentId()][] = $commentsFilter->out($item->toArray()); + } + } + } + return $comments; + } + + /** + * Prepare and return order comments collection + * + * @param array $orderIds Orders' identifiers + * @return Mage_Sales_Model_Resource_Order_Status_History_Collection|Object + */ + protected function _getCommentsCollection(array $orderIds) + { + /* @var $collection Mage_Sales_Model_Resource_Order_Status_History_Collection */ + $collection = Mage::getResourceModel('sales/order_status_history_collection'); + $collection->setOrderFilter($orderIds); + + return $collection; + } + + /** + * Retrieve a list or orders' items in a form of [order ID => array of items, ...] + * + * @param array $orderIds Orders identifiers + * @return array + */ + protected function _getItems(array $orderIds) + { + $items = array(); + + if ($this->_isSubCallAllowed('order_items')) { + /** @var $itemsFilter Mage_Api2_Model_Acl_Filter */ + $itemsFilter = $this->_getSubModel('order_items', array())->getFilter(); + // do items request if at least one attribute allowed + if ($itemsFilter->getAllowedAttributes()) { + /* @var $collection Mage_Sales_Model_Resource_Order_Item_Collection */ + $collection = Mage::getResourceModel('sales/order_item_collection'); + + $collection->addAttributeToFilter('order_id', $orderIds); + + foreach ($collection->getItems() as $item) { + $items[$item->getOrderId()][] = $itemsFilter->out($item->toArray()); + } + } + } + return $items; + } + + /** + * Check gift messages information is allowed + * + * @return bool + */ + public function _isGiftMessageAllowed() + { + return in_array(self::PARAM_GIFT_MESSAGE, $this->getFilter()->getAllowedAttributes()); + } + + /** + * Check order comments information is allowed + * + * @return bool + */ + public function _isOrderCommentsAllowed() + { + return in_array(self::PARAM_ORDER_COMMENTS, $this->getFilter()->getAllowedAttributes()); + } + + /** + * Check payment method information is allowed + * + * @return bool + */ + public function _isPaymentMethodAllowed() + { + return in_array(self::PARAM_PAYMENT_METHOD, $this->getFilter()->getAllowedAttributes()); + } + + /** + * Check tax name information is allowed + * + * @return bool + */ + public function _isTaxNameAllowed() + { + return in_array(self::PARAM_TAX_NAME, $this->getFilter()->getAllowedAttributes()); + } + + /** + * Check tax rate information is allowed + * + * @return bool + */ + public function _isTaxRateAllowed() + { + return in_array(self::PARAM_TAX_RATE, $this->getFilter()->getAllowedAttributes()); + } + + /** + * Get orders list + * + * @return array + */ + protected function _retrieveCollection() + { + $collection = $this->_getCollectionForRetrieve(); + + if ($this->_isPaymentMethodAllowed()) { + $this->_addPaymentMethodInfo($collection); + } + if ($this->_isGiftMessageAllowed()) { + $this->_addGiftMessageInfo($collection); + } + $this->_addTaxInfo($collection); + + $ordersData = array(); + + foreach ($collection->getItems() as $order) { + $ordersData[$order->getId()] = $order->toArray(); + } + if ($ordersData) { + foreach ($this->_getAddresses(array_keys($ordersData)) as $orderId => $addresses) { + $ordersData[$orderId]['addresses'] = $addresses; + } + foreach ($this->_getItems(array_keys($ordersData)) as $orderId => $items) { + $ordersData[$orderId]['order_items'] = $items; + } + foreach ($this->_getComments(array_keys($ordersData)) as $orderId => $comments) { + $ordersData[$orderId]['order_comments'] = $comments; + } + } + return $ordersData; + } +} diff --git a/app/code/core/Mage/Sales/Model/Api2/Order/Address.php b/app/code/core/Mage/Sales/Model/Api2/Order/Address.php new file mode 100644 index 0000000000..a7f5071157 --- /dev/null +++ b/app/code/core/Mage/Sales/Model/Api2/Order/Address.php @@ -0,0 +1,36 @@ + + */ +class Mage_Sales_Model_Api2_Order_Address extends Mage_Api2_Model_Resource +{ +} diff --git a/app/code/core/Mage/Sales/Model/Api2/Order/Address/Rest.php b/app/code/core/Mage/Sales/Model/Api2/Order/Address/Rest.php new file mode 100644 index 0000000000..c433967f49 --- /dev/null +++ b/app/code/core/Mage/Sales/Model/Api2/Order/Address/Rest.php @@ -0,0 +1,92 @@ + + */ +abstract class Mage_Sales_Model_Api2_Order_Address_Rest extends Mage_Sales_Model_Api2_Order_Address +{ + /**#@+ + * Parameters in request used in model (usually specified in route mask) + */ + const PARAM_ORDER_ID = 'order_id'; + const PARAM_ADDRESS_TYPE = 'address_type'; + /**#@-*/ + + /** + * Retrieve order address + * + * @return array + */ + protected function _retrieve() + { + /** @var $address Mage_Sales_Model_Order_Address */ + $address = $this->_getCollectionForRetrieve() + ->addAttributeToFilter('address_type', $this->getRequest()->getParam(self::PARAM_ADDRESS_TYPE)) + ->getFirstItem(); + if (!$address->getId()) { + $this->_critical(self::RESOURCE_NOT_FOUND); + } + return $address->getData(); + } + + /** + * Retrieve order addresses + * + * @return array + */ + protected function _retrieveCollection() + { + $collection = $this->_getCollectionForRetrieve(); + + $this->_applyCollectionModifiers($collection); + $data = $collection->load()->toArray(); + + if (0 == count($data['items'])) { + $this->_critical(self::RESOURCE_NOT_FOUND); + } + + return $data['items']; + } + + /** + * Retrieve collection instances + * + * @return Mage_Sales_Model_Resource_Order_Address_Collection + */ + protected function _getCollectionForRetrieve() + { + /* @var $collection Mage_Sales_Model_Resource_Order_Address_Collection */ + $collection = Mage::getResourceModel('sales/order_address_collection'); + $collection->addAttributeToFilter('parent_id', $this->getRequest()->getParam(self::PARAM_ORDER_ID)); + + return $collection; + } +} diff --git a/app/code/core/Mage/Sales/Model/Api2/Order/Address/Rest/Admin/V1.php b/app/code/core/Mage/Sales/Model/Api2/Order/Address/Rest/Admin/V1.php new file mode 100644 index 0000000000..490087ca96 --- /dev/null +++ b/app/code/core/Mage/Sales/Model/Api2/Order/Address/Rest/Admin/V1.php @@ -0,0 +1,36 @@ + + */ +class Mage_Sales_Model_Api2_Order_Address_Rest_Admin_V1 extends Mage_Sales_Model_Api2_Order_Address_Rest +{ +} diff --git a/app/code/core/Mage/Sales/Model/Api2/Order/Address/Rest/Customer/V1.php b/app/code/core/Mage/Sales/Model/Api2/Order/Address/Rest/Customer/V1.php new file mode 100644 index 0000000000..ab19a11625 --- /dev/null +++ b/app/code/core/Mage/Sales/Model/Api2/Order/Address/Rest/Customer/V1.php @@ -0,0 +1,48 @@ + + */ +class Mage_Sales_Model_Api2_Order_Address_Rest_Customer_V1 extends Mage_Sales_Model_Api2_Order_Address_Rest +{ + /** + * Retrieve collection instances + * + * @return Mage_Sales_Model_Resource_Order_Address_Collection + */ + protected function _getCollectionForRetrieve() + { + $collection = parent::_getCollectionForRetrieve(); + $collection->addAttributeToFilter('customer_id', $this->getApiUser()->getUserId()); + + return $collection; + } +} diff --git a/app/code/core/Mage/Sales/Model/Api2/Order/Comments.php b/app/code/core/Mage/Sales/Model/Api2/Order/Comments.php new file mode 100644 index 0000000000..b7b1ddfd59 --- /dev/null +++ b/app/code/core/Mage/Sales/Model/Api2/Order/Comments.php @@ -0,0 +1,36 @@ + + */ +class Mage_Sales_Model_Api2_Order_Comments extends Mage_Api2_Model_Resource +{ +} diff --git a/app/code/core/Mage/Sales/Model/Api2/Order/Comments/Rest.php b/app/code/core/Mage/Sales/Model/Api2/Order/Comments/Rest.php new file mode 100644 index 0000000000..bfdf308cf4 --- /dev/null +++ b/app/code/core/Mage/Sales/Model/Api2/Order/Comments/Rest.php @@ -0,0 +1,89 @@ + + */ +abstract class Mage_Sales_Model_Api2_Order_Comments_Rest extends Mage_Sales_Model_Api2_Order_Comments +{ + /**#@+ + * Parameters in request used in model (usually specified in route mask) + */ + const PARAM_ORDER_ID = 'id'; + /**#@-*/ + + /** + * Get sales order comments + * + * @return array + */ + protected function _retrieveCollection() + { + $collection = $this->_getCollectionForRetrieve(); + $collection->addFieldToSelect($this->getForcedAttributes()); + + $this->_applyCollectionModifiers($collection); + + $data = $collection->load()->toArray(); + return isset($data['items']) ? $data['items'] : $data; + } + + /** + * Retrieve collection instances + * + * @return Mage_Sales_Model_Resource_Order_Status_History_Collection + */ + protected function _getCollectionForRetrieve() + { + /* @var $collection Mage_Sales_Model_Resource_Order_Status_History_Collection */ + $collection = Mage::getResourceModel('sales/order_status_history_collection'); + $collection->setOrderFilter($this->_loadOrderById($this->getRequest()->getParam(self::PARAM_ORDER_ID))); + + return $collection; + } + + /** + * Load order by id + * + * @param int $id + * @throws Mage_Api2_Exception + * @return Mage_Sales_Model_Order + */ + protected function _loadOrderById($id) + { + /* @var $order Mage_Sales_Model_Order */ + $order = Mage::getModel('sales/order')->load($id); + if (!$order->getId()) { + $this->_critical(self::RESOURCE_NOT_FOUND); + } + + return $order; + } +} diff --git a/app/code/core/Mage/Sales/Model/Api2/Order/Comments/Rest/Admin/V1.php b/app/code/core/Mage/Sales/Model/Api2/Order/Comments/Rest/Admin/V1.php new file mode 100644 index 0000000000..e6e6b45aab --- /dev/null +++ b/app/code/core/Mage/Sales/Model/Api2/Order/Comments/Rest/Admin/V1.php @@ -0,0 +1,36 @@ + + */ +class Mage_Sales_Model_Api2_Order_Comments_Rest_Admin_V1 extends Mage_Sales_Model_Api2_Order_Comments_Rest +{ +} diff --git a/app/code/core/Mage/Sales/Model/Api2/Order/Comments/Rest/Customer/V1.php b/app/code/core/Mage/Sales/Model/Api2/Order/Comments/Rest/Customer/V1.php new file mode 100644 index 0000000000..203d6614ae --- /dev/null +++ b/app/code/core/Mage/Sales/Model/Api2/Order/Comments/Rest/Customer/V1.php @@ -0,0 +1,66 @@ + + */ +class Mage_Sales_Model_Api2_Order_Comments_Rest_Customer_V1 extends Mage_Sales_Model_Api2_Order_Comments_Rest +{ + /** + * Load order by id + * + * @param int $id + * @throws Mage_Api2_Exception + * @return Mage_Sales_Model_Order + */ + protected function _loadOrderById($id) + { + $order = parent::_loadOrderById($id); + + // Check sales order's owner + if ($this->getApiUser()->getUserId() !== $order->getCustomerId()) { + $this->_critical(self::RESOURCE_NOT_FOUND); + } + return $order; + } + + /** + * Retrieve collection instances + * + * @return Mage_Sales_Model_Resource_Order_Status_History_Collection + */ + protected function _getCollectionForRetrieve() + { + $collection = parent::_getCollectionForRetrieve(); + $collection->addFieldToFilter('is_visible_on_front', 1); + + return $collection; + } +} diff --git a/app/code/core/Mage/Sales/Model/Api2/Order/Items.php b/app/code/core/Mage/Sales/Model/Api2/Order/Items.php new file mode 100644 index 0000000000..4497003ba3 --- /dev/null +++ b/app/code/core/Mage/Sales/Model/Api2/Order/Items.php @@ -0,0 +1,36 @@ + + */ +class Mage_Sales_Model_Api2_Order_Items extends Mage_Api2_Model_Resource +{ +} diff --git a/app/code/core/Mage/Sales/Model/Api2/Order/Items/Rest.php b/app/code/core/Mage/Sales/Model/Api2/Order/Items/Rest.php new file mode 100644 index 0000000000..24b3a18e04 --- /dev/null +++ b/app/code/core/Mage/Sales/Model/Api2/Order/Items/Rest.php @@ -0,0 +1,93 @@ + + */ +abstract class Mage_Sales_Model_Api2_Order_Items_Rest extends Mage_Sales_Model_Api2_Order_Items +{ + /**#@+ + * Parameters in request used in model (usually specified in route) + */ + const PARAM_ORDER_ID = 'id'; + /**#@-*/ + + /** + * Get order items list + * + * @return array + */ + protected function _retrieveCollection() + { + $data = array(); + /* @var $item Mage_Sales_Model_Order_Item */ + foreach ($this->_getCollectionForRetrieve() as $item) { + $itemData = $item->getData(); + $itemData['status'] = $item->getStatus(); + $data[] = $itemData; + } + return $data; + } + /** + * Retrieve order items collection + * + * @return Mage_Sales_Model_Resource_Order_Item_Collection + */ + protected function _getCollectionForRetrieve() + { + /* @var $order Mage_Sales_Model_Order */ + $order = $this->_loadOrderById( + $this->getRequest()->getParam(self::PARAM_ORDER_ID) + ); + + /* @var $collection Mage_Sales_Model_Resource_Order_Item_Collection */ + $collection = Mage::getResourceModel('sales/order_item_collection'); + $collection->setOrderFilter($order->getId()); + $this->_applyCollectionModifiers($collection); + return $collection; + } + + /** + * Load order by id + * + * @param int $id + * @throws Mage_Api2_Exception + * @return Mage_Sales_Model_Order + */ + protected function _loadOrderById($id) + { + /* @var $order Mage_Sales_Model_Order */ + $order = Mage::getModel('sales/order')->load($id); + if (!$order->getId()) { + $this->_critical(self::RESOURCE_NOT_FOUND); + } + return $order; + } +} diff --git a/app/code/core/Mage/Sales/Model/Api2/Order/Items/Rest/Admin/V1.php b/app/code/core/Mage/Sales/Model/Api2/Order/Items/Rest/Admin/V1.php new file mode 100644 index 0000000000..eb76e98c6b --- /dev/null +++ b/app/code/core/Mage/Sales/Model/Api2/Order/Items/Rest/Admin/V1.php @@ -0,0 +1,36 @@ + + */ +class Mage_Sales_Model_Api2_Order_Items_Rest_Admin_V1 extends Mage_Sales_Model_Api2_Order_Items_Rest +{ +} diff --git a/app/code/core/Mage/Sales/Model/Api2/Order/Items/Rest/Customer/V1.php b/app/code/core/Mage/Sales/Model/Api2/Order/Items/Rest/Customer/V1.php new file mode 100644 index 0000000000..87404efadd --- /dev/null +++ b/app/code/core/Mage/Sales/Model/Api2/Order/Items/Rest/Customer/V1.php @@ -0,0 +1,56 @@ + + */ +class Mage_Sales_Model_Api2_Order_Items_Rest_Customer_V1 extends Mage_Sales_Model_Api2_Order_Items_Rest +{ + /** + * Load order by id + * + * @param int $id + * @throws Mage_Api2_Exception + * @return Mage_Sales_Model_Order + */ + protected function _loadOrderById($id) + { + /* @var $order Mage_Sales_Model_Order */ + $order = Mage::getModel('sales/order')->load($id); + if (!$order->getId()) { + $this->_critical(self::RESOURCE_NOT_FOUND); + } + // check order owner + if ($this->getApiUser()->getUserId() != $order->getCustomerId()) { + $this->_critical(self::RESOURCE_NOT_FOUND); + } + return $order; + } +} diff --git a/app/code/core/Mage/Sales/Model/Api2/Order/Rest.php b/app/code/core/Mage/Sales/Model/Api2/Order/Rest.php new file mode 100644 index 0000000000..9e1cf0915c --- /dev/null +++ b/app/code/core/Mage/Sales/Model/Api2/Order/Rest.php @@ -0,0 +1,76 @@ + + */ +abstract class Mage_Sales_Model_Api2_Order_Rest extends Mage_Sales_Model_Api2_Order +{ + /** + * Retrieve information about specified order item + * + * @throws Mage_Api2_Exception + * @return array + */ + protected function _retrieve() + { + $orderId = $this->getRequest()->getParam('id'); + $collection = $this->_getCollectionForSingleRetrieve($orderId); + + if ($this->_isPaymentMethodAllowed()) { + $this->_addPaymentMethodInfo($collection); + } + if ($this->_isGiftMessageAllowed()) { + $this->_addGiftMessageInfo($collection); + } + $this->_addTaxInfo($collection); + + $order = $collection->getItemById($orderId); + + if (!$order) { + $this->_critical(self::RESOURCE_NOT_FOUND); + } + $orderData = $order->getData(); + $addresses = $this->_getAddresses(array($orderId)); + $items = $this->_getItems(array($orderId)); + $comments = $this->_getComments(array($orderId)); + + if ($addresses) { + $orderData['addresses'] = $addresses[$orderId]; + } + if ($items) { + $orderData['order_items'] = $items[$orderId]; + } + if ($comments) { + $orderData['order_comments'] = $comments[$orderId]; + } + return $orderData; + } +} diff --git a/app/code/core/Mage/Sales/Model/Api2/Order/Rest/Admin/V1.php b/app/code/core/Mage/Sales/Model/Api2/Order/Rest/Admin/V1.php new file mode 100644 index 0000000000..ea6cf24f49 --- /dev/null +++ b/app/code/core/Mage/Sales/Model/Api2/Order/Rest/Admin/V1.php @@ -0,0 +1,36 @@ + + */ +class Mage_Sales_Model_Api2_Order_Rest_Admin_V1 extends Mage_Sales_Model_Api2_Order_Rest +{ +} diff --git a/app/code/core/Mage/Sales/Model/Api2/Order/Rest/Customer/V1.php b/app/code/core/Mage/Sales/Model/Api2/Order/Rest/Customer/V1.php new file mode 100644 index 0000000000..b0ea2aa571 --- /dev/null +++ b/app/code/core/Mage/Sales/Model/Api2/Order/Rest/Customer/V1.php @@ -0,0 +1,71 @@ + + */ +class Mage_Sales_Model_Api2_Order_Rest_Customer_V1 extends Mage_Sales_Model_Api2_Order_Rest +{ + /** + * Retrieve collection instance for orders + * + * @return Mage_Sales_Model_Resource_Order_Collection + */ + protected function _getCollectionForRetrieve() + { + return parent::_getCollectionForRetrieve()->addAttributeToFilter( + 'customer_id', array('eq' => $this->getApiUser()->getUserId()) + ); + } + + /** + * Retrieve collection instance for single order + * + * @param int $orderId Order identifier + * @return Mage_Sales_Model_Resource_Order_Collection + */ + protected function _getCollectionForSingleRetrieve($orderId) + { + return parent::_getCollectionForSingleRetrieve($orderId)->addAttributeToFilter( + 'customer_id', array('eq' => $this->getApiUser()->getUserId()) + ); + } + + /** + * Prepare and return order comments collection + * + * @param array $orderIds Orders' identifiers + * @return Mage_Sales_Model_Resource_Order_Status_History_Collection|Object + */ + protected function _getCommentsCollection(array $orderIds) + { + return parent::_getCommentsCollection($orderIds)->addFieldToFilter('is_visible_on_front', 1); + } +} diff --git a/app/code/core/Mage/Sales/Model/Order/Api.php b/app/code/core/Mage/Sales/Model/Order/Api.php index 20d19fa42f..a4b7313be1 100644 --- a/app/code/core/Mage/Sales/Model/Order/Api.php +++ b/app/code/core/Mage/Sales/Model/Order/Api.php @@ -73,7 +73,7 @@ public function items($filters = null) //TODO: add full name logic $billingAliasName = 'billing_o_a'; $shippingAliasName = 'shipping_o_a'; - + $collection = Mage::getModel("sales/order")->getCollection() ->addAttributeToSelect('*') ->addAddressFields() @@ -84,7 +84,8 @@ public function items($filters = null) 'billing_lastname', "{{billing_lastname}}", array('billing_lastname'=>"$billingAliasName.lastname") ) ->addExpressionFieldToSelect( - 'shipping_firstname', "{{shipping_firstname}}", array('shipping_firstname'=>"$shippingAliasName.firstname") + 'shipping_firstname', "{{shipping_firstname}}", + array('shipping_firstname'=>"$shippingAliasName.firstname") ) ->addExpressionFieldToSelect( 'shipping_lastname', "{{shipping_lastname}}", array('shipping_lastname'=>"$shippingAliasName.lastname") @@ -92,14 +93,20 @@ public function items($filters = null) ->addExpressionFieldToSelect( 'billing_name', "CONCAT({{billing_firstname}}, ' ', {{billing_lastname}})", - array('billing_firstname'=>"$billingAliasName.firstname", 'billing_lastname'=>"$billingAliasName.lastname") + array( + 'billing_firstname'=>"$billingAliasName.firstname", + 'billing_lastname'=>"$billingAliasName.lastname" + ) ) ->addExpressionFieldToSelect( 'shipping_name', 'CONCAT({{shipping_firstname}}, " ", {{shipping_lastname}})', - array('shipping_firstname'=>"$shippingAliasName.firstname", 'shipping_lastname'=>"$shippingAliasName.lastname") + array( + 'shipping_firstname'=>"$shippingAliasName.firstname", + 'shipping_lastname'=>"$shippingAliasName.lastname" + ) ); - + if (is_array($filters)) { try { foreach ($filters as $field => $value) { @@ -254,13 +261,18 @@ public function cancel($orderIncrementId) { $order = $this->_initOrder($orderIncrementId); + if (Mage_Sales_Model_Order::STATE_CANCELED == $order->getState()) { + $this->_fault('status_not_changed'); + } try { $order->cancel(); $order->save(); } catch (Mage_Core_Exception $e) { $this->_fault('status_not_changed', $e->getMessage()); } - + if (Mage_Sales_Model_Order::STATE_CANCELED != $order->getState()) { + $this->_fault('status_not_changed'); + } return true; } 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 edc26b9bf2..b9900b6f6d 100644 --- a/app/code/core/Mage/Sales/Model/Order/Api/V2.php +++ b/app/code/core/Mage/Sales/Model/Order/Api/V2.php @@ -55,7 +55,9 @@ public function items($filters = null) 'billing_lastname', "{{billing_lastname}}", array('billing_lastname'=>"$billingAliasName.lastname") ) ->addExpressionFieldToSelect( - 'shipping_firstname', "{{shipping_firstname}}", array('shipping_firstname'=>"$shippingAliasName.firstname") + 'shipping_firstname', + "{{shipping_firstname}}", + array('shipping_firstname'=>"$shippingAliasName.firstname") ) ->addExpressionFieldToSelect( 'shipping_lastname', "{{shipping_lastname}}", array('shipping_lastname'=>"$shippingAliasName.lastname") @@ -63,30 +65,40 @@ public function items($filters = null) ->addExpressionFieldToSelect( 'billing_name', "CONCAT({{billing_firstname}}, ' ', {{billing_lastname}})", - array('billing_firstname'=>"$billingAliasName.firstname", 'billing_lastname'=>"$billingAliasName.lastname") + array( + 'billing_firstname'=>"$billingAliasName.firstname", + 'billing_lastname'=>"$billingAliasName.lastname" + ) ) ->addExpressionFieldToSelect( 'shipping_name', 'CONCAT({{shipping_firstname}}, " ", {{shipping_lastname}})', - array('shipping_firstname'=>"$shippingAliasName.firstname", 'shipping_lastname'=>"$shippingAliasName.lastname") + array( + 'shipping_firstname'=>"$shippingAliasName.firstname", + 'shipping_lastname'=>"$shippingAliasName.lastname" + ) ); $preparedFilters = array(); if (isset($filters->filter)) { - foreach ($filters->filter as $_filter) { - $preparedFilters[][$_filter->key] = $_filter->value; + foreach ($filters->filter as $_filterKey => $_filterValue) { + if (is_object($_filterValue)) { + $preparedFilters[][$_filterValue->key] = $_filterValue->value; + } else { + $preparedFilters[][$_filterKey] = $_filterValue; + } } } if (isset($filters->complex_filter)) { - foreach ($filters->complex_filter as $_filter) { + foreach ($filters->complex_filter as $_key => $_filter) { $_value = $_filter->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'] + $preparedFilters[][$_key] = array( + $_filter->key => $_value ); } else { $preparedFilters[][$_filter->key] = $_value; diff --git a/app/code/core/Mage/Sales/Model/Order/Creditmemo/Api.php b/app/code/core/Mage/Sales/Model/Order/Creditmemo/Api.php index 5a3a3aef12..b33c87f502 100644 --- a/app/code/core/Mage/Sales/Model/Order/Creditmemo/Api.php +++ b/app/code/core/Mage/Sales/Model/Order/Creditmemo/Api.php @@ -56,9 +56,9 @@ public function __construct() * @param array|null $filter * @return array */ - public function items($filter = null) + public function items($filters = null) { - $filter = $this->_prepareListFilter($filter); + $filter = $this->_prepareListFilter($filters); try { $result = array(); /** @var $creditmemoModel Mage_Sales_Model_Order_Creditmemo */ @@ -115,14 +115,15 @@ public function info($creditmemoIncrementId) foreach ($creditmemo->getCommentsCollection() as $comment) { $result['comments'][] = $this->_getAttributes($comment, 'creditmemo_comment'); } + return $result; } /** * Create new credit memo for order * - * @param string $orderIncrementId - * @param array $data array('qtys' => array('sku1' => qty1, ... , 'skuN' => qtyN), + * @param string $creditmemoIncrementId + * @param array $creditmemoData array('qtys' => array('sku1' => qty1, ... , 'skuN' => qtyN), * 'shipping_amount' => value, 'adjustment_positive' => value, 'adjustment_negative' => value) * @param string|null $comment * @param bool $notifyCustomer @@ -130,23 +131,23 @@ public function info($creditmemoIncrementId) * @param string $refundToStoreCreditAmount * @return string $creditmemoIncrementId */ - public function create($orderIncrementId, $data = null, $comment = null, $notifyCustomer = false, + public function create($creditmemoIncrementId, $creditmemoData = null, $comment = null, $notifyCustomer = false, $includeComment = false, $refundToStoreCreditAmount = null) { /** @var $order Mage_Sales_Model_Order */ - $order = Mage::getModel('sales/order')->load($orderIncrementId, 'increment_id'); + $order = Mage::getModel('sales/order')->load($creditmemoIncrementId, 'increment_id'); if (!$order->getId()) { $this->_fault('order_not_exists'); } if (!$order->canCreditmemo()) { $this->_fault('cannot_create_creditmemo'); } - $data = $this->_prepareCreateData($data); + $creditmemoData = $this->_prepareCreateData($creditmemoData); /** @var $service Mage_Sales_Model_Service_Order */ $service = Mage::getModel('sales/service_order', $order); /** @var $creditmemo Mage_Sales_Model_Order_Creditmemo */ - $creditmemo = $service->prepareCreditmemo($data); + $creditmemo = $service->prepareCreditmemo($creditmemoData); // refund to Store Credit if ($refundToStoreCreditAmount) { 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 710e48b508..754c0cf0b6 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 @@ -116,16 +116,16 @@ protected function _prepareItemQtyData($data) /** * Create new invoice for order * - * @param string $orderIncrementId + * @param string $invoiceIncrementId * @param array $itemsQty * @param string $comment * @param booleam $email * @param boolean $includeComment * @return string */ - public function create($orderIncrementId, $itemsQty, $comment = null, $email = false, $includeComment = false) + public function create($invoiceIncrementId, $itemsQty, $comment = null, $email = false, $includeComment = false) { - $order = Mage::getModel('sales/order')->loadByIncrementId($orderIncrementId); + $order = Mage::getModel('sales/order')->loadByIncrementId($invoiceIncrementId); $itemsQty = $this->_prepareItemQtyData($itemsQty); /* @var $order Mage_Sales_Model_Order */ /** diff --git a/app/code/core/Mage/Sales/Model/Order/Pdf/Abstract.php b/app/code/core/Mage/Sales/Model/Order/Pdf/Abstract.php index f557e9a682..b16b39c631 100644 --- a/app/code/core/Mage/Sales/Model/Order/Pdf/Abstract.php +++ b/app/code/core/Mage/Sales/Model/Order/Pdf/Abstract.php @@ -24,7 +24,6 @@ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ - /** * Sales Order PDF abstract model * @@ -34,7 +33,13 @@ */ abstract class Mage_Sales_Model_Order_Pdf_Abstract extends Varien_Object { + /** + * Y coordinate + * + * @var int + */ public $y; + /** * Item renderers with render type key * @@ -45,9 +50,12 @@ abstract class Mage_Sales_Model_Order_Pdf_Abstract extends Varien_Object */ protected $_renderers = array(); - const XML_PATH_SALES_PDF_INVOICE_PUT_ORDER_ID = 'sales_pdf/invoice/put_order_id'; - const XML_PATH_SALES_PDF_SHIPMENT_PUT_ORDER_ID = 'sales_pdf/shipment/put_order_id'; - const XML_PATH_SALES_PDF_CREDITMEMO_PUT_ORDER_ID = 'sales_pdf/creditmemo/put_order_id'; + /** + * Predefined constants + */ + const XML_PATH_SALES_PDF_INVOICE_PUT_ORDER_ID = 'sales_pdf/invoice/put_order_id'; + const XML_PATH_SALES_PDF_SHIPMENT_PUT_ORDER_ID = 'sales_pdf/shipment/put_order_id'; + const XML_PATH_SALES_PDF_CREDITMEMO_PUT_ORDER_ID = 'sales_pdf/creditmemo/put_order_id'; /** * Zend PDF object @@ -56,6 +64,11 @@ abstract class Mage_Sales_Model_Order_Pdf_Abstract extends Varien_Object */ protected $_pdf; + /** + * Default total model + * + * @var string + */ protected $_defaultTotalModel = 'sales/order_pdf_total_default'; /** @@ -74,9 +87,9 @@ abstract public function getPdf(); * Similar calculations exist inside the layout manager class, but widths are * generally calculated only after determining line fragments. * - * @param string $string - * @param Zend_Pdf_Resource_Font $font - * @param float $fontSize Font size in points + * @param string $string + * @param Zend_Pdf_Resource_Font $font + * @param float $fontSize Font size in points * @return float */ public function widthForStringUsingFontSize($string, $font, $fontSize) @@ -99,12 +112,12 @@ public function widthForStringUsingFontSize($string, $font, $fontSize) /** * Calculate coordinates to draw something in a column aligned to the right * - * @param string $string - * @param int $x - * @param int $columnWidth - * @param Zend_Pdf_Resource_Font $font - * @param int $fontSize - * @param int $padding + * @param string $string + * @param int $x + * @param int $columnWidth + * @param Zend_Pdf_Resource_Font $font + * @param int $fontSize + * @param int $padding * @return int */ public function getAlignRight($string, $x, $columnWidth, Zend_Pdf_Resource_Font $font, $fontSize, $padding = 5) @@ -116,11 +129,11 @@ public function getAlignRight($string, $x, $columnWidth, Zend_Pdf_Resource_Font /** * Calculate coordinates to draw something in a column aligned to the center * - * @param string $string - * @param int $x - * @param int $columnWidth - * @param Zend_Pdf_Resource_Font $font - * @param int $fontSize + * @param string $string + * @param int $x + * @param int $columnWidth + * @param Zend_Pdf_Resource_Font $font + * @param int $fontSize * @return int */ public function getAlignCenter($string, $x, $columnWidth, Zend_Pdf_Resource_Font $font, $fontSize) @@ -129,50 +142,91 @@ public function getAlignCenter($string, $x, $columnWidth, Zend_Pdf_Resource_Font return $x + round(($columnWidth - $width) / 2); } + /** + * Insert logo to pdf page + * + * @param Zend_Pdf_Page $page + * @param null $store + */ protected function insertLogo(&$page, $store = null) { + $this->y = $this->y ? $this->y : 815; $image = Mage::getStoreConfig('sales/identity/logo', $store); if ($image) { $image = Mage::getBaseDir('media') . '/sales/store/logo/' . $image; if (is_file($image)) { - $image = Zend_Pdf_Image::imageWithPath($image); - $page->drawImage($image, 25, 800, 125, 825); + $image = Zend_Pdf_Image::imageWithPath($image); + $top = 830; //top border of the page + $widthLimit = 270; //half of the page width + $heightLimit = 270; //assuming the image is not a "skyscraper" + $width = $image->getPixelWidth(); + $height = $image->getPixelHeight(); + + //preserving aspect ratio (proportions) + $ratio = $width / $height; + if ($ratio > 1 && $width > $widthLimit) { + $width = $widthLimit; + $height = $width / $ratio; + } elseif ($ratio < 1 && $height > $heightLimit) { + $height = $heightLimit; + $width = $height * $ratio; + } elseif ($ratio == 1 && $height > $heightLimit) { + $height = $heightLimit; + $width = $widthLimit; + } + + $y1 = $top - $height; + $y2 = $top; + $x1 = 25; + $x2 = $x1 + $width; + + //coordinates after transformation are rounded by Zend + $page->drawImage($image, $x1, $y1, $x2, $y2); + + $this->y = $y1 - 10; } } - //return $page; } + /** + * Insert address to pdf page + * + * @param Zend_Pdf_Page $page + * @param null $store + */ protected function insertAddress(&$page, $store = null) { $page->setFillColor(new Zend_Pdf_Color_GrayScale(0)); - $this->_setFontRegular($page, 5); - - $page->setLineWidth(0.5); - $page->setLineColor(new Zend_Pdf_Color_GrayScale(0.5)); - $page->drawLine(125, 825, 125, 790); - + $font = $this->_setFontRegular($page, 10); $page->setLineWidth(0); - $this->y = 820; + $this->y = $this->y ? $this->y : 815; + $top = 815; foreach (explode("\n", Mage::getStoreConfig('sales/identity/address', $store)) as $value){ - if ($value!=='') { - $page->drawText(trim(strip_tags($value)), 130, $this->y, 'UTF-8'); - $this->y -=7; + if ($value !== '') { + $value = preg_replace('/]*>/i', "\n", $value); + foreach (Mage::helper('core/string')->str_split($value, 45, true, true) as $_value) { + $page->drawText(trim(strip_tags($_value)), + $this->getAlignRight($_value, 130, 440, $font, 10), + $top, + 'UTF-8'); + $top -= 10; + } } } - //return $page; + $this->y = ($this->y > $top) ? $top : $this->y; } /** * Format address * - * @param string $address + * @param string $address * @return array */ protected function _formatAddress($address) { $return = array(); foreach (explode('|', $address) as $str) { - foreach (Mage::helper('core/string')->str_split($str, 65, true, true) as $part) { + foreach (Mage::helper('core/string')->str_split($str, 45, true, true) as $part) { if (empty($part)) { continue; } @@ -182,6 +236,36 @@ protected function _formatAddress($address) return $return; } + /** + * Calculate address height + * + * @param array $address + * @return int Height + */ + protected function _calcAddressHeight($address) + { + $y = 0; + foreach ($address as $value){ + if ($value !== '') { + $text = array(); + foreach (Mage::helper('core/string')->str_split($value, 55, true, true) as $_value) { + $text[] = $_value; + } + foreach ($text as $part) { + $y += 15; + } + } + } + return $y; + } + + /** + * Insert order to pdf page + * + * @param Zend_Pdf_Page $page + * @param Mage_Sales_Model_Order $obj + * @param bool $putOrderId + */ protected function insertOrder(&$page, $obj, $putOrderId = true) { if ($obj instanceof Mage_Sales_Model_Order) { @@ -192,26 +276,36 @@ protected function insertOrder(&$page, $obj, $putOrderId = true) $order = $shipment->getOrder(); } - /* @var $order Mage_Sales_Model_Order */ - $page->setFillColor(new Zend_Pdf_Color_GrayScale(0.5)); - - $page->drawRectangle(25, 790, 570, 755); + $this->y = $this->y ? $this->y : 815; + $top = $this->y; + $page->setFillColor(new Zend_Pdf_Color_GrayScale(0.45)); + $page->setLineColor(new Zend_Pdf_Color_GrayScale(0.45)); + $page->drawRectangle(25, $top, 570, $top - 55); $page->setFillColor(new Zend_Pdf_Color_GrayScale(1)); - $this->_setFontRegular($page); - + $this->setDocHeaderCoordinates(array(25, $top, 570, $top - 55)); + $this->_setFontRegular($page, 10); if ($putOrderId) { - $page->drawText(Mage::helper('sales')->__('Order # ').$order->getRealOrderId(), 35, 770, 'UTF-8'); + $page->drawText( + Mage::helper('sales')->__('Order # ') . $order->getRealOrderId(), 35, ($top -= 30), 'UTF-8' + ); } - //$page->drawText(Mage::helper('sales')->__('Order Date: ') . date( 'D M j Y', strtotime( $order->getCreatedAt() ) ), 35, 760, 'UTF-8'); - $page->drawText(Mage::helper('sales')->__('Order Date: ') . Mage::helper('core')->formatDate($order->getCreatedAtStoreDate(), 'medium', false), 35, 760, 'UTF-8'); + $page->drawText( + Mage::helper('sales')->__('Order Date: ') . Mage::helper('core')->formatDate( + $order->getCreatedAtStoreDate(), 'medium', false + ), + 35, + ($top -= 15), + 'UTF-8' + ); + $top -= 10; $page->setFillColor(new Zend_Pdf_Color_Rgb(0.93, 0.92, 0.92)); $page->setLineColor(new Zend_Pdf_Color_GrayScale(0.5)); $page->setLineWidth(0.5); - $page->drawRectangle(25, 755, 275, 730); - $page->drawRectangle(275, 755, 570, 730); + $page->drawRectangle(25, $top, 275, ($top - 25)); + $page->drawRectangle(275, $top, 570, ($top - 25)); /* Calculate blocks info */ @@ -235,58 +329,71 @@ protected function insertOrder(&$page, $obj, $putOrderId = true) if (!$order->getIsVirtual()) { /* Shipping Address */ $shippingAddress = $this->_formatAddress($order->getShippingAddress()->format('pdf')); - $shippingMethod = $order->getShippingDescription(); } $page->setFillColor(new Zend_Pdf_Color_GrayScale(0)); - $this->_setFontRegular($page); - $page->drawText(Mage::helper('sales')->__('SOLD TO:'), 35, 740 , 'UTF-8'); + $this->_setFontBold($page, 12); + $page->drawText(Mage::helper('sales')->__('Sold to:'), 35, ($top - 15), 'UTF-8'); if (!$order->getIsVirtual()) { - $page->drawText(Mage::helper('sales')->__('SHIP TO:'), 285, 740 , 'UTF-8'); - } - else { - $page->drawText(Mage::helper('sales')->__('Payment Method:'), 285, 740 , 'UTF-8'); + $page->drawText(Mage::helper('sales')->__('Ship to:'), 285, ($top - 15), 'UTF-8'); + } else { + $page->drawText(Mage::helper('sales')->__('Payment Method:'), 285, ($top - 15), 'UTF-8'); } - if (!$order->getIsVirtual()) { - $y = 730 - (max(count($billingAddress), count($shippingAddress)) * 10 + 5); - } - else { - $y = 730 - (count($billingAddress) * 10 + 5); + $addressesHeight = $this->_calcAddressHeight($billingAddress); + if (isset($shippingAddress)) { + $addressesHeight = max($addressesHeight, $this->_calcAddressHeight($shippingAddress)); } $page->setFillColor(new Zend_Pdf_Color_GrayScale(1)); - $page->drawRectangle(25, 730, 570, $y); + $page->drawRectangle(25, ($top - 25), 570, $top - 33 - $addressesHeight); $page->setFillColor(new Zend_Pdf_Color_GrayScale(0)); - $this->_setFontRegular($page); - $this->y = 720; + $this->_setFontRegular($page, 10); + $this->y = $top - 40; + $addressesStartY = $this->y; foreach ($billingAddress as $value){ - if ($value!=='') { - $page->drawText(strip_tags(ltrim($value)), 35, $this->y, 'UTF-8'); - $this->y -=10; + if ($value !== '') { + $text = array(); + foreach (Mage::helper('core/string')->str_split($value, 45, true, true) as $_value) { + $text[] = $_value; + } + foreach ($text as $part) { + $page->drawText(strip_tags(ltrim($part)), 35, $this->y, 'UTF-8'); + $this->y -= 15; + } } } + $addressesEndY = $this->y; + if (!$order->getIsVirtual()) { - $this->y = 720; + $this->y = $addressesStartY; foreach ($shippingAddress as $value){ if ($value!=='') { - $page->drawText(strip_tags(ltrim($value)), 285, $this->y, 'UTF-8'); - $this->y -=10; + $text = array(); + foreach (Mage::helper('core/string')->str_split($value, 45, true, true) as $_value) { + $text[] = $_value; + } + foreach ($text as $part) { + $page->drawText(strip_tags(ltrim($part)), 285, $this->y, 'UTF-8'); + $this->y -= 15; + } } - } + $addressesEndY = min($addressesEndY, $this->y); + $this->y = $addressesEndY; + $page->setFillColor(new Zend_Pdf_Color_Rgb(0.93, 0.92, 0.92)); $page->setLineWidth(0.5); $page->drawRectangle(25, $this->y, 275, $this->y-25); $page->drawRectangle(275, $this->y, 570, $this->y-25); - $this->y -=15; - $this->_setFontBold($page); + $this->y -= 15; + $this->_setFontBold($page, 12); $page->setFillColor(new Zend_Pdf_Color_GrayScale(0)); $page->drawText(Mage::helper('sales')->__('Payment Method'), 35, $this->y, 'UTF-8'); $page->drawText(Mage::helper('sales')->__('Shipping Method:'), 285, $this->y , 'UTF-8'); @@ -294,36 +401,52 @@ protected function insertOrder(&$page, $obj, $putOrderId = true) $this->y -=10; $page->setFillColor(new Zend_Pdf_Color_GrayScale(1)); - $this->_setFontRegular($page); + $this->_setFontRegular($page, 10); $page->setFillColor(new Zend_Pdf_Color_GrayScale(0)); $paymentLeft = 35; $yPayments = $this->y - 15; } else { - $yPayments = 720; + $yPayments = $addressesStartY; $paymentLeft = 285; } foreach ($payment as $value){ - if (trim($value)!=='') { - $page->drawText(strip_tags(trim($value)), $paymentLeft, $yPayments, 'UTF-8'); - $yPayments -=10; + if (trim($value) != '') { + //Printing "Payment Method" lines + $value = preg_replace('/]*>/i', "\n", $value); + foreach (Mage::helper('core/string')->str_split($value, 45, true, true) as $_value) { + $page->drawText(strip_tags(trim($_value)), $paymentLeft, $yPayments, 'UTF-8'); + $yPayments -= 15; + } } } - if (!$order->getIsVirtual()) { - $this->y -=15; - - $page->drawText($shippingMethod, 285, $this->y, 'UTF-8'); + if ($order->getIsVirtual()) { + // replacement of Shipments-Payments rectangle block + $yPayments = min($addressesEndY, $yPayments); + $page->drawLine(25, ($top - 25), 25, $yPayments); + $page->drawLine(570, ($top - 25), 570, $yPayments); + $page->drawLine(25, $yPayments, 570, $yPayments); - $yShipments = $this->y; + $this->y = $yPayments - 15; + } else { + $topMargin = 15; + $methodStartY = $this->y; + $this->y -= 15; + foreach (Mage::helper('core/string')->str_split($shippingMethod, 45, true, true) as $_value) { + $page->drawText(strip_tags(trim($_value)), 285, $this->y, 'UTF-8'); + $this->y -= 15; + } - $totalShippingChargesText = "(" . Mage::helper('sales')->__('Total Shipping Charges') . " " . $order->formatPriceTxt($order->getShippingAmount()) . ")"; + $yShipments = $this->y; + $totalShippingChargesText = "(" . Mage::helper('sales')->__('Total Shipping Charges') . " " + . $order->formatPriceTxt($order->getShippingAmount()) . ")"; - $page->drawText($totalShippingChargesText, 285, $yShipments-7, 'UTF-8'); - $yShipments -=10; + $page->drawText($totalShippingChargesText, 285, $yShipments - $topMargin, 'UTF-8'); + $yShipments -= $topMargin + 10; $tracks = array(); if ($shipment) { @@ -333,27 +456,24 @@ protected function insertOrder(&$page, $obj, $putOrderId = true) $page->setFillColor(new Zend_Pdf_Color_Rgb(0.93, 0.92, 0.92)); $page->setLineWidth(0.5); $page->drawRectangle(285, $yShipments, 510, $yShipments - 10); - $page->drawLine(380, $yShipments, 380, $yShipments - 10); + $page->drawLine(400, $yShipments, 400, $yShipments - 10); //$page->drawLine(510, $yShipments, 510, $yShipments - 10); - $this->_setFontRegular($page); + $this->_setFontRegular($page, 9); $page->setFillColor(new Zend_Pdf_Color_GrayScale(0)); //$page->drawText(Mage::helper('sales')->__('Carrier'), 290, $yShipments - 7 , 'UTF-8'); $page->drawText(Mage::helper('sales')->__('Title'), 290, $yShipments - 7, 'UTF-8'); - $page->drawText(Mage::helper('sales')->__('Number'), 385, $yShipments - 7, 'UTF-8'); + $page->drawText(Mage::helper('sales')->__('Number'), 410, $yShipments - 7, 'UTF-8'); - $yShipments -=17; - $this->_setFontRegular($page, 6); + $yShipments -= 20; + $this->_setFontRegular($page, 8); foreach ($tracks as $track) { $CarrierCode = $track->getCarrierCode(); - if ($CarrierCode!='custom') - { + if ($CarrierCode != 'custom') { $carrier = Mage::getSingleton('shipping/config')->getCarrierInstance($CarrierCode); $carrierTitle = $carrier->getConfigData('title'); - } - else - { + } else { $carrierTitle = Mage::helper('sales')->__('Custom Value'); } @@ -362,26 +482,48 @@ protected function insertOrder(&$page, $obj, $putOrderId = true) $endOfTitle = strlen($track->getTitle()) > $maxTitleLen ? '...' : ''; $truncatedTitle = substr($track->getTitle(), 0, $maxTitleLen) . $endOfTitle; //$page->drawText($truncatedCarrierTitle, 285, $yShipments , 'UTF-8'); - $page->drawText($truncatedTitle, 300, $yShipments , 'UTF-8'); - $page->drawText($track->getNumber(), 395, $yShipments , 'UTF-8'); - $yShipments -=7; + $page->drawText($truncatedTitle, 292, $yShipments , 'UTF-8'); + $page->drawText($track->getNumber(), 410, $yShipments , 'UTF-8'); + $yShipments -= $topMargin - 5; } } else { - $yShipments -= 7; + $yShipments -= $topMargin - 5; } $currentY = min($yPayments, $yShipments); // replacement of Shipments-Payments rectangle block - $page->drawLine(25, $this->y + 15, 25, $currentY); - $page->drawLine(25, $currentY, 570, $currentY); - $page->drawLine(570, $currentY, 570, $this->y + 15); + $page->drawLine(25, $methodStartY, 25, $currentY); //left + $page->drawLine(25, $currentY, 570, $currentY); //bottom + $page->drawLine(570, $currentY, 570, $methodStartY); //right $this->y = $currentY; $this->y -= 15; } } + /** + * Insert title and number for concrete document type + * + * @param Zend_Pdf_Page $page + * @param string $text + * @return void + */ + public function insertDocumentNumber(Zend_Pdf_Page $page, $text) + { + $page->setFillColor(new Zend_Pdf_Color_GrayScale(1)); + $this->_setFontRegular($page, 10); + $docHeader = $this->getDocHeaderCoordinates(); + $page->drawText($text, 35, $docHeader[1] - 15, 'UTF-8'); + } + + /** + * Sort totals list + * + * @param array $a + * @param array $b + * @return int + */ protected function _sortTotalsList($a, $b) { if (!isset($a['sort_order']) || !isset($b['sort_order'])) { return 0; @@ -394,6 +536,12 @@ protected function _sortTotalsList($a, $b) { return ($a['sort_order'] > $b['sort_order']) ? 1 : -1; } + /** + * Return total list + * + * @param Mage_Sales_Model_Abstract $source + * @return array + */ protected function _getTotalsList($source) { $totals = Mage::getConfig()->getNode('global/pdf/totals')->asArray(); @@ -419,6 +567,13 @@ protected function _getTotalsList($source) return $totalModels; } + /** + * Insert totals to pdf page + * + * @param Zend_Pdf_Page $page + * @param Mage_Sales_Model_Abstract $source + * @return Zend_Pdf_Page + */ protected function insertTotals($page, $source){ $order = $source->getOrder(); $totals = $this->_getTotalsList($source); @@ -431,6 +586,7 @@ protected function insertTotals($page, $source){ ->setSource($source); if ($total->canDisplay()) { + $total->setFontSize(10); foreach ($total->getTotalsForDisplay() as $totalData) { $lineBlock['lines'][] = array( array( @@ -452,10 +608,17 @@ protected function insertTotals($page, $source){ } } + $this->y -= 20; $page = $this->drawLineBlocks($page, array($lineBlock)); return $page; } + /** + * Parse item description + * + * @param Varien_Object $item + * @return array + */ protected function _parseItemDescription($item) { $matches = array(); @@ -469,7 +632,6 @@ protected function _parseItemDescription($item) /** * Before getPdf processing - * */ protected function _beforeGetPdf() { $translate = Mage::getSingleton('core/translate'); @@ -479,7 +641,6 @@ protected function _beforeGetPdf() { /** * After getPdf processing - * */ protected function _afterGetPdf() { $translate = Mage::getSingleton('core/translate'); @@ -487,6 +648,13 @@ protected function _afterGetPdf() { $translate->setTranslateInline(true); } + /** + * Format option value process + * + * @param array|string $value + * @param Mage_Sales_Model_Order $order + * @return string + */ protected function _formatOptionValue($value, $order) { $resultValue = ''; @@ -506,9 +674,14 @@ protected function _formatOptionValue($value, $order) } } + /** + * Initialize renderer process + * + * @param string $type + */ protected function _initRenderer($type) { - $node = Mage::getConfig()->getNode('global/pdf/'.$type); + $node = Mage::getConfig()->getNode('global/pdf/' . $type); foreach ($node->children() as $renderer) { $this->_renderers[$renderer->getName()] = array( 'model' => (string)$renderer, @@ -520,6 +693,7 @@ protected function _initRenderer($type) /** * Retrieve renderer model * + * @param string $type * @throws Mage_Core_Exception * @return Mage_Sales_Model_Order_Pdf_Items_Abstract */ @@ -545,7 +719,7 @@ protected function _getRenderer($type) * * Retrieve renderer model * - * @param string $type + * @param string $type * @return Mage_Sales_Model_Order_Pdf_Items_Abstract */ public function getRenderer($type) @@ -556,9 +730,9 @@ public function getRenderer($type) /** * Draw Item process * - * @param Varien_Object $item - * @param Zend_Pdf_Page $page - * @param Mage_Sales_Model_Order $order + * @param Varien_Object $item + * @param Zend_Pdf_Page $page + * @param Mage_Sales_Model_Order $order * @return Zend_Pdf_Page */ protected function _drawItem(Varien_Object $item, Zend_Pdf_Page $page, Mage_Sales_Model_Order $order) @@ -576,6 +750,13 @@ protected function _drawItem(Varien_Object $item, Zend_Pdf_Page $page, Mage_Sale return $renderer->getPage(); } + /** + * Set font as regular + * + * @param Zend_Pdf_Page $object + * @param int $size + * @return Zend_Pdf_Resource_Font + */ protected function _setFontRegular($object, $size = 7) { $font = Zend_Pdf_Font::fontWithPath(Mage::getBaseDir() . '/lib/LinLibertineFont/LinLibertine_Re-4.4.1.ttf'); @@ -583,6 +764,13 @@ protected function _setFontRegular($object, $size = 7) return $font; } + /** + * Set font as bold + * + * @param Zend_Pdf_Page $object + * @param int $size + * @return Zend_Pdf_Resource_Font + */ protected function _setFontBold($object, $size = 7) { $font = Zend_Pdf_Font::fontWithPath(Mage::getBaseDir() . '/lib/LinLibertineFont/LinLibertine_Bd-2.8.1.ttf'); @@ -590,6 +778,13 @@ protected function _setFontBold($object, $size = 7) return $font; } + /** + * Set font as italic + * + * @param Zend_Pdf_Page $object + * @param int $size + * @return Zend_Pdf_Resource_Font + */ protected function _setFontItalic($object, $size = 7) { $font = Zend_Pdf_Font::fontWithPath(Mage::getBaseDir() . '/lib/LinLibertineFont/LinLibertine_It-2.8.2.ttf'); @@ -600,7 +795,7 @@ protected function _setFontItalic($object, $size = 7) /** * Set PDF object * - * @param Zend_Pdf $pdf + * @param Zend_Pdf $pdf * @return Mage_Sales_Model_Order_Pdf_Abstract */ protected function _setPdf(Zend_Pdf $pdf) @@ -627,7 +822,7 @@ protected function _getPdf() /** * Create new page and assign to PDF object * - * @param array $settings + * @param array $settings * @return Zend_Pdf_Page */ public function newPage(array $settings = array()) @@ -659,9 +854,9 @@ public function newPage(array $settings = array()) * align string; text align (also see feed parametr), optional left, right * height int;line spacing (default 10) * - * @param Zend_Pdf_Page $page - * @param array $draw - * @param array $pageSettings + * @param Zend_Pdf_Page $page + * @param array $draw + * @param array $pageSettings * @throws Mage_Core_Exception * @return Zend_Pdf_Page */ @@ -702,12 +897,11 @@ public function drawLineBlocks(Zend_Pdf_Page $page, array $draw, array $pageSett foreach ($lines as $line) { $maxHeight = 0; foreach ($line as $column) { - $fontSize = empty($column['font_size']) ? 7 : $column['font_size']; + $fontSize = empty($column['font_size']) ? 10 : $column['font_size']; if (!empty($column['font_file'])) { $font = Zend_Pdf_Font::fontWithPath($column['font_file']); $page->setFont($font, $fontSize); - } - else { + } else { $fontStyle = empty($column['font']) ? 'regular' : $column['font']; switch ($fontStyle) { case 'bold': diff --git a/app/code/core/Mage/Sales/Model/Order/Pdf/Creditmemo.php b/app/code/core/Mage/Sales/Model/Order/Pdf/Creditmemo.php index ddbf5c12cc..75487b9cc7 100644 --- a/app/code/core/Mage/Sales/Model/Order/Pdf/Creditmemo.php +++ b/app/code/core/Mage/Sales/Model/Order/Pdf/Creditmemo.php @@ -34,6 +34,84 @@ */ class Mage_Sales_Model_Order_Pdf_Creditmemo extends Mage_Sales_Model_Order_Pdf_Abstract { + /** + * Draw table header for product items + * + * @param Zend_Pdf_Page $page + * @return void + */ + protected function _drawHeader(Zend_Pdf_Page $page) + { + $this->_setFontRegular($page, 10); + $page->setFillColor(new Zend_Pdf_Color_RGB(0.93, 0.92, 0.92)); + $page->setLineColor(new Zend_Pdf_Color_GrayScale(0.5)); + $page->setLineWidth(0.5); + $page->drawRectangle(25, $this->y, 570, $this->y - 30); + $this->y -= 10; + $page->setFillColor(new Zend_Pdf_Color_RGB(0, 0, 0)); + + //columns headers + $lines[0][] = array( + 'text' => Mage::helper('sales')->__('Products'), + 'feed' => 35, + ); + + $lines[0][] = array( + 'text' => Mage::helper('core/string')->str_split(Mage::helper('sales')->__('SKU'), 12, true, true), + 'feed' => 255, + 'align' => 'right' + ); + + $lines[0][] = array( + 'text' => Mage::helper('core/string')->str_split(Mage::helper('sales')->__('Total (ex)'), 12, true, true), + 'feed' => 330, + 'align' => 'right', + //'width' => 50, + ); + + $lines[0][] = array( + 'text' => Mage::helper('core/string')->str_split(Mage::helper('sales')->__('Discount'), 12, true, true), + 'feed' => 380, + 'align' => 'right', + //'width' => 50, + ); + + $lines[0][] = array( + 'text' => Mage::helper('core/string')->str_split(Mage::helper('sales')->__('Qty'), 12, true, true), + 'feed' => 445, + 'align' => 'right', + //'width' => 30, + ); + + $lines[0][] = array( + 'text' => Mage::helper('core/string')->str_split(Mage::helper('sales')->__('Tax'), 12, true, true), + 'feed' => 495, + 'align' => 'right', + //'width' => 45, + ); + + $lines[0][] = array( + 'text' => Mage::helper('core/string')->str_split(Mage::helper('sales')->__('Total (inc)'), 12, true, true), + 'feed' => 565, + 'align' => 'right' + ); + + $lineBlock = array( + 'lines' => $lines, + 'height' => 10 + ); + + $this->drawLineBlocks($page, array($lineBlock), array('table_header' => true)); + $page->setFillColor(new Zend_Pdf_Color_GrayScale(0)); + $this->y -= 20; + } + + /** + * Return PDF document + * + * @param array $creditmemos + * @return Zend_Pdf + */ public function getPdf($creditmemos = array()) { $this->_beforeGetPdf(); @@ -49,116 +127,56 @@ public function getPdf($creditmemos = array()) Mage::app()->getLocale()->emulate($creditmemo->getStoreId()); Mage::app()->setCurrentStore($creditmemo->getStoreId()); } - $page = $pdf->newPage(Zend_Pdf_Page::SIZE_A4); - $pdf->pages[] = $page; - + $page = $this->newPage(); $order = $creditmemo->getOrder(); - /* Add image */ $this->insertLogo($page, $creditmemo->getStore()); - /* Add address */ $this->insertAddress($page, $creditmemo->getStore()); - /* Add head */ - $this->insertOrder($page, $order, Mage::getStoreConfigFlag(self::XML_PATH_SALES_PDF_CREDITMEMO_PUT_ORDER_ID, $order->getStoreId())); - - $page->setFillColor(new Zend_Pdf_Color_GrayScale(1)); - $this->_setFontRegular($page); - $page->drawText(Mage::helper('sales')->__('Credit Memo # ') . $creditmemo->getIncrementId(), 35, 780, 'UTF-8'); - + $this->insertOrder( + $page, + $order, + Mage::getStoreConfigFlag(self::XML_PATH_SALES_PDF_CREDITMEMO_PUT_ORDER_ID, $order->getStoreId()) + ); + /* Add document text and number */ + $this->insertDocumentNumber( + $page, + Mage::helper('sales')->__('Credit Memo # ') . $creditmemo->getIncrementId() + ); /* Add table head */ - $page->setFillColor(new Zend_Pdf_Color_RGB(0.93, 0.92, 0.92)); - $page->setLineColor(new Zend_Pdf_Color_GrayScale(0.5)); - $page->setLineWidth(0.5); - $page->drawRectangle(25, $this->y, 570, $this->y-15); - $this->y -=10; - $page->setFillColor(new Zend_Pdf_Color_RGB(0.4, 0.4, 0.4)); $this->_drawHeader($page); - $this->y -=15; - - $page->setFillColor(new Zend_Pdf_Color_GrayScale(0)); - /* Add body */ foreach ($creditmemo->getAllItems() as $item){ if ($item->getOrderItem()->getParentItem()) { continue; } - - if ($this->y<20) { - $page = $this->newPage(array('table_header' => true)); - } - /* Draw item */ - $page = $this->_drawItem($item, $page, $order); + $this->_drawItem($item, $page, $order); + $page = end($pdf->pages); } - /* Add totals */ - $page = $this->insertTotals($page, $creditmemo); + $this->insertTotals($page, $creditmemo); } - $this->_afterGetPdf(); - if ($creditmemo->getStoreId()) { Mage::app()->getLocale()->revert(); } return $pdf; } - protected function _drawHeader(Zend_Pdf_Page $page) - { - $font = $page->getFont(); - $size = $page->getFontSize(); - - $page->drawText(Mage::helper('sales')->__('Products'), $x = 35, $this->y, 'UTF-8'); - $x += 220; - - $page->drawText(Mage::helper('sales')->__('SKU'), $x, $this->y, 'UTF-8'); - $x += 100; - - $text = Mage::helper('sales')->__('Total (ex)'); - $page->drawText($text, $this->getAlignRight($text, $x, 50, $font, $size), $this->y, 'UTF-8'); - $x += 50; - - $text = Mage::helper('sales')->__('Discount'); - $page->drawText($text, $this->getAlignRight($text, $x, 50, $font, $size), $this->y, 'UTF-8'); - $x += 50; - - $text = Mage::helper('sales')->__('Qty'); - $page->drawText($text, $this->getAlignCenter($text, $x, 30, $font, $size), $this->y, 'UTF-8'); - $x += 30; - - $text = Mage::helper('sales')->__('Tax'); - $page->drawText($text, $this->getAlignRight($text, $x, 45, $font, $size, 10), $this->y, 'UTF-8'); - $x += 45; - - $text = Mage::helper('sales')->__('Total (inc)'); - $page->drawText($text, $this->getAlignRight($text, $x, 570 - $x, $font, $size), $this->y, 'UTF-8'); - } - /** * Create new page and assign to PDF object * - * @param array $settings + * @param array $settings * @return Zend_Pdf_Page */ public function newPage(array $settings = array()) { $page = parent::newPage($settings); - if (!empty($settings['table_header'])) { - $this->_setFontRegular($page); - $page->setFillColor(new Zend_Pdf_Color_RGB(0.93, 0.92, 0.92)); - $page->setLineColor(new Zend_Pdf_Color_GrayScale(0.5)); - $page->setLineWidth(0.5); - $page->drawRectangle(25, $this->y, 570, $this->y-15); - $this->y -=10; - $page->setFillColor(new Zend_Pdf_Color_RGB(0.4, 0.4, 0.4)); $this->_drawHeader($page); - $page->setFillColor(new Zend_Pdf_Color_GrayScale(0)); - $this->y -=20; } - return $page; } } diff --git a/app/code/core/Mage/Sales/Model/Order/Pdf/Invoice.php b/app/code/core/Mage/Sales/Model/Order/Pdf/Invoice.php index 2612b60fc0..3577c9bffc 100644 --- a/app/code/core/Mage/Sales/Model/Order/Pdf/Invoice.php +++ b/app/code/core/Mage/Sales/Model/Order/Pdf/Invoice.php @@ -24,7 +24,6 @@ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ - /** * Sales Order Invoice PDF model * @@ -34,6 +33,75 @@ */ class Mage_Sales_Model_Order_Pdf_Invoice extends Mage_Sales_Model_Order_Pdf_Abstract { + /** + * Draw header for item table + * + * @param Zend_Pdf_Page $page + * @return void + */ + protected function _drawHeader(Zend_Pdf_Page $page) + { + /* Add table head */ + $this->_setFontRegular($page, 10); + $page->setFillColor(new Zend_Pdf_Color_RGB(0.93, 0.92, 0.92)); + $page->setLineColor(new Zend_Pdf_Color_GrayScale(0.5)); + $page->setLineWidth(0.5); + $page->drawRectangle(25, $this->y, 570, $this->y -15); + $this->y -= 10; + $page->setFillColor(new Zend_Pdf_Color_RGB(0, 0, 0)); + + //columns headers + $lines[0][] = array( + 'text' => Mage::helper('sales')->__('Products'), + 'feed' => 35 + ); + + $lines[0][] = array( + 'text' => Mage::helper('sales')->__('SKU'), + 'feed' => 290, + 'align' => 'right' + ); + + $lines[0][] = array( + 'text' => Mage::helper('sales')->__('Qty'), + 'feed' => 435, + 'align' => 'right' + ); + + $lines[0][] = array( + 'text' => Mage::helper('sales')->__('Price'), + 'feed' => 360, + 'align' => 'right' + ); + + $lines[0][] = array( + 'text' => Mage::helper('sales')->__('Tax'), + 'feed' => 495, + 'align' => 'right' + ); + + $lines[0][] = array( + 'text' => Mage::helper('sales')->__('Subtotal'), + 'feed' => 565, + 'align' => 'right' + ); + + $lineBlock = array( + 'lines' => $lines, + 'height' => 5 + ); + + $this->drawLineBlocks($page, array($lineBlock), array('table_header' => true)); + $page->setFillColor(new Zend_Pdf_Color_GrayScale(0)); + $this->y -= 20; + } + + /** + * Return PDF document + * + * @param array $invoices + * @return Zend_Pdf + */ public function getPdf($invoices = array()) { $this->_beforeGetPdf(); @@ -49,77 +117,48 @@ public function getPdf($invoices = array()) Mage::app()->getLocale()->emulate($invoice->getStoreId()); Mage::app()->setCurrentStore($invoice->getStoreId()); } - $page = $pdf->newPage(Zend_Pdf_Page::SIZE_A4); - $pdf->pages[] = $page; - + $page = $this->newPage(); $order = $invoice->getOrder(); - /* Add image */ $this->insertLogo($page, $invoice->getStore()); - /* Add address */ $this->insertAddress($page, $invoice->getStore()); - /* Add head */ - $this->insertOrder($page, $order, Mage::getStoreConfigFlag(self::XML_PATH_SALES_PDF_INVOICE_PUT_ORDER_ID, $order->getStoreId())); - - - $page->setFillColor(new Zend_Pdf_Color_GrayScale(1)); - $this->_setFontRegular($page); - $page->drawText(Mage::helper('sales')->__('Invoice # ') . $invoice->getIncrementId(), 35, 780, 'UTF-8'); - + $this->insertOrder( + $page, + $order, + Mage::getStoreConfigFlag(self::XML_PATH_SALES_PDF_INVOICE_PUT_ORDER_ID, $order->getStoreId()) + ); + /* Add document text and number */ + $this->insertDocumentNumber( + $page, + Mage::helper('sales')->__('Invoice # ') . $invoice->getIncrementId() + ); /* Add table */ - $page->setFillColor(new Zend_Pdf_Color_RGB(0.93, 0.92, 0.92)); - $page->setLineColor(new Zend_Pdf_Color_GrayScale(0.5)); - $page->setLineWidth(0.5); - - $page->drawRectangle(25, $this->y, 570, $this->y -15); - $this->y -=10; - - /* Add table head */ - $page->setFillColor(new Zend_Pdf_Color_RGB(0.4, 0.4, 0.4)); - $page->drawText(Mage::helper('sales')->__('Products'), 35, $this->y, 'UTF-8'); - $page->drawText(Mage::helper('sales')->__('SKU'), 255, $this->y, 'UTF-8'); - $page->drawText(Mage::helper('sales')->__('Price'), 380, $this->y, 'UTF-8'); - $page->drawText(Mage::helper('sales')->__('Qty'), 430, $this->y, 'UTF-8'); - $page->drawText(Mage::helper('sales')->__('Tax'), 480, $this->y, 'UTF-8'); - $page->drawText(Mage::helper('sales')->__('Subtotal'), 535, $this->y, 'UTF-8'); - - $this->y -=15; - - $page->setFillColor(new Zend_Pdf_Color_GrayScale(0)); - + $this->_drawHeader($page); /* Add body */ foreach ($invoice->getAllItems() as $item){ if ($item->getOrderItem()->getParentItem()) { continue; } - - if ($this->y < 15) { - $page = $this->newPage(array('table_header' => true)); - } - /* Draw item */ - $page = $this->_drawItem($item, $page, $order); + $this->_drawItem($item, $page, $order); + $page = end($pdf->pages); } - /* Add totals */ - $page = $this->insertTotals($page, $invoice); - + $this->insertTotals($page, $invoice); if ($invoice->getStoreId()) { Mage::app()->getLocale()->revert(); } } - $this->_afterGetPdf(); - return $pdf; } /** * Create new page and assign to PDF object * - * @param array $settings + * @param array $settings * @return Zend_Pdf_Page */ public function newPage(array $settings = array()) @@ -128,27 +167,9 @@ public function newPage(array $settings = array()) $page = $this->_getPdf()->newPage(Zend_Pdf_Page::SIZE_A4); $this->_getPdf()->pages[] = $page; $this->y = 800; - if (!empty($settings['table_header'])) { - $this->_setFontRegular($page); - $page->setFillColor(new Zend_Pdf_Color_RGB(0.93, 0.92, 0.92)); - $page->setLineColor(new Zend_Pdf_Color_GrayScale(0.5)); - $page->setLineWidth(0.5); - $page->drawRectangle(25, $this->y, 570, $this->y-15); - $this->y -=10; - - $page->setFillColor(new Zend_Pdf_Color_RGB(0.4, 0.4, 0.4)); - $page->drawText(Mage::helper('sales')->__('Product'), 35, $this->y, 'UTF-8'); - $page->drawText(Mage::helper('sales')->__('SKU'), 255, $this->y, 'UTF-8'); - $page->drawText(Mage::helper('sales')->__('Price'), 380, $this->y, 'UTF-8'); - $page->drawText(Mage::helper('sales')->__('Qty'), 430, $this->y, 'UTF-8'); - $page->drawText(Mage::helper('sales')->__('Tax'), 480, $this->y, 'UTF-8'); - $page->drawText(Mage::helper('sales')->__('Subtotal'), 535, $this->y, 'UTF-8'); - - $page->setFillColor(new Zend_Pdf_Color_GrayScale(0)); - $this->y -=20; + $this->_drawHeader($page); } - return $page; } } diff --git a/app/code/core/Mage/Sales/Model/Order/Pdf/Items/Abstract.php b/app/code/core/Mage/Sales/Model/Order/Pdf/Items/Abstract.php index 86cfec3526..034de55e13 100644 --- a/app/code/core/Mage/Sales/Model/Order/Pdf/Items/Abstract.php +++ b/app/code/core/Mage/Sales/Model/Order/Pdf/Items/Abstract.php @@ -24,7 +24,6 @@ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ - /** * Sales Order Pdf Items renderer Abstract * @@ -72,7 +71,7 @@ abstract class Mage_Sales_Model_Order_Pdf_Items_Abstract extends Mage_Core_Model /** * Set order model * - * @param Mage_Sales_Model_Order $order + * @param Mage_Sales_Model_Order $order * @return Mage_Sales_Model_Order_Pdf_Items_Abstract */ public function setOrder(Mage_Sales_Model_Order $order) @@ -84,7 +83,7 @@ public function setOrder(Mage_Sales_Model_Order $order) /** * Set Source model * - * @param Mage_Core_Model_Abstract $source + * @param Mage_Core_Model_Abstract $source * @return Mage_Sales_Model_Order_Pdf_Items_Abstract */ public function setSource(Mage_Core_Model_Abstract $source) @@ -96,7 +95,7 @@ public function setSource(Mage_Core_Model_Abstract $source) /** * Set item object * - * @param Varien_Object $item + * @param Varien_Object $item * @return Mage_Sales_Model_Order_Pdf_Items_Abstract */ public function setItem(Varien_Object $item) @@ -108,7 +107,7 @@ public function setItem(Varien_Object $item) /** * Set Pdf model * - * @param Mage_Sales_Model_Order_Pdf_Abstract $pdf + * @param Mage_Sales_Model_Order_Pdf_Abstract $pdf * @return Mage_Sales_Model_Order_Pdf_Items_Abstract */ public function setPdf(Mage_Sales_Model_Order_Pdf_Abstract $pdf) @@ -120,7 +119,7 @@ public function setPdf(Mage_Sales_Model_Order_Pdf_Abstract $pdf) /** * Set current page * - * @param Zend_Pdf_Page $page + * @param Zend_Pdf_Page $page * @return Mage_Sales_Model_Order_Pdf_Items_Abstract */ public function setPage(Zend_Pdf_Page $page) @@ -205,6 +204,12 @@ public function getPage() */ abstract public function draw(); + /** + * Format option value process + * + * @param $value + * @return string + */ protected function _formatOptionValue($value) { $order = $this->getOrder(); @@ -283,6 +288,11 @@ public function getItemPricesForDisplay() return $prices; } + /** + * Retrieve item options + * + * @return array + */ public function getItemOptions() { $result = array(); if ($options = $this->getItem()->getOrderItem()->getProductOptions()) { @@ -299,6 +309,12 @@ public function getItemOptions() { return $result; } + /** + * Set font as regular + * + * @param int $size + * @return Zend_Pdf_Resource_Font + */ protected function _setFontRegular($size = 7) { $font = Zend_Pdf_Font::fontWithPath(Mage::getBaseDir() . '/lib/LinLibertineFont/LinLibertine_Re-4.4.1.ttf'); @@ -306,6 +322,12 @@ protected function _setFontRegular($size = 7) return $font; } + /** + * Set font as bold + * + * @param int $size + * @return Zend_Pdf_Resource_Font + */ protected function _setFontBold($size = 7) { $font = Zend_Pdf_Font::fontWithPath(Mage::getBaseDir() . '/lib/LinLibertineFont/LinLibertine_Bd-2.8.1.ttf'); @@ -313,6 +335,12 @@ protected function _setFontBold($size = 7) return $font; } + /** + * Set font as italic + * + * @param int $size + * @return Zend_Pdf_Resource_Font + */ protected function _setFontItalic($size = 7) { $font = Zend_Pdf_Font::fontWithPath(Mage::getBaseDir() . '/lib/LinLibertineFont/LinLibertine_It-2.8.2.ttf'); @@ -320,6 +348,12 @@ protected function _setFontItalic($size = 7) return $font; } + /** + * Return item Sku + * + * @param $item + * @return mixed + */ public function getSku($item) { if ($item->getOrderItem()->getProductOptionByCode('simple_sku')) diff --git a/app/code/core/Mage/Sales/Model/Order/Pdf/Items/Creditmemo/Default.php b/app/code/core/Mage/Sales/Model/Order/Pdf/Items/Creditmemo/Default.php index bfeb865886..8109869768 100644 --- a/app/code/core/Mage/Sales/Model/Order/Pdf/Items/Creditmemo/Default.php +++ b/app/code/core/Mage/Sales/Model/Order/Pdf/Items/Creditmemo/Default.php @@ -24,7 +24,6 @@ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ - /** * Sales Order Creditmemo Pdf default items renderer * @@ -34,6 +33,9 @@ */ class Mage_Sales_Model_Order_Pdf_Items_Creditmemo_Default extends Mage_Sales_Model_Order_Pdf_Items_Abstract { + /** + * Draw process + */ public function draw() { $order = $this->getOrder(); @@ -42,24 +44,19 @@ public function draw() $page = $this->getPage(); $lines = array(); - $leftBound = 35; - $rightBound = 565; - - $x = $leftBound; // draw Product name $lines[0] = array(array( - 'text' => Mage::helper('core/string')->str_split($item->getName(), 60, true, true), - 'feed' => $x, + 'text' => Mage::helper('core/string')->str_split($item->getName(), 35, true, true), + 'feed' => 35, )); - $x += 220; // draw SKU $lines[0][] = array( - 'text' => Mage::helper('core/string')->str_split($this->getSku($item), 25), - 'feed' => $x + 'text' => Mage::helper('core/string')->str_split($this->getSku($item), 17), + 'feed' => 255, + 'align' => 'right' ); - $x += 100; // draw Total (ex) $i = 0; $prices = $this->getItemPricesForDisplay(); @@ -68,7 +65,7 @@ public function draw() // draw Subtotal label $lines[$i][] = array( 'text' => $priceData['label'], - 'feed' => $x, + 'feed' => 330, 'align' => 'right', 'width' => 50, ); @@ -77,51 +74,43 @@ public function draw() // draw Subtotal $lines[$i][] = array( 'text' => $priceData['subtotal'], - 'feed' => $x, + 'feed' => 330, 'font' => 'bold', 'align' => 'right', - 'width' => 50, ); $i++; } - $x += 50; // draw Discount $lines[0][] = array( 'text' => $order->formatPriceTxt(-$item->getDiscountAmount()), - 'feed' => $x, + 'feed' => 380, 'font' => 'bold', - 'align' => 'right', - 'width' => 50, + 'align' => 'right' ); - $x += 50; // draw QTY $lines[0][] = array( - 'text' => $item->getQty()*1, - 'feed' => $x, + 'text' => $item->getQty() * 1, + 'feed' => 445, 'font' => 'bold', - 'align' => 'center', - 'width' => 30, + 'align' => 'right', ); - $x += 30; // draw Tax $lines[0][] = array( 'text' => $order->formatPriceTxt($item->getTaxAmount()), - 'feed' => $x, + 'feed' => 495, 'font' => 'bold', - 'align' => 'right', - 'width' => 45, + 'align' => 'right' ); - $x += 45; // draw Subtotal $subtotal = $item->getRowTotal() + $item->getTaxAmount() + $item->getHiddenTaxAmount() - $item->getDiscountAmount(); $lines[0][] = array( 'text' => $order->formatPriceTxt($subtotal), - 'feed' => $rightBound, + 'feed' => 565, 'font' => 'bold', 'align' => 'right' ); @@ -132,23 +121,23 @@ public function draw() foreach ($options as $option) { // draw options label $lines[][] = array( - 'text' => Mage::helper('core/string')->str_split(strip_tags($option['label']), 70, true, true), + 'text' => Mage::helper('core/string')->str_split(strip_tags($option['label']), 40, true, true), 'font' => 'italic', - 'feed' => $leftBound + 'feed' => 35 ); // draw options value $_printValue = isset($option['print_value']) ? $option['print_value'] : strip_tags($option['value']); $lines[][] = array( - 'text' => Mage::helper('core/string')->str_split($_printValue, 50, true, true), - 'feed' => $leftBound + 5 + 'text' => Mage::helper('core/string')->str_split($_printValue, 30, true, true), + 'feed' => 40 ); } } $lineBlock = array( 'lines' => $lines, - 'height' => 10 + 'height' => 20 ); $page = $pdf->drawLineBlocks($page, array($lineBlock), array('table_header' => true)); diff --git a/app/code/core/Mage/Sales/Model/Order/Pdf/Items/Creditmemo/Grouped.php b/app/code/core/Mage/Sales/Model/Order/Pdf/Items/Creditmemo/Grouped.php index 2133347a7e..498a2ecf31 100644 --- a/app/code/core/Mage/Sales/Model/Order/Pdf/Items/Creditmemo/Grouped.php +++ b/app/code/core/Mage/Sales/Model/Order/Pdf/Items/Creditmemo/Grouped.php @@ -24,7 +24,6 @@ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ - /** * Sales Order Creditmemo Pdf grouped items renderer * @@ -34,6 +33,9 @@ */ class Mage_Sales_Model_Order_Pdf_Items_Creditmemo_Grouped extends Mage_Sales_Model_Order_Pdf_Items_Creditmemo_Default { + /** + * Draw process + */ public function draw() { $type = $this->getItem()->getOrderItem()->getRealProductType(); diff --git a/app/code/core/Mage/Sales/Model/Order/Pdf/Items/Invoice/Default.php b/app/code/core/Mage/Sales/Model/Order/Pdf/Items/Invoice/Default.php index b174967038..54e94d14ae 100644 --- a/app/code/core/Mage/Sales/Model/Order/Pdf/Items/Invoice/Default.php +++ b/app/code/core/Mage/Sales/Model/Order/Pdf/Items/Invoice/Default.php @@ -24,7 +24,6 @@ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ - /** * Sales Order Invoice Pdf default items renderer * @@ -36,7 +35,6 @@ class Mage_Sales_Model_Order_Pdf_Items_Invoice_Default extends Mage_Sales_Model_ { /** * Draw item line - * */ public function draw() { @@ -48,20 +46,22 @@ public function draw() // draw Product name $lines[0] = array(array( - 'text' => Mage::helper('core/string')->str_split($item->getName(), 60, true, true), + 'text' => Mage::helper('core/string')->str_split($item->getName(), 35, true, true), 'feed' => 35, )); // draw SKU $lines[0][] = array( - 'text' => Mage::helper('core/string')->str_split($this->getSku($item), 25), - 'feed' => 255 + 'text' => Mage::helper('core/string')->str_split($this->getSku($item), 17), + 'feed' => 290, + 'align' => 'right' ); // draw QTY $lines[0][] = array( - 'text' => $item->getQty()*1, - 'feed' => 435 + 'text' => $item->getQty() * 1, + 'feed' => 435, + 'align' => 'right' ); // draw item Prices @@ -72,13 +72,13 @@ public function draw() // draw Price label $lines[$i][] = array( 'text' => $priceData['label'], - 'feed' => 395, + 'feed' => 360, 'align' => 'right' ); // draw Subtotal label $lines[$i][] = array( 'text' => $priceData['label'], - 'feed' => 565, + 'feed' => 530, 'align' => 'right' ); $i++; @@ -86,14 +86,14 @@ public function draw() // draw Price $lines[$i][] = array( 'text' => $priceData['price'], - 'feed' => 395, + 'feed' => 360, 'font' => 'bold', 'align' => 'right' ); // draw Subtotal $lines[$i][] = array( 'text' => $priceData['subtotal'], - 'feed' => 565, + 'feed' => 530, 'font' => 'bold', 'align' => 'right' ); @@ -114,7 +114,7 @@ public function draw() foreach ($options as $option) { // draw options label $lines[][] = array( - 'text' => Mage::helper('core/string')->str_split(strip_tags($option['label']), 70, true, true), + 'text' => Mage::helper('core/string')->str_split(strip_tags($option['label']), 40, true, true), 'font' => 'italic', 'feed' => 35 ); @@ -128,7 +128,7 @@ public function draw() $values = explode(', ', $_printValue); foreach ($values as $value) { $lines[][] = array( - 'text' => Mage::helper('core/string')->str_split($value, 50, true, true), + 'text' => Mage::helper('core/string')->str_split($value, 30, true, true), 'feed' => 40 ); } @@ -138,7 +138,7 @@ public function draw() $lineBlock = array( 'lines' => $lines, - 'height' => 10 + 'height' => 20 ); $page = $pdf->drawLineBlocks($page, array($lineBlock), array('table_header' => true)); diff --git a/app/code/core/Mage/Sales/Model/Order/Pdf/Items/Invoice/Grouped.php b/app/code/core/Mage/Sales/Model/Order/Pdf/Items/Invoice/Grouped.php index 71d764fe76..fb857a01a2 100644 --- a/app/code/core/Mage/Sales/Model/Order/Pdf/Items/Invoice/Grouped.php +++ b/app/code/core/Mage/Sales/Model/Order/Pdf/Items/Invoice/Grouped.php @@ -24,7 +24,6 @@ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ - /** * Sales Order Invoice Pdf grouped items renderer * @@ -34,6 +33,9 @@ */ class Mage_Sales_Model_Order_Pdf_Items_Invoice_Grouped extends Mage_Sales_Model_Order_Pdf_Items_Invoice_Default { + /** + * Draw process + */ public function draw() { $type = $this->getItem()->getOrderItem()->getRealProductType(); diff --git a/app/code/core/Mage/Sales/Model/Order/Pdf/Items/Shipment/Default.php b/app/code/core/Mage/Sales/Model/Order/Pdf/Items/Shipment/Default.php index 1eb83eeee7..d58b3d99d3 100644 --- a/app/code/core/Mage/Sales/Model/Order/Pdf/Items/Shipment/Default.php +++ b/app/code/core/Mage/Sales/Model/Order/Pdf/Items/Shipment/Default.php @@ -24,7 +24,6 @@ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ - /** * Sales Order Shipment Pdf default items renderer * @@ -36,7 +35,6 @@ class Mage_Sales_Model_Order_Pdf_Items_Shipment_Default extends Mage_Sales_Model { /** * Draw item line - * */ public function draw() { @@ -48,7 +46,7 @@ public function draw() // draw Product name $lines[0] = array(array( 'text' => Mage::helper('core/string')->str_split($item->getName(), 60, true, true), - 'feed' => 60, + 'feed' => 100, )); // draw QTY @@ -60,7 +58,8 @@ public function draw() // draw SKU $lines[0][] = array( 'text' => Mage::helper('core/string')->str_split($this->getSku($item), 25), - 'feed' => 440 + 'feed' => 565, + 'align' => 'right' ); // Custom options @@ -71,17 +70,19 @@ public function draw() $lines[][] = array( 'text' => Mage::helper('core/string')->str_split(strip_tags($option['label']), 70, true, true), 'font' => 'italic', - 'feed' => 60 + 'feed' => 110 ); // draw options value if ($option['value']) { - $_printValue = isset($option['print_value']) ? $option['print_value'] : strip_tags($option['value']); + $_printValue = isset($option['print_value']) + ? $option['print_value'] + : strip_tags($option['value']); $values = explode(', ', $_printValue); foreach ($values as $value) { $lines[][] = array( 'text' => Mage::helper('core/string')->str_split($value, 50, true, true), - 'feed' => 65 + 'feed' => 115 ); } } @@ -90,7 +91,7 @@ public function draw() $lineBlock = array( 'lines' => $lines, - 'height' => 10 + 'height' => 20 ); $page = $pdf->drawLineBlocks($page, array($lineBlock), array('table_header' => true)); diff --git a/app/code/core/Mage/Sales/Model/Order/Pdf/Shipment.php b/app/code/core/Mage/Sales/Model/Order/Pdf/Shipment.php index 84eded40d3..af39db094f 100644 --- a/app/code/core/Mage/Sales/Model/Order/Pdf/Shipment.php +++ b/app/code/core/Mage/Sales/Model/Order/Pdf/Shipment.php @@ -24,7 +24,6 @@ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ - /** * Sales Order Shipment PDF model * @@ -34,6 +33,56 @@ */ class Mage_Sales_Model_Order_Pdf_Shipment extends Mage_Sales_Model_Order_Pdf_Abstract { + /** + * Draw table header for product items + * + * @param Zend_Pdf_Page $page + * @return void + */ + protected function _drawHeader(Zend_Pdf_Page $page) + { + /* Add table head */ + $this->_setFontRegular($page, 10); + $page->setFillColor(new Zend_Pdf_Color_RGB(0.93, 0.92, 0.92)); + $page->setLineColor(new Zend_Pdf_Color_GrayScale(0.5)); + $page->setLineWidth(0.5); + $page->drawRectangle(25, $this->y, 570, $this->y-15); + $this->y -= 10; + $page->setFillColor(new Zend_Pdf_Color_RGB(0, 0, 0)); + + //columns headers + $lines[0][] = array( + 'text' => Mage::helper('sales')->__('Products'), + 'feed' => 100, + ); + + $lines[0][] = array( + 'text' => Mage::helper('sales')->__('Qty'), + 'feed' => 35 + ); + + $lines[0][] = array( + 'text' => Mage::helper('sales')->__('SKU'), + 'feed' => 565, + 'align' => 'right' + ); + + $lineBlock = array( + 'lines' => $lines, + 'height' => 10 + ); + + $this->drawLineBlocks($page, array($lineBlock), array('table_header' => true)); + $page->setFillColor(new Zend_Pdf_Color_GrayScale(0)); + $this->y -= 20; + } + + /** + * Return PDF document + * + * @param array $shipments + * @return Zend_Pdf + */ public function getPdf($shipments = array()) { $this->_beforeGetPdf(); @@ -48,59 +97,36 @@ public function getPdf($shipments = array()) Mage::app()->getLocale()->emulate($shipment->getStoreId()); Mage::app()->setCurrentStore($shipment->getStoreId()); } - $page = $pdf->newPage(Zend_Pdf_Page::SIZE_A4); - $pdf->pages[] = $page; - + $page = $this->newPage(); $order = $shipment->getOrder(); - /* Add image */ $this->insertLogo($page, $shipment->getStore()); - /* Add address */ $this->insertAddress($page, $shipment->getStore()); - /* Add head */ - $this->insertOrder($page, $shipment, Mage::getStoreConfigFlag(self::XML_PATH_SALES_PDF_SHIPMENT_PUT_ORDER_ID, $order->getStoreId())); - - $page->setFillColor(new Zend_Pdf_Color_GrayScale(1)); - $this->_setFontRegular($page); - $page->drawText(Mage::helper('sales')->__('Packingslip # ') . $shipment->getIncrementId(), 35, 780, 'UTF-8'); - + $this->insertOrder( + $page, + $shipment, + Mage::getStoreConfigFlag(self::XML_PATH_SALES_PDF_SHIPMENT_PUT_ORDER_ID, $order->getStoreId()) + ); + /* Add document text and number */ + $this->insertDocumentNumber( + $page, + Mage::helper('sales')->__('Packingslip # ') . $shipment->getIncrementId() + ); /* Add table */ - $page->setFillColor(new Zend_Pdf_Color_RGB(0.93, 0.92, 0.92)); - $page->setLineColor(new Zend_Pdf_Color_GrayScale(0.5)); - $page->setLineWidth(0.5); - - - /* Add table head */ - $page->drawRectangle(25, $this->y, 570, $this->y-15); - $this->y -=10; - $page->setFillColor(new Zend_Pdf_Color_RGB(0.4, 0.4, 0.4)); - $page->drawText(Mage::helper('sales')->__('Qty'), 35, $this->y, 'UTF-8'); - $page->drawText(Mage::helper('sales')->__('Products'), 60, $this->y, 'UTF-8'); - $page->drawText(Mage::helper('sales')->__('SKU'), 470, $this->y, 'UTF-8'); - - $this->y -=15; - - $page->setFillColor(new Zend_Pdf_Color_GrayScale(0)); - + $this->_drawHeader($page); /* Add body */ - foreach ($shipment->getAllItems() as $item){ + foreach ($shipment->getAllItems() as $item) { if ($item->getOrderItem()->getParentItem()) { continue; } - - if ($this->y<15) { - $page = $this->newPage(array('table_header' => true)); - } - /* Draw item */ - $page = $this->_drawItem($item, $page, $order); + $this->_drawItem($item, $page, $order); + $page = end($pdf->pages); } } - $this->_afterGetPdf(); - if ($shipment->getStoreId()) { Mage::app()->getLocale()->revert(); } @@ -110,7 +136,7 @@ public function getPdf($shipments = array()) /** * Create new page and assign to PDF object * - * @param array $settings + * @param array $settings * @return Zend_Pdf_Page */ public function newPage(array $settings = array()) @@ -119,24 +145,9 @@ public function newPage(array $settings = array()) $page = $this->_getPdf()->newPage(Zend_Pdf_Page::SIZE_A4); $this->_getPdf()->pages[] = $page; $this->y = 800; - if (!empty($settings['table_header'])) { - $this->_setFontRegular($page); - $page->setFillColor(new Zend_Pdf_Color_RGB(0.93, 0.92, 0.92)); - $page->setLineColor(new Zend_Pdf_Color_GrayScale(0.5)); - $page->setLineWidth(0.5); - $page->drawRectangle(25, $this->y, 570, $this->y-15); - $this->y -=10; - - $page->setFillColor(new Zend_Pdf_Color_RGB(0.4, 0.4, 0.4)); - $page->drawText(Mage::helper('sales')->__('Qty'), 35, $this->y, 'UTF-8'); - $page->drawText(Mage::helper('sales')->__('Products'), 60, $this->y, 'UTF-8'); - $page->drawText(Mage::helper('sales')->__('SKU'), 470, $this->y, 'UTF-8'); - - $page->setFillColor(new Zend_Pdf_Color_GrayScale(0)); - $this->y -=20; + $this->_drawHeader($page); } - return $page; } } diff --git a/app/code/core/Mage/Sales/Model/Order/Pdf/Shipment/Packaging.php b/app/code/core/Mage/Sales/Model/Order/Pdf/Shipment/Packaging.php index f2c4e40d7d..e69488135e 100644 --- a/app/code/core/Mage/Sales/Model/Order/Pdf/Shipment/Packaging.php +++ b/app/code/core/Mage/Sales/Model/Order/Pdf/Shipment/Packaging.php @@ -24,7 +24,6 @@ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ - /** * Sales Order Shipment PDF model * @@ -37,7 +36,7 @@ class Mage_Sales_Model_Order_Pdf_Shipment_Packaging extends Mage_Sales_Model_Ord /** * Format pdf file * - * @param null $shipment + * @param null $shipment * @return Zend_Pdf */ public function getPdf($shipment = null) @@ -46,8 +45,8 @@ public function getPdf($shipment = null) $this->_initRenderer('shipment'); $pdf = new Zend_Pdf(); - $page = $pdf->newPage(Zend_Pdf_Page::SIZE_A4); - $pdf->pages[] = $page; + $this->_setPdf($pdf); + $page = $this->newPage(); if ($shipment->getStoreId()) { Mage::app()->getLocale()->emulate($shipment->getStoreId()); @@ -71,7 +70,7 @@ public function getPdf($shipment = null) /** * Draw header block * - * @param Zend_Pdf_Page $page + * @param Zend_Pdf_Page $page * @return Mage_Sales_Model_Order_Pdf_Shipment_Packaging */ protected function _drawHeaderBlock(Zend_Pdf_Page $page) { @@ -89,7 +88,7 @@ protected function _drawHeaderBlock(Zend_Pdf_Page $page) { /** * Draw packages block * - * @param Zend_Pdf_Page $page + * @param Zend_Pdf_Page $page * @return Mage_Sales_Model_Order_Pdf_Shipment_Packaging */ protected function _drawPackageBlock(Zend_Pdf_Page $page) 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 d83a239a67..d92f0d460f 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 @@ -24,6 +24,13 @@ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ +/** + * Sales Order Total PDF model + * + * @category Mage + * @package Mage_Sales + * @author Magento Core Team + */ class Mage_Sales_Model_Order_Pdf_Total_Default extends Varien_Object { /** diff --git a/app/code/core/Mage/Sales/Model/Order/Shipment/Api.php b/app/code/core/Mage/Sales/Model/Order/Shipment/Api.php index 68ada16a84..73673bb8d4 100644 --- a/app/code/core/Mage/Sales/Model/Order/Shipment/Api.php +++ b/app/code/core/Mage/Sales/Model/Order/Shipment/Api.php @@ -131,8 +131,9 @@ public function info($shipmentIncrementId) * @param boolean $includeComment * @return string */ - public function create($orderIncrementId, $itemsQty = array(), $comment = null, $email = false, $includeComment = false) - { + public function create($orderIncrementId, $itemsQty = array(), $comment = null, $email = false, + $includeComment = false + ) { $order = Mage::getModel('sales/order')->loadByIncrementId($orderIncrementId); /** @@ -244,6 +245,39 @@ public function removeTrack($shipmentIncrementId, $trackId) return true; } + /** + * Send email with shipment data to customer + * + * @param string $shipmentIncrementId + * @param string $comment + * @return bool + */ + public function sendInfo($shipmentIncrementId, $comment = '') + { + /* @var $shipment Mage_Sales_Model_Order_Shipment */ + $shipment = Mage::getModel('sales/order_shipment')->loadByIncrementId($shipmentIncrementId); + + if (!$shipment->getId()) { + $this->_fault('not_exists'); + } + + try { + $shipment->sendEmail(true, $comment) + ->setEmailSent(true) + ->save(); + $historyItem = Mage::getResourceModel('sales/order_status_history_collection') + ->getUnnotifiedForInstance($shipment, Mage_Sales_Model_Order_Shipment::HISTORY_ENTITY_NAME); + if ($historyItem) { + $historyItem->setIsCustomerNotified(1); + $historyItem->save(); + } + } catch (Mage_Core_Exception $e) { + $this->_fault('data_invalid', $e->getMessage()); + } + + return true; + } + /** * Retrieve tracking number info * diff --git a/app/code/core/Mage/Sales/Model/Quote/Payment.php b/app/code/core/Mage/Sales/Model/Quote/Payment.php index 796c8aae77..be4db0c55d 100644 --- a/app/code/core/Mage/Sales/Model/Quote/Payment.php +++ b/app/code/core/Mage/Sales/Model/Quote/Payment.php @@ -142,7 +142,7 @@ public function importData(array $data) $method = $this->getMethodInstance(); /** - * Payment avalability related with quote totals. + * Payment availability related with quote totals. * We have recollect quote totals before checking */ $this->getQuote()->collectTotals(); diff --git a/app/code/core/Mage/Sales/Model/Resource/Report/Bestsellers.php b/app/code/core/Mage/Sales/Model/Resource/Report/Bestsellers.php index 25937f694a..10eb1117bb 100755 --- a/app/code/core/Mage/Sales/Model/Resource/Report/Bestsellers.php +++ b/app/code/core/Mage/Sales/Model/Resource/Report/Bestsellers.php @@ -228,9 +228,9 @@ public function aggregate($from = null, $to = null) 'period' => 'period', 'store_id' => new Zend_Db_Expr(Mage_Core_Model_App::ADMIN_STORE_ID), 'product_id' => 'product_id', - 'product_name' => new Zend_Db_expr('MIN(product_name)'), - 'product_price' => new Zend_Db_expr('MIN(product_price)'), - 'qty_ordered' => new Zend_Db_expr('SUM(qty_ordered)'), + 'product_name' => new Zend_Db_Expr('MIN(product_name)'), + 'product_price' => new Zend_Db_Expr('MIN(product_price)'), + 'qty_ordered' => new Zend_Db_Expr('SUM(qty_ordered)'), ); $select->reset(); diff --git a/app/code/core/Mage/Sales/Model/Resource/Report/Invoiced.php b/app/code/core/Mage/Sales/Model/Resource/Report/Invoiced.php index 90c30e8014..813455b0a2 100755 --- a/app/code/core/Mage/Sales/Model/Resource/Report/Invoiced.php +++ b/app/code/core/Mage/Sales/Model/Resource/Report/Invoiced.php @@ -104,13 +104,13 @@ protected function _aggregateByInvoiceCreatedAt($from, $to) 'period' => $periodExpr, 'store_id' => 'order_table.store_id', 'order_status' => 'order_table.status', - 'orders_count' => new Zend_Db_expr('COUNT(order_table.entity_id)'), - 'orders_invoiced' => new Zend_Db_expr('COUNT(order_table.entity_id)'), - 'invoiced' => new Zend_Db_expr('SUM(order_table.base_total_invoiced' + 'orders_count' => new Zend_Db_Expr('COUNT(order_table.entity_id)'), + 'orders_invoiced' => new Zend_Db_Expr('COUNT(order_table.entity_id)'), + 'invoiced' => new Zend_Db_Expr('SUM(order_table.base_total_invoiced' . ' * order_table.base_to_global_rate)'), - 'invoiced_captured' => new Zend_Db_expr('SUM(order_table.base_total_paid' + 'invoiced_captured' => new Zend_Db_Expr('SUM(order_table.base_total_paid' . ' * order_table.base_to_global_rate)'), - 'invoiced_not_captured' => new Zend_Db_expr( + 'invoiced_not_captured' => new Zend_Db_Expr( 'SUM((order_table.base_total_invoiced - order_table.base_total_paid)' . ' * order_table.base_to_global_rate)') ); @@ -151,11 +151,11 @@ protected function _aggregateByInvoiceCreatedAt($from, $to) 'period' => 'period', 'store_id' => new Zend_Db_Expr(Mage_Core_Model_App::ADMIN_STORE_ID), 'order_status' => 'order_status', - 'orders_count' => new Zend_Db_expr('SUM(orders_count)'), - 'orders_invoiced' => new Zend_Db_expr('SUM(orders_invoiced)'), - 'invoiced' => new Zend_Db_expr('SUM(invoiced)'), - 'invoiced_captured' => new Zend_Db_expr('SUM(invoiced_captured)'), - 'invoiced_not_captured' => new Zend_Db_expr('SUM(invoiced_not_captured)') + 'orders_count' => new Zend_Db_Expr('SUM(orders_count)'), + 'orders_invoiced' => new Zend_Db_Expr('SUM(orders_invoiced)'), + 'invoiced' => new Zend_Db_Expr('SUM(invoiced)'), + 'invoiced_captured' => new Zend_Db_Expr('SUM(invoiced_captured)'), + 'invoiced_not_captured' => new Zend_Db_Expr('SUM(invoiced_not_captured)') ); $select @@ -267,11 +267,11 @@ protected function _aggregateByOrderCreatedAt($from, $to) 'period' => 'period', 'store_id' => new Zend_Db_Expr(Mage_Core_Model_App::ADMIN_STORE_ID), 'order_status' => 'order_status', - 'orders_count' => new Zend_Db_expr('SUM(orders_count)'), - 'orders_invoiced' => new Zend_Db_expr('SUM(orders_invoiced)'), - 'invoiced' => new Zend_Db_expr('SUM(invoiced)'), - 'invoiced_captured' => new Zend_Db_expr('SUM(invoiced_captured)'), - 'invoiced_not_captured' => new Zend_Db_expr('SUM(invoiced_not_captured)') + 'orders_count' => new Zend_Db_Expr('SUM(orders_count)'), + 'orders_invoiced' => new Zend_Db_Expr('SUM(orders_invoiced)'), + 'invoiced' => new Zend_Db_Expr('SUM(invoiced)'), + 'invoiced_captured' => new Zend_Db_Expr('SUM(invoiced_captured)'), + 'invoiced_not_captured' => new Zend_Db_Expr('SUM(invoiced_not_captured)') ); $select->from($table, $columns) diff --git a/app/code/core/Mage/Sales/Model/Resource/Report/Order/Createdat.php b/app/code/core/Mage/Sales/Model/Resource/Report/Order/Createdat.php index c21da52fd1..2274cd58bd 100644 --- a/app/code/core/Mage/Sales/Model/Resource/Report/Order/Createdat.php +++ b/app/code/core/Mage/Sales/Model/Resource/Report/Order/Createdat.php @@ -202,8 +202,8 @@ protected function _aggregateByField($aggregationField, $from, $to) $qtyCanceledExpr = $adapter->getIfNullSql('qty_canceled', 0); $cols = array( 'order_id' => 'order_id', - 'total_qty_ordered' => new Zend_Db_expr("SUM(qty_ordered - {$qtyCanceledExpr})"), - 'total_qty_invoiced' => new Zend_Db_expr('SUM(qty_invoiced)'), + 'total_qty_ordered' => new Zend_Db_Expr("SUM(qty_ordered - {$qtyCanceledExpr})"), + 'total_qty_invoiced' => new Zend_Db_Expr('SUM(qty_invoiced)'), ); $selectOrderItem->from($this->getTable('sales/order_item'), $cols) ->where('parent_item_id IS NULL') @@ -230,7 +230,7 @@ protected function _aggregateByField($aggregationField, $from, $to) // setup all columns to select SUM() except period, store_id and order_status foreach ($columns as $k => $v) { - $columns[$k] = new Zend_Db_expr('SUM(' . $k . ')'); + $columns[$k] = new Zend_Db_Expr('SUM(' . $k . ')'); } $columns['period'] = 'period'; $columns['store_id'] = new Zend_Db_Expr(Mage_Core_Model_App::ADMIN_STORE_ID); diff --git a/app/code/core/Mage/Sales/etc/api.xml b/app/code/core/Mage/Sales/etc/api.xml index f4a47abfb6..5f0f65d02a 100644 --- a/app/code/core/Mage/Sales/etc/api.xml +++ b/app/code/core/Mage/Sales/etc/api.xml @@ -92,6 +92,10 @@ Retrieve shipment information sales/order/shipment/info + + Send shipment info + sales/order/shipment/send + Create new shipment for order sales/order/shipment/create @@ -320,6 +324,9 @@ Retrieve shipment info + + Send shipment info + Order invoice diff --git a/app/code/core/Mage/Sales/etc/api2.xml b/app/code/core/Mage/Sales/etc/api2.xml new file mode 100644 index 0000000000..3fceda27f6 --- /dev/null +++ b/app/code/core/Mage/Sales/etc/api2.xml @@ -0,0 +1,304 @@ + + + + + + + Sales + 130 + + + + + sales + 10 + sales/api2_order + Orders + + + 1 + + + 1 + + + + + /orders/:id + entity + + + /orders + collection + + + + Order ID (internal) + Order ID + Order Date + Order Status + Shipping Method + <_payment_method>Payment Method + Base Currency + Order Currency + Store Name + Placed from IP + Store Currency to Order Currency Rate + Subtotal + Subtotal Including Tax + Discount + Grand Total to Be Charged + Grand Total + Shipping Amount + Shipping Including Tax + Shipping Tax + Tax Amount + <_tax_name>Tax Name + <_tax_rate>Tax Rate + Coupon Code + Base Discount + Base Subtotal + Base Shipping + Base Shipping Tax + Base Tax Amount + Total Paid + Base Total Paid + Total Refunded + Base Total Refunded + Base Subtotal Including Tax + Base Total Due + Total Due + Shipping Discount + Base Shipping Discount + Discount Description + Customer Balance + Base Customer Balance + <_gift_message>Gift Message + <_order_comments>Order Comments + Customer ID + + + + + 1 + 1 + 1 + 1 + 1 + <_payment_method>1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + <_tax_name>1 + <_tax_rate>1 + <_gift_message>1 + <_order_comments>1 + + + + + + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + + + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + + + 1 + + + orders + 30 + sales/api2_order_items + sales/order_item + Order Items + + + 1 + + + 1 + + + + Order Item ID + Product and Custom Options Name + Parent Order Item ID + SKU + Price + Price Including Tax + Ordered Qty + Invoiced Qty + Shipped Qty + Canceled Qty + Refunded Qty + Item Subtotal + Item Subtotal Including Tax + Base Price + Original Price + Base Original Price + Base Price Including Tax + Tax Percent + Tax Amount + Base Tax Amount + Discount Amount + Base Discount Amount + Base Item Subtotal + Base Item Subtotal Including Tax + + + + + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + + + + + + /orders/:id/items + collection + + + 1 + + + orders + 40 + sales/api2_order_address + sales/order_address + Order Addresses + + + 1 + + + 1 + + + + + /orders/:order_id/addresses/:address_type + entity + + + /orders/:order_id/addresses + collection + + + 1 + + Customer Last Name + Customer First Name + Customer Middle Name + Customer Prefix + Customer Suffix + Company + Street + City + State + ZIP/Postal Code + Country + Phone Number + Address Type + Email + + + + orders + 60 + sales/api2_order_comments + sales/order_status_history + Order Comments + + + 1 + + + 1 + + + + + 1 + 1 + 1 + 1 + 1 + + + 1 + 1 + + + + + /orders/:id/comments + collection + + + 1 + + + + diff --git a/app/code/core/Mage/Sales/etc/system.xml b/app/code/core/Mage/Sales/etc/system.xml index 0622812922..4f5dd1c623 100644 --- a/app/code/core/Mage/Sales/etc/system.xml +++ b/app/code/core/Mage/Sales/etc/system.xml @@ -148,7 +148,7 @@ 1 1 1 - Default logo, will be used in PDF and HTML documents.<br />(jpeg, tiff, png) + Default logo, will be used in PDF and HTML documents.<br />(jpeg, tiff, png) If you see image distortion in PDF, try to use larger image diff --git a/app/code/core/Mage/Sales/etc/wsdl.xml b/app/code/core/Mage/Sales/etc/wsdl.xml index 9c14c2e519..969eeb42c1 100644 --- a/app/code/core/Mage/Sales/etc/wsdl.xml +++ b/app/code/core/Mage/Sales/etc/wsdl.xml @@ -825,7 +825,7 @@ - + @@ -847,6 +847,14 @@ + + + + + + + + @@ -885,7 +893,7 @@ - + @@ -921,7 +929,7 @@ - + @@ -1017,6 +1025,11 @@ + + Send shipment info + + + Remove tracking number @@ -1189,6 +1202,15 @@ + + + + + + + + + diff --git a/app/code/core/Mage/Sales/etc/wsi.xml b/app/code/core/Mage/Sales/etc/wsi.xml index afb499ffe6..5ed29468d1 100644 --- a/app/code/core/Mage/Sales/etc/wsi.xml +++ b/app/code/core/Mage/Sales/etc/wsi.xml @@ -907,6 +907,22 @@ + + + + + + + + + + + + + + + + @@ -1209,6 +1225,12 @@ + + + + + + @@ -1348,6 +1370,11 @@ + + Send shipment info + + + Retrieve list of allowed carriers for order @@ -1524,6 +1551,15 @@ + + + + + + + + + diff --git a/app/code/core/Mage/Sales/sql/sales_setup/mysql4-upgrade-0.9.26-0.9.27.php b/app/code/core/Mage/Sales/sql/sales_setup/mysql4-upgrade-0.9.26-0.9.27.php index f5d066c3b8..efec25d2c8 100644 --- a/app/code/core/Mage/Sales/sql/sales_setup/mysql4-upgrade-0.9.26-0.9.27.php +++ b/app/code/core/Mage/Sales/sql/sales_setup/mysql4-upgrade-0.9.26-0.9.27.php @@ -29,11 +29,16 @@ $installer = $this; /* @var $installer Mage_Sales_Model_Mysql4_Setup */ -$installer->getConnection()->beginTransaction(); -$installer->getConnection()->addColumn($this->getTable('sales/quote'), 'global_currency_code', 'varchar(255) NULL AFTER `store_to_quote_rate`'); -$installer->getConnection()->addColumn($this->getTable('sales/quote'), 'base_to_quote_rate', 'decimal(12,4) NULL AFTER `store_to_quote_rate`'); -$installer->getConnection()->addColumn($this->getTable('sales/quote'), 'base_to_global_rate', 'decimal(12,4) NULL AFTER `store_to_quote_rate`'); +$installer->getConnection()->addColumn( + $this->getTable('sales/quote'), 'global_currency_code', 'varchar(255) NULL AFTER `store_to_quote_rate`' +); +$installer->getConnection()->addColumn( + $this->getTable('sales/quote'), 'base_to_quote_rate', 'decimal(12,4) NULL AFTER `store_to_quote_rate`' +); +$installer->getConnection()->addColumn( + $this->getTable('sales/quote'), 'base_to_global_rate', 'decimal(12,4) NULL AFTER `store_to_quote_rate`' +); $installer->addAttribute('quote', 'global_currency_code', array('type'=>'static')); $installer->addAttribute('quote', 'base_to_global_rate', array('type'=>'static')); @@ -66,100 +71,110 @@ $invoiceEntityType['entity_type_id'] => $invoiceEntityType, $creditmemoEntityType['entity_type_id'] => $creditmemoEntityType); -foreach ($entityTypes as $typeId => $entity) { - - $globalCurrencyCode = $installer->getAttribute($typeId, 'global_currency_code'); - if ($globalCurrencyCode['backend_type'] == 'static') { - $globalCurrencyCodeTable = $this->getTable($entity['entity_table']); - } else { - $globalCurrencyCodeTable = $this->getTable($entity['entity_table']) . '_' . $globalCurrencyCode['backend_type']; - } - - $baseCurrencyCode = $installer->getAttribute($typeId, 'base_currency_code'); - if ($baseCurrencyCode['backend_type'] == 'static') { - $baseCurrencyCodeTable = $this->getTable($entity['entity_table']); - } else { - $baseCurrencyCodeTable = $this->getTable($entity['entity_table']) . '_' . $baseCurrencyCode['backend_type']; - } - - $storeCurrencyCode = $installer->getAttribute($typeId, 'store_currency_code'); - if ($storeCurrencyCode['backend_type'] == 'static') { - $storeCurrencyCodeTable = $this->getTable($entity['entity_table']); - } else { - $storeCurrencyCodeTable = $this->getTable($entity['entity_table']) . '_' . $storeCurrencyCode['backend_type']; - } - - $baseToGlobalRate = $installer->getAttribute($typeId, 'base_to_global_rate'); - if ($baseToGlobalRate['backend_type'] == 'static') { - $baseToGlobalRateTable = $this->getTable($entity['entity_table']); - } else { - $baseToGlobalRateTable = $this->getTable($entity['entity_table']) . '_' . $baseToGlobalRate['backend_type']; - } - - $storeToBaseRate = $installer->getAttribute($typeId, 'store_to_base_rate'); - if ($storeToBaseRate['backend_type'] == 'static') { - $storeToBaseRateTable = $this->getTable($entity['entity_table']); - } else { - $storeToBaseRateTable = $this->getTable($entity['entity_table']) . '_' . $storeToBaseRate['backend_type']; - } - - $baseToOrderRate = $installer->getAttribute($typeId, 'base_to_order_rate'); - if ($baseToOrderRate['backend_type'] == 'static') { - $baseToOrderRateTable = $this->getTable($entity['entity_table']); - } else { - $baseToOrderRateTable = $this->getTable($entity['entity_table']) . '_' . $baseToOrderRate['backend_type']; - } - - $storeToOrderRate = $installer->getAttribute($typeId, 'store_to_order_rate'); - if ($storeToOrderRate['backend_type'] == 'static') { - $storeToOrderRateTable = $this->getTable($entity['entity_table']); - } else { - $storeToOrderRateTable = $this->getTable($entity['entity_table']) . '_' . $storeToOrderRate['backend_type']; +try { + $installer->getConnection()->beginTransaction(); + + foreach ($entityTypes as $typeId => $entity) { + + $globalCurrencyCode = $installer->getAttribute($typeId, 'global_currency_code'); + if ($globalCurrencyCode['backend_type'] == 'static') { + $globalCurrencyCodeTable = $this->getTable($entity['entity_table']); + } else { + $globalCurrencyCodeTable = $this->getTable($entity['entity_table']) . '_' + . $globalCurrencyCode['backend_type']; + } + + $baseCurrencyCode = $installer->getAttribute($typeId, 'base_currency_code'); + if ($baseCurrencyCode['backend_type'] == 'static') { + $baseCurrencyCodeTable = $this->getTable($entity['entity_table']); + } else { + $baseCurrencyCodeTable = $this->getTable($entity['entity_table']) . '_' + . $baseCurrencyCode['backend_type']; + } + + $storeCurrencyCode = $installer->getAttribute($typeId, 'store_currency_code'); + if ($storeCurrencyCode['backend_type'] == 'static') { + $storeCurrencyCodeTable = $this->getTable($entity['entity_table']); + } else { + $storeCurrencyCodeTable = $this->getTable($entity['entity_table']) . '_' + . $storeCurrencyCode['backend_type']; + } + + $baseToGlobalRate = $installer->getAttribute($typeId, 'base_to_global_rate'); + if ($baseToGlobalRate['backend_type'] == 'static') { + $baseToGlobalRateTable = $this->getTable($entity['entity_table']); + } else { + $baseToGlobalRateTable = $this->getTable($entity['entity_table']) . '_' . $baseToGlobalRate['backend_type']; + } + + $storeToBaseRate = $installer->getAttribute($typeId, 'store_to_base_rate'); + if ($storeToBaseRate['backend_type'] == 'static') { + $storeToBaseRateTable = $this->getTable($entity['entity_table']); + } else { + $storeToBaseRateTable = $this->getTable($entity['entity_table']) . '_' . $storeToBaseRate['backend_type']; + } + + $baseToOrderRate = $installer->getAttribute($typeId, 'base_to_order_rate'); + if ($baseToOrderRate['backend_type'] == 'static') { + $baseToOrderRateTable = $this->getTable($entity['entity_table']); + } else { + $baseToOrderRateTable = $this->getTable($entity['entity_table']) . '_' . $baseToOrderRate['backend_type']; + } + + $storeToOrderRate = $installer->getAttribute($typeId, 'store_to_order_rate'); + if ($storeToOrderRate['backend_type'] == 'static') { + $storeToOrderRateTable = $this->getTable($entity['entity_table']); + } else { + $storeToOrderRateTable = $this->getTable($entity['entity_table']) . '_' . $storeToOrderRate['backend_type']; + } + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + //copy data from base_currency_code into global_currency_code + $query = 'INSERT INTO `' . $globalCurrencyCodeTable . + '` (`entity_type_id`, `attribute_id`, `entity_id`, `value`) SELECT `entity_type_id`, "' . + $globalCurrencyCode['attribute_id'] . '" as `attribute_id`, `entity_id`, `value` FROM `' . + $baseCurrencyCodeTable . '` WHERE `attribute_id` = ' . $baseCurrencyCode['attribute_id'] . ';'; + + //echo $query . "
    "; + $installer->getConnection()->query($query); + + //delete old data in base_currency_code + $query = 'DELETE FROM `' . $baseCurrencyCodeTable . '` WHERE `attribute_id` = ' + . $baseCurrencyCode['attribute_id'] . ';'; + + //echo $query . "
    "; + $installer->getConnection()->query($query); + + //copy data from store_currency_code into base_currency_code + $query = 'INSERT INTO `' . $baseCurrencyCodeTable . + '` (`entity_type_id`, `attribute_id`, `entity_id`, `value`) SELECT `entity_type_id`, "' . + $baseCurrencyCode['attribute_id'] . '" as `attribute_id`, `entity_id`, `value` FROM `' . + $storeCurrencyCodeTable . '` WHERE `attribute_id` = ' . $storeCurrencyCode['attribute_id'] . ';'; + + //echo $query . "
    "; + $installer->getConnection()->query($query); + + //copy data from store_to_base_rate into base_to_global_rate + $query = 'INSERT INTO `' . $baseToGlobalRateTable . + '` (`entity_type_id`, `attribute_id`, `entity_id`, `value`) SELECT `entity_type_id`, "' . + $baseToGlobalRate['attribute_id'] . '" as `attribute_id`, `entity_id`, `value` FROM `' . + $storeToBaseRateTable . '` WHERE `attribute_id` = ' . $storeToBaseRate['attribute_id'] . ';'; + + //echo $query . "
    "; + $installer->getConnection()->query($query); + + //copy data from store_to_order_rate into base_to_order_rate + $query = 'INSERT INTO `' . $baseToOrderRateTable . + '` (`entity_type_id`, `attribute_id`, `entity_id`, `value`) SELECT `entity_type_id`, "' . + $baseToOrderRate['attribute_id'] . '" as `attribute_id`, `entity_id`, `value` FROM `' . + $storeToOrderRateTable . '` WHERE `attribute_id` = ' . $storeToOrderRate['attribute_id'] . ';'; + + //echo $query . "
    "; + $installer->getConnection()->query($query); } - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - //copy data from base_currency_code into global_currency_code - $query = 'INSERT INTO `' . $globalCurrencyCodeTable . - '` (`entity_type_id`, `attribute_id`, `entity_id`, `value`) SELECT `entity_type_id`, "' . - $globalCurrencyCode['attribute_id'] . '" as `attribute_id`, `entity_id`, `value` FROM `' . - $baseCurrencyCodeTable . '` WHERE `attribute_id` = ' . $baseCurrencyCode['attribute_id'] . ';'; - - //echo $query . "
    "; - $installer->getConnection()->query($query); - - //delete old data in base_currency_code - $query = 'DELETE FROM `' . $baseCurrencyCodeTable . '` WHERE `attribute_id` = ' . $baseCurrencyCode['attribute_id'] . ';'; - - //echo $query . "
    "; - $installer->getConnection()->query($query); - - //copy data from store_currency_code into base_currency_code - $query = 'INSERT INTO `' . $baseCurrencyCodeTable . - '` (`entity_type_id`, `attribute_id`, `entity_id`, `value`) SELECT `entity_type_id`, "' . - $baseCurrencyCode['attribute_id'] . '" as `attribute_id`, `entity_id`, `value` FROM `' . - $storeCurrencyCodeTable . '` WHERE `attribute_id` = ' . $storeCurrencyCode['attribute_id'] . ';'; - - //echo $query . "
    "; - $installer->getConnection()->query($query); - - //copy data from store_to_base_rate into base_to_global_rate - $query = 'INSERT INTO `' . $baseToGlobalRateTable . - '` (`entity_type_id`, `attribute_id`, `entity_id`, `value`) SELECT `entity_type_id`, "' . - $baseToGlobalRate['attribute_id'] . '" as `attribute_id`, `entity_id`, `value` FROM `' . - $storeToBaseRateTable . '` WHERE `attribute_id` = ' . $storeToBaseRate['attribute_id'] . ';'; - - //echo $query . "
    "; - $installer->getConnection()->query($query); - - //copy data from store_to_order_rate into base_to_order_rate - $query = 'INSERT INTO `' . $baseToOrderRateTable . - '` (`entity_type_id`, `attribute_id`, `entity_id`, `value`) SELECT `entity_type_id`, "' . - $baseToOrderRate['attribute_id'] . '" as `attribute_id`, `entity_id`, `value` FROM `' . - $storeToOrderRateTable . '` WHERE `attribute_id` = ' . $storeToOrderRate['attribute_id'] . ';'; - - //echo $query . "
    "; - $installer->getConnection()->query($query); + $installer->getConnection()->commit(); +} catch(Exception $e) { + $installer->getConnection()->rollBack(); + throw $e; } - -$installer->getConnection()->commit(); diff --git a/app/code/core/Mage/SalesRule/etc/adminhtml.xml b/app/code/core/Mage/SalesRule/etc/adminhtml.xml index 640138f558..d814bc4190 100644 --- a/app/code/core/Mage/SalesRule/etc/adminhtml.xml +++ b/app/code/core/Mage/SalesRule/etc/adminhtml.xml @@ -48,19 +48,19 @@ - - - - - - Shopping Cart Price Rules - - - - - + + + + + + Shopping Cart Price Rules + + + + + - + diff --git a/app/code/core/Mage/Shipping/Model/Carrier/Abstract.php b/app/code/core/Mage/Shipping/Model/Carrier/Abstract.php index ea5ff0b909..d69cf15f09 100644 --- a/app/code/core/Mage/Shipping/Model/Carrier/Abstract.php +++ b/app/code/core/Mage/Shipping/Model/Carrier/Abstract.php @@ -556,7 +556,7 @@ public function getDebugFlag() } /** - * Used to call debug method from not Paymant Method context + * Used to call debug method from not Payment Method context * * @param mixed $debugData */ diff --git a/app/code/core/Mage/Sitemap/etc/system.xml b/app/code/core/Mage/Sitemap/etc/system.xml index 50e6b07c68..be560a6efc 100644 --- a/app/code/core/Mage/Sitemap/etc/system.xml +++ b/app/code/core/Mage/Sitemap/etc/system.xml @@ -143,6 +143,7 @@ text + validate-email 5 1 1 diff --git a/app/code/core/Mage/Tag/Model/Api/V2.php b/app/code/core/Mage/Tag/Model/Api/V2.php index 454b8a58fd..129bad78e2 100644 --- a/app/code/core/Mage/Tag/Model/Api/V2.php +++ b/app/code/core/Mage/Tag/Model/Api/V2.php @@ -46,7 +46,7 @@ public function items($productId, $store) foreach ($result as $key => $tag) { $result[$key] = Mage::helper('api')->wsiArrayPacker($tag); } - return $result; + return array_values($result); } /** diff --git a/app/code/core/Mage/Tag/Model/Resource/Popular/Collection.php b/app/code/core/Mage/Tag/Model/Resource/Popular/Collection.php index a466ef3eae..3f2d02dba9 100755 --- a/app/code/core/Mage/Tag/Model/Resource/Popular/Collection.php +++ b/app/code/core/Mage/Tag/Model/Resource/Popular/Collection.php @@ -105,4 +105,22 @@ public function limit($limit) $this->getSelect()->limit($limit); return $this; } + + /** + * Get SQL for get record count + * + * @return Varien_Db_Select + */ + public function getSelectCountSql() + { + $this->_renderFilters(); + $select = clone $this->getSelect(); + $select->reset(Zend_Db_Select::ORDER); + $select->reset(Zend_Db_Select::LIMIT_COUNT); + $select->reset(Zend_Db_Select::LIMIT_OFFSET); + + $countSelect = $this->getConnection()->select(); + $countSelect->from(array('a' => $select), 'COUNT(popularity)'); + return $countSelect; + } } diff --git a/app/code/core/Mage/Tax/Model/Calculation/Rate.php b/app/code/core/Mage/Tax/Model/Calculation/Rate.php index e0c8f3ded0..06182a451b 100644 --- a/app/code/core/Mage/Tax/Model/Calculation/Rate.php +++ b/app/code/core/Mage/Tax/Model/Calculation/Rate.php @@ -71,10 +71,9 @@ protected function _construct() protected function _beforeSave() { if ($this->getZipIsRange()) { - $zipFrom = (strlen($this->getZipFrom()) > 10) ? substr($this->getZipFrom(), 0, 10) : $this->getZipFrom(); - $zipTo = (strlen($this->getZipTo()) > 10) ? substr($this->getZipTo(), 0, 10) : $this->getZipTo(); - - $this->setTaxPostcode("{$zipFrom}-{$zipTo}"); + $zipFrom = substr($this->getZipFrom(), 0, 9); + $zipTo = substr($this->getZipTo(), 0, 9); + $this->setTaxPostcode($zipFrom . '-' . $zipTo); } else { $taxPostCode = $this->getTaxPostcode(); diff --git a/app/code/core/Mage/Tax/Model/Resource/Calculation.php b/app/code/core/Mage/Tax/Model/Resource/Calculation.php index 2a38f6cd18..1d83d49e87 100755 --- a/app/code/core/Mage/Tax/Model/Resource/Calculation.php +++ b/app/code/core/Mage/Tax/Model/Resource/Calculation.php @@ -415,7 +415,7 @@ public function getRatesByCustomerTaxClass($customerTaxClass, $productTaxClass = $adapter->quoteInto('calc_table.customer_tax_class_id = ?', $customerTaxClassId), ); - if ($productTaxClass) { + if ($productTaxClass !== null) { $productTaxClassId = (int)$productTaxClass; $calcJoinConditions[] = $adapter->quoteInto('calc_table.product_tax_class_id = ?', $productTaxClassId); } @@ -426,13 +426,13 @@ public function getRatesByCustomerTaxClass($customerTaxClass, $productTaxClass = array('main_table' => $this->getTable('tax/tax_calculation_rate')), array('country' => 'tax_country_id', 'region_id' => 'tax_region_id', 'postcode' => 'tax_postcode')) ->joinInner( - array('calc_table' => $this->getTable('tax/tax_calculation')), - implode(' AND ', $calcJoinConditions), - array('product_class' => 'calc_table.product_tax_class_id')) + array('calc_table' => $this->getTable('tax/tax_calculation')), + implode(' AND ', $calcJoinConditions), + array('product_class' => 'calc_table.product_tax_class_id')) ->joinLeft( - array('state_table' => $this->getTable('directory/country_region')), - 'state_table.region_id = main_table.tax_region_id', - array('region_code' => 'state_table.code')) + array('state_table' => $this->getTable('directory/country_region')), + 'state_table.region_id = main_table.tax_region_id', + array('region_code' => 'state_table.code')) ->distinct(true); $CSP = $adapter->fetchAll($selectCSP); diff --git a/app/code/core/Mage/Usa/Helper/Data.php b/app/code/core/Mage/Usa/Helper/Data.php index 8818068929..615f743378 100644 --- a/app/code/core/Mage/Usa/Helper/Data.php +++ b/app/code/core/Mage/Usa/Helper/Data.php @@ -109,26 +109,27 @@ public function getMeasureDimensionName($key) */ public function displayGirthValue($shippingMethod) { - if (in_array($shippingMethod, array('usps_Priority Mail International', - 'usps_Priority Mail International Small Flat Rate Box', - 'usps_Priority Mail International Medium Flat Rate Box', - 'usps_Priority Mail International Large Flat Rate Box', - 'usps_Priority Mail International Flat Rate Envelope', - 'usps_Express Mail International Flat Rate Envelope', - 'usps_Express Mail Hold For Pickup', - 'usps_Express Mail International', - 'usps_First-Class Mail International Package', - 'usps_First-Class Mail International Large Envelope', - 'usps_First-Class Mail International', - 'usps_Global Express Guaranteed (GXG)', - 'usps_USPS GXG Envelopes', - 'usps_Global Express Guaranteed Non-Document Non-Rectangular', - 'usps_Media Mail', - 'usps_Parcel Post', - 'usps_Express Mail', - 'usps_Priority Mail' - ) - )) { + if (in_array($shippingMethod, array( + 'usps_Priority Mail International', + 'usps_Priority Mail International Small Flat Rate Box', + 'usps_Priority Mail International Medium Flat Rate Box', + 'usps_Priority Mail International Large Flat Rate Box', + 'usps_Priority Mail International Flat Rate Envelope', + 'usps_Express Mail International Flat Rate Envelope', + 'usps_Express Mail Hold For Pickup', + 'usps_Express Mail International', + 'usps_First-Class Mail International Package', + 'usps_First-Class Mail International Parcel', + 'usps_First-Class Mail International Large Envelope', + 'usps_First-Class Mail International', + 'usps_Global Express Guaranteed (GXG)', + 'usps_USPS GXG Envelopes', + 'usps_Global Express Guaranteed Non-Document Non-Rectangular', + 'usps_Media Mail', + 'usps_Parcel Post', + 'usps_Express Mail', + 'usps_Priority Mail' + ))) { return true; } else { return false; diff --git a/app/code/core/Mage/Usa/Model/Shipping/Carrier/Dhl/International.php b/app/code/core/Mage/Usa/Model/Shipping/Carrier/Dhl/International.php index dc49ba2328..6aed24fbd4 100644 --- a/app/code/core/Mage/Usa/Model/Shipping/Carrier/Dhl/International.php +++ b/app/code/core/Mage/Usa/Model/Shipping/Carrier/Dhl/International.php @@ -153,6 +153,13 @@ class Mage_Usa_Model_Shipping_Carrier_Dhl_International 'contentdesc' => array('code' => 'dhl_content_desc', 'setCode' => 'content_desc') ); + /** + * Flag that shows if shipping is domestic + * + * @var bool + */ + protected $_isDomestic = false; + /** * Dhl International Class constructor * @@ -381,16 +388,21 @@ public function getAllowedMethods() { $contentType = $this->getConfigData('content_type'); $allowedMethods = array(); - switch ($contentType) { - case self::DHL_CONTENT_TYPE_DOC: - $allowedMethods = explode(',', $this->getConfigData('doc_methods')); - break; - - case self::DHL_CONTENT_TYPE_NON_DOC: - $allowedMethods = explode(',', $this->getConfigData('nondoc_methods')); - break; - default: - Mage::throwException(Mage::helper('usa')->__('Wrong Content Type.')); + if ($this->_isDomestic) { + $allowedMethods = array_merge(explode(',', $this->getConfigData('doc_methods')), + explode(',', $this->getConfigData('nondoc_methods')) + ); + } else { + switch ($contentType) { + case self::DHL_CONTENT_TYPE_DOC: + $allowedMethods = explode(',', $this->getConfigData('doc_methods')); + break; + case self::DHL_CONTENT_TYPE_NON_DOC: + $allowedMethods = explode(',', $this->getConfigData('nondoc_methods')); + break; + default: + Mage::throwException(Mage::helper('usa')->__('Wrong Content Type.')); + } } $methods = array(); foreach ($allowedMethods as $method) { @@ -469,47 +481,54 @@ public function getCode($type, $code = '') */ public function getDhlProducts($doc) { + $docType = array( + '2' => Mage::helper('usa')->__('Easy shop'), + '5' => Mage::helper('usa')->__('Sprintline'), + '6' => Mage::helper('usa')->__('Secureline'), + '7' => Mage::helper('usa')->__('Express easy'), + '9' => Mage::helper('usa')->__('Europack'), + 'B' => Mage::helper('usa')->__('Break bulk express'), + 'C' => Mage::helper('usa')->__('Medical express'), + 'D' => Mage::helper('usa')->__('Express worldwide'), // product content code: DOX + 'U' => Mage::helper('usa')->__('Express worldwide'), // product content code: ECX + 'K' => Mage::helper('usa')->__('Express 9:00'), + 'L' => Mage::helper('usa')->__('Express 10:30'), + 'G' => Mage::helper('usa')->__('Domestic economy select'), + 'W' => Mage::helper('usa')->__('Economy select'), + 'I' => Mage::helper('usa')->__('Break bulk economy'), + 'N' => Mage::helper('usa')->__('Domestic express'), + 'O' => Mage::helper('usa')->__('Others'), + 'R' => Mage::helper('usa')->__('Globalmail business'), + 'S' => Mage::helper('usa')->__('Same day'), + 'T' => Mage::helper('usa')->__('Express 12:00'), + 'X' => Mage::helper('usa')->__('Express envelope'), + ); + + $nonDocType = array( + '1' => Mage::helper('usa')->__('Customer services'), + '3' => Mage::helper('usa')->__('Easy shop'), + '4' => Mage::helper('usa')->__('Jetline'), + '8' => Mage::helper('usa')->__('Express easy'), + 'P' => Mage::helper('usa')->__('Express worldwide'), + 'Q' => Mage::helper('usa')->__('Medical express'), + 'E' => Mage::helper('usa')->__('Express 9:00'), + 'F' => Mage::helper('usa')->__('Freight worldwide'), + 'H' => Mage::helper('usa')->__('Economy select'), + 'J' => Mage::helper('usa')->__('Jumbo box'), + 'M' => Mage::helper('usa')->__('Express 10:30'), + 'V' => Mage::helper('usa')->__('Europack'), + 'Y' => Mage::helper('usa')->__('Express 12:00'), + ); + + if ($this->_isDomestic) { + return $docType + $nonDocType; + } if ($doc == self::DHL_CONTENT_TYPE_DOC) { // Documents shipping - return array( - '2' => Mage::helper('usa')->__('Easy shop'), - '5' => Mage::helper('usa')->__('Sprintline'), - '6' => Mage::helper('usa')->__('Secureline'), - '7' => Mage::helper('usa')->__('Express easy'), - '9' => Mage::helper('usa')->__('Europack'), - 'B' => Mage::helper('usa')->__('Break bulk express'), - 'C' => Mage::helper('usa')->__('Medical express'), - 'D' => Mage::helper('usa')->__('Express worldwide'), // product content code: DOX - 'U' => Mage::helper('usa')->__('Express worldwide'), // product content code: ECX - 'K' => Mage::helper('usa')->__('Express 9:00'), - 'L' => Mage::helper('usa')->__('Express 10:30'), - 'G' => Mage::helper('usa')->__('Domestic economy select'), - 'W' => Mage::helper('usa')->__('Economy select'), - 'I' => Mage::helper('usa')->__('Break bulk economy'), - 'N' => Mage::helper('usa')->__('Domestic express'), - 'O' => Mage::helper('usa')->__('Others'), - 'R' => Mage::helper('usa')->__('Globalmail business'), - 'S' => Mage::helper('usa')->__('Same day'), - 'T' => Mage::helper('usa')->__('Express 12:00'), - 'X' => Mage::helper('usa')->__('Express envelope'), - ); + return $docType; } else { // Services for shipping non-documents cargo - return array( - '1' => Mage::helper('usa')->__('Customer services'), - '3' => Mage::helper('usa')->__('Easy shop'), - '4' => Mage::helper('usa')->__('Jetline'), - '8' => Mage::helper('usa')->__('Express easy'), - 'P' => Mage::helper('usa')->__('Express worldwide'), - 'Q' => Mage::helper('usa')->__('Medical express'), - 'E' => Mage::helper('usa')->__('Express 9:00'), - 'F' => Mage::helper('usa')->__('Freight worldwide'), - 'H' => Mage::helper('usa')->__('Economy select'), - 'J' => Mage::helper('usa')->__('Jumbo box'), - 'M' => Mage::helper('usa')->__('Express 10:30'), - 'V' => Mage::helper('usa')->__('Europack'), - 'Y' => Mage::helper('usa')->__('Express 12:00'), - ); + return $nonDocType; } } @@ -810,7 +829,9 @@ protected function _getQuotes() $nodeTo->addChild('Postalcode', $rawRequest->getDestPostal()); $nodeTo->addChild('City', $rawRequest->getDestCity()); - if ($this->getConfigData('content_type') == self::DHL_CONTENT_TYPE_NON_DOC) { + $this->_checkDomesticStatus($rawRequest->getOrigCountryId(), $rawRequest->getDestCountryId()); + + if ($this->getConfigData('content_type') == self::DHL_CONTENT_TYPE_NON_DOC && !$this->_isDomestic) { // IsDutiable flag and Dutiable node indicates that cargo is not a documentation $nodeBkgDetails->addChild('IsDutiable', 'Y'); $nodeDutiable = $nodeGetQuote->addChild('Dutiable'); @@ -1079,10 +1100,17 @@ protected function _doShipmentRequest(Varien_Object $request) public function proccessAdditionalValidation(Mage_Shipping_Model_Rate_Request $request) { //Skip by item validation if there is no items in request - if(!count($this->getAllItems($request))) { + if (!count($this->getAllItems($request))) { $this->_errors[] = Mage::helper('usa')->__('There is no items in this order'); } + $countryParams = $this->getCountryParams( + Mage::getStoreConfig(Mage_Shipping_Model_Shipping::XML_PATH_STORE_COUNTRY_ID, $this->getStore()) + ); + if (!$countryParams->getData()) { + $this->_errors[] = Mage::helper('usa')->__('Please, specify origin country'); + } + if (!empty($this->_errors)) { return $this->_showError(); } @@ -1277,8 +1305,12 @@ protected function _doRequest() $nodeCommodity = $xml->addChild('Commodity', '', ''); $nodeCommodity->addChild('CommodityCode', '1'); + $this->_checkDomesticStatus($rawRequest->getShipperAddressCountryCode(), + $rawRequest->getRecipientAddressCountryCode() + ); + /* Dutiable */ - if ($this->getConfigData('content_type') == self::DHL_CONTENT_TYPE_NON_DOC) { + if ($this->getConfigData('content_type') == self::DHL_CONTENT_TYPE_NON_DOC && !$this->_isDomestic) { $nodeDutiable = $xml->addChild('Dutiable', '', ''); $nodeDutiable->addChild('DeclaredValue', sprintf("%.2F", $rawRequest->getOrderShipment()->getOrder()->getSubtotal()) @@ -1678,4 +1710,26 @@ public function requestToShipment(Mage_Shipping_Model_Shipment_Request $request) return $response; } + + /** + * Check if shipping is domestic + * + * @param string $origCountryCode + * @param string $destCountryCode + * @return bool + */ + protected function _checkDomesticStatus($origCountryCode, $destCountryCode) + { + $this->_isDomestic = false; + + $origCountry = (string)$this->getCountryParams($origCountryCode)->name; + $destCountry = (string)$this->getCountryParams($destCountryCode)->name; + $isDomestic = (string)$this->getCountryParams($destCountryCode)->domestic; + + if ($origCountry == $destCountry && $isDomestic) { + $this->_isDomestic = true; + } + + return $this->_isDomestic; + } } 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 afc97a8200..604f1fadb0 100644 --- a/app/code/core/Mage/Usa/Model/Shipping/Carrier/Fedex.php +++ b/app/code/core/Mage/Usa/Model/Shipping/Carrier/Fedex.php @@ -43,6 +43,20 @@ class Mage_Usa_Model_Shipping_Carrier_Fedex */ const CODE = 'fedex'; + /** + * Purpose of rate request + * + * @var string + */ + const RATE_REQUEST_GENERAL = 'general'; + + /** + * Purpose of rate request + * + * @var string + */ + const RATE_REQUEST_SMARTPOST = 'SMART_POST'; + /** * Code of the carrier * @@ -167,10 +181,9 @@ public function collectRates(Mage_Shipping_Model_Rate_Request $request) if (!$this->getConfigFlag($this->_activeFlag)) { return false; } - $this->setRequest($request); - $this->_result = $this->_getQuotes(); + $this->_getQuotes(); $this->_updateFreeMethodQuote($request); @@ -294,11 +307,12 @@ public function getVersionInfo() } /** - * Do remote request for and handle errors + * Forming request for rate estimation depending to the purpose * - * @return Mage_Shipping_Model_Rate_Result + * @param string $purpose + * @return array */ - protected function _getQuotes() + protected function _formRateRequest($purpose) { $r = $this->_rawRequest; $ratesRequest = array( @@ -356,15 +370,37 @@ protected function _getQuotes() 'Value' => (float)$r->getWeight(), 'Units' => 'LB' ), - 'InsuredValue' => array( - 'Amount' => $r->getValue(), - 'Currency' => $this->getCurrencyCode() - ), 'GroupPackageCount' => 1, ) ) ) ); + + if ($purpose == self::RATE_REQUEST_GENERAL) { + $ratesRequest['RequestedShipment']['RequestedPackageLineItems'][0]['InsuredValue'] = array( + 'Amount' => $r->getValue(), + 'Currency' => $this->getCurrencyCode() + ); + } else if ($purpose == self::RATE_REQUEST_SMARTPOST) { + $ratesRequest['RequestedShipment']['ServiceType'] = self::RATE_REQUEST_SMARTPOST; + $ratesRequest['RequestedShipment']['SmartPostDetail'] = array( + 'Indicia' => ((float)$r->getWeight() >= 1) ? 'PARCEL_SELECT' : 'PRESORTED_STANDARD', + 'HubId' => $this->getConfigData('smartpost_hubid') + ); + } + + return $ratesRequest; + } + + /** + * Makes remote request to the carrier and returns a response + * + * @param string $purpose + * @return mixed + */ + protected function _doRatesRequest($purpose) + { + $ratesRequest = $this->_formRateRequest($purpose); $requestString = serialize($ratesRequest); $response = $this->_getCachedQuotes($requestString); $debugData = array('request' => $ratesRequest); @@ -383,7 +419,33 @@ protected function _getQuotes() $debugData['result'] = $response; } $this->_debug($debugData); - return $this->_prepareRateResponse($response); + return $response; + } + + /** + * Do remote request for and handle errors + * + * @return Mage_Shipping_Model_Rate_Result + */ + protected function _getQuotes() + { + $this->_result = Mage::getModel('shipping/rate_result'); + // make separate request for Smart Post method + $allowedMethods = explode(',', $this->getConfigData('allowed_methods')); + if (in_array(self::RATE_REQUEST_SMARTPOST, $allowedMethods)) { + $response = $this->_doRatesRequest(self::RATE_REQUEST_SMARTPOST); + $preparedSmartpost = $this->_prepareRateResponse($response); + if (!$preparedSmartpost->getError()) { + $this->_result->append($preparedSmartpost); + } + } + // make general request for all methods + $response = $this->_doRatesRequest(self::RATE_REQUEST_GENERAL); + $preparedGeneral = $this->_prepareRateResponse($response); + if (!$preparedGeneral->getError() || ($this->_result->getError() && $preparedGeneral->getError())) { + $this->_result->append($preparedGeneral); + } + return $this->_result; } /** @@ -1208,7 +1270,7 @@ protected function _getAuthDetails() ), 'Version' => array( 'ServiceId' => 'ship', - 'Major' => '9', + 'Major' => '10', 'Intermediate' => '0', 'Minor' => '0' ) diff --git a/app/code/core/Mage/Usa/Model/Shipping/Carrier/Ups/Source/Mode.php b/app/code/core/Mage/Usa/Model/Shipping/Carrier/Ups/Source/Mode.php new file mode 100644 index 0000000000..ba786f9878 --- /dev/null +++ b/app/code/core/Mage/Usa/Model/Shipping/Carrier/Ups/Source/Mode.php @@ -0,0 +1,44 @@ + + */ +class Mage_Usa_Model_Shipping_Carrier_Ups_Source_Mode +{ + public function toOptionArray() + { + return array( + array('value' => '1', 'label' => Mage::helper('usa')->__('Live')), + array('value' => '0', 'label' => Mage::helper('usa')->__('Development')), + ); + } +} diff --git a/app/code/core/Mage/Usa/Model/Shipping/Carrier/Usps.php b/app/code/core/Mage/Usa/Model/Shipping/Carrier/Usps.php index 98c45df50a..a268101a10 100644 --- a/app/code/core/Mage/Usa/Model/Shipping/Carrier/Usps.php +++ b/app/code/core/Mage/Usa/Model/Shipping/Carrier/Usps.php @@ -648,6 +648,7 @@ public function getCode($type, $code='') 'Priority Mail International', 'First-Class Mail International Package', 'First-Class Mail International Large Envelope', + 'First-Class Mail International Parcel', ) ) ) @@ -706,6 +707,7 @@ public function getCode($type, $code='') 'Express Mail International', 'Priority Mail International', 'First-Class Mail International Package', + 'First-Class Mail International Parcel', ) ) ) @@ -728,6 +730,7 @@ public function getCode($type, $code='') 'Express Mail International', 'Priority Mail International', 'First-Class Mail International Package', + 'First-Class Mail International Parcel', ) ) ) @@ -1197,7 +1200,9 @@ protected function _getCountryName($countryId) */ protected function _filterServiceName($name) { - $name = (string)preg_replace(array('~<[^/!][^>]+>.*]+>~sU', '~\ + + + + + + + + + + + + + diff --git a/app/design/adminhtml/default/default/layout/api2.xml b/app/design/adminhtml/default/default/layout/api2.xml new file mode 100644 index 0000000000..ba5f3f5389 --- /dev/null +++ b/app/design/adminhtml/default/default/layout/api2.xml @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + api2_role_section_info + adminhtml.role.edit.tab.info + + + api2_role_section_resources + adminhtml.role.edit.tab.resources + + + + + + + + + 1 + + + + + + + + + + + + api2_role_section_info + adminhtml.role.edit.tab.info + + + api2_role_section_resources + adminhtml.role.edit.tab.resources + + + roleUsersGrid + adminhtml.role.edit.tab.users + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + api2_roles_section + adminhtml.permissions.user.edit.tab.roles + roles_section + + + + + + + + + + + + + + + + + + + + api2_attribute_section_resources + api2.attribute.tab.resource + + + + + + + + + + + 1 + + + + + diff --git a/app/design/adminhtml/default/default/layout/oauth.xml b/app/design/adminhtml/default/default/layout/oauth.xml new file mode 100644 index 0000000000..652e9897ef --- /dev/null +++ b/app/design/adminhtml/default/default/layout/oauth.xml @@ -0,0 +1,169 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/design/adminhtml/default/default/template/api/roleinfo.phtml b/app/design/adminhtml/default/default/template/api/roleinfo.phtml index 053723aeba..f41a0001ba 100644 --- a/app/design/adminhtml/default/default/template/api/roleinfo.phtml +++ b/app/design/adminhtml/default/default/template/api/roleinfo.phtml @@ -23,11 +23,13 @@ * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ +/** @var $this Mage_Adminhtml_Block_Api_Tab_Roleinfo */ + ?>
    - + - + @@ -62,7 +62,7 @@ - + getParentItem()): ?> @@ -355,7 +355,7 @@ getItemOptions()): ?>
    -
    htmlEscape($_option['label']) ?>
    +
    escapeHtml($_option['label']) ?>
    getPrintStatus()): ?> getFormatedOptionValue($_option) ?> class="truncated"> @@ -363,19 +363,19 @@
    -
    htmlEscape($_option['label']) ?>
    +
    escapeHtml($_option['label']) ?>
    -
    htmlEscape( (isset($_option['print_value']) ? $_option['print_value'] : $_option['value']) ) ?>
    +
    escapeHtml( (isset($_option['print_value']) ? $_option['print_value'] : $_option['value']) ) ?>
    - htmlEscape($_item->getDescription()) ?> + escapeHtml($_item->getDescription()) ?> helper('giftmessage/message')->getIsMessagesAvailable('order_item', $parentItem) && $parentItem->getGiftMessageId()): ?> __('Gift Message') ?> diff --git a/app/design/frontend/default/iphone/template/catalog/product/compare/list.phtml b/app/design/frontend/default/iphone/template/catalog/product/compare/list.phtml index 27def95c51..a143621fe0 100644 --- a/app/design/frontend/default/iphone/template/catalog/product/compare/list.phtml +++ b/app/design/frontend/default/iphone/template/catalog/product/compare/list.phtml @@ -63,7 +63,7 @@ helper('wishlist')->isAllow()) : ?> @@ -85,7 +85,7 @@ getPriceHtml($_item, true, '-compare-list-' . $_attribute->getCode()) ?> - <?php echo $this->htmlEscape($_item->getName()) ?> + <?php echo $this->escapeHtml($_item->getName()) ?>getProductAttributeValue($_item, $_attribute),0,10); @@ -116,7 +116,7 @@ helper('wishlist')->isAllow()) : ?> diff --git a/app/design/frontend/default/iphone/template/catalog/product/gallery.phtml b/app/design/frontend/default/iphone/template/catalog/product/gallery.phtml index 8c9a9a5da4..3abe86c1b7 100644 --- a/app/design/frontend/default/iphone/template/catalog/product/gallery.phtml +++ b/app/design/frontend/default/iphone/template/catalog/product/gallery.phtml @@ -37,11 +37,11 @@ -
    count() ?> @@ -174,25 +151,9 @@
    diff --git a/app/design/frontend/default/iphone/template/catalog/product/list/upsell.phtml b/app/design/frontend/default/iphone/template/catalog/product/list/upsell.phtml index 625ae23c42..2ee353c52a 100644 --- a/app/design/frontend/default/iphone/template/catalog/product/list/upsell.phtml +++ b/app/design/frontend/default/iphone/template/catalog/product/list/upsell.phtml @@ -37,9 +37,9 @@ getColumnCount();$_j++): ?> getIterableItem()): ?> ->
  • - -
    <?php echo $this->htmlEscape($_link->getName()) ?>
    -
    htmlEscape($_link->getName()) ?>
    +
    +
    <?php echo $this->escapeHtml($_link->getName()) ?>
    +
    escapeHtml($_link->getName()) ?>
    getPriceHtml($_link, true, '-upsell') ?>
    diff --git a/app/design/frontend/default/iphone/template/catalog/product/price.phtml b/app/design/frontend/default/iphone/template/catalog/product/price.phtml deleted file mode 100644 index b257084cb3..0000000000 --- a/app/design/frontend/default/iphone/template/catalog/product/price.phtml +++ /dev/null @@ -1,412 +0,0 @@ - - - -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 */ - - $_product = $this->getProduct(); - $_id = $_product->getId(); - $_weeeSeparator = ''; - $_simplePricesTax = ($_taxHelper->displayPriceIncludingTax() || $_taxHelper->displayBothPrices()); - $_minimalPriceValue = $_product->getMinimalPrice(); - $_minimalPrice = $_taxHelper->getPrice($_product, $_minimalPriceValue, $_simplePricesTax); -?> - -isGrouped()): ?> - getAmountForDisplay($_product); ?> - typeOfDisplay($_product, array(1,2,4))): ?> - getAmount($_product); ?> - getProductWeeeAttributesForDisplay($_product); ?> - - -
    - getPrice($_product, $_product->getPrice()) ?> - getPrice($_product, $_product->getPrice(), $_simplePricesTax) ?> - getPrice($_product, $_product->getFinalPrice()) ?> - getPrice($_product, $_product->getFinalPrice(), true) ?> - getPriceDisplayType(); ?> - = $_price): ?> - displayBothPrices()): ?> - typeOfDisplay($_product, 0)): // including ?> - - helper('tax')->__('Excl. Tax:') ?> - - currency($_price + $_weeeTaxAmount, true, false) ?> - - - - helper('tax')->__('Incl. Tax:') ?> - - currency($_finalPriceInclTax + $_weeeTaxAmount, true, false) ?> - - - typeOfDisplay($_product, 1)): // incl. + weee ?> - - helper('tax')->__('Excl. Tax:') ?> - - currency($_price + $_weeeTaxAmount, true, false) ?> - - - - helper('tax')->__('Incl. Tax:') ?> - - currency($_finalPriceInclTax + $_weeeTaxAmount, true, false) ?> - - ( - - - getName(); ?>: currency($_weeeTaxAttribute->getAmount(), true, true); ?> - - - ) - - typeOfDisplay($_product, 4)): // incl. + weee ?> - - helper('tax')->__('Excl. Tax:') ?> - - currency($_price + $_weeeTaxAmount, true, false) ?> - - - - helper('tax')->__('Incl. Tax:') ?> - - currency($_finalPriceInclTax + $_weeeTaxAmount, true, false) ?> - - ( - - - getName(); ?>: currency($_weeeTaxAttribute->getAmount() + $_weeeTaxAttribute->getTaxAmount(), true, true); ?> - - - ) - - typeOfDisplay($_product, 2)): // excl. + weee + final ?> - - helper('tax')->__('Excl. Tax:') ?> - - currency($_price, true, false) ?> - - - - - getName(); ?>: currency($_weeeTaxAttribute->getAmount(), true, true); ?> - - - - helper('tax')->__('Incl. Tax:') ?> - - currency($_finalPriceInclTax + $_weeeTaxAmount, true, false) ?> - - - - - helper('tax')->__('Excl. Tax:') ?> - - currency($_price, true, false) ?> - - currency($_price, true, false) ?> - - currency($_finalPrice, true, false) ?> - - - - - helper('tax')->__('Incl. Tax:') ?> - - currency($_finalPriceInclTax, true, false) ?> - - - - - typeOfDisplay($_product, 0)): // including ?> - - currency($_price + $_weeeTaxAmount,true,true) ?> - - typeOfDisplay($_product, 1)): // incl. + weee ?> - - currency($_price + $_weeeTaxAmount,true,true) ?> - - ( - - - getName(); ?>: currency($_weeeTaxAttribute->getAmount(), true, true); ?> - - - ) - typeOfDisplay($_product, 4)): // incl. + weee ?> - - currency($_price + $_weeeTaxAmount,true,true) ?> - - ( - - - getName(); ?>: currency($_weeeTaxAttribute->getAmount() + $_weeeTaxAttribute->getTaxAmount(), true, true); ?> - - - ) - typeOfDisplay($_product, 2)): // excl. + weee + final ?> - currency($_price,true,true) ?>
    - - - getName(); ?>: currency($_weeeTaxAttribute->getAmount(), true, true); ?> - - - - currency($_price + $_weeeTaxAmount,true,true) ?> - - - - currency($_price,true,true) ?> - - - - - getOriginalAmount($_product); ?> - - typeOfDisplay($_product, 0)): // including ?> - displayBothPrices()): ?> -

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

    - -

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

    - -

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

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

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

    -

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

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

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

    -

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

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

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

    -

    - __('Regular Price:') ?> - - currency($_regularPrice, true, false) ?> - -

    - - displayBothPrices()): ?> -

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

    - -

    - __('Special Price:') ?> - - currency($_finalPrice, true, false) ?> - -

    - -

    - __('Regular Price:') ?> - - currency($_regularPrice, true, false) ?> - -

    - - - - - getDisplayMinimalPrice() && $_minimalPriceValue && $_minimalPriceValue < $_product->getFinalPrice()): ?> - - - typeOfDisplay($_product, array(0, 1, 4))): ?> - - - - getUseLinkForAsLowAs()):?> - - - - __('As low as:') ?> - - currency($_minimalPriceDisplayValue, true, false) ?> - - getUseLinkForAsLowAs()):?> - - - - getDisplayMinimalPrice() && $_minimalPrice && $_minimalPrice < $_finalPrice): */ ?> -
    - -isGrouped()): */ ?> - getPrice($_product, $_minimalPriceValue, $includingTax = null); - $_inclTax = $_taxHelper->getPrice($_product, $_minimalPriceValue, $includingTax = true); - ?> - getDisplayMinimalPrice() && $_minimalPriceValue): ?> -
    -

    - __('Starting at:') ?> - displayBothPrices()): ?> - - helper('tax')->__('Excl. Tax:') ?> - - currency($_exclTax, true, false) ?> - - - - helper('tax')->__('Incl. Tax:') ?> - - currency($_inclTax, true, false) ?> - - - - displayPriceIncludingTax()) { - $_showPrice = $_exclTax; - } - ?> - - currency($_showPrice, true, false) ?> - - -

    -
    - getDisplayMinimalPrice() && $_minimalPrice): */ ?> -isGrouped()): */ ?> diff --git a/app/design/frontend/default/iphone/template/catalog/product/view.phtml b/app/design/frontend/default/iphone/template/catalog/product/view.phtml index 1a13808fff..d26c70ac27 100644 --- a/app/design/frontend/default/iphone/template/catalog/product/view.phtml +++ b/app/design/frontend/default/iphone/template/catalog/product/view.phtml @@ -62,7 +62,7 @@ - isSaleable() && $this->hasOptions()):?> + isSaleable() && $this->hasOptions() && $this->getChildChildHtml('container1')):?> getChildChildHtml('container1', '', true, true) ?> hasOptions() && $_product->getTypeId() != Mage_Catalog_Model_Product_Type_Grouped::TYPE_CODE) :?> @@ -92,6 +92,7 @@ + getChildHtml('giftcard_form'); ?> isSaleable() && $this->hasOptions()):?> getChildChildHtml('container2', '', true, true) ?> @@ -118,12 +119,16 @@ } var e = null; try { - var originalImg = $$('.product-image-wrap .product-image img')[0]; - originalImg.up('.product-image-wrap').insert(originalImg.clone().addClassName('cloned')); - setTimeout(function () {$$('.cloned')[0].setStyle({'webkitTransform' : 'translate3d(' + (document.body.offsetWidth - 190) + 'px, -150px, 2px) scale(0) rotate(200deg)'});}, 1); - $$('.product-image-wrap .cloned')[0].observe('webkitTransitionEnd', function(e) { + if ( window.WebKitCSSMatrix ) { + var originalImg = $$('.product-image-wrap .product-image img')[0]; + originalImg.up('.product-image-wrap').insert(originalImg.clone().addClassName('cloned')); + setTimeout(function () {$$('.cloned')[0].setStyle({'webkitTransform' : 'translate3d(' + (document.body.offsetWidth - 190) + 'px, -150px, 2px) scale(0) rotate(200deg)'});}, 1); + $$('.product-image-wrap .cloned')[0].observe('webkitTransitionEnd', function(e) { + this.form.submit(); + }.bind(this)); + } else { this.form.submit(); - }.bind(this)); + } } catch (e) { } this.form.action = oldUrl; @@ -143,6 +148,13 @@ delete Validation.methods['required-entry']; delete Validation.methods['validate-one-required']; delete Validation.methods['validate-one-required-by-name']; + // Remove custom datetime validators + for (var methodName in Validation.methods) { + if (methodName.match(/^validate-datetime-.*/i)) { + delete Validation.methods[methodName]; + } + } + if (this.validator.validate()) { if (url) { this.form.action = url; diff --git a/app/design/frontend/default/iphone/template/catalog/product/view/addto.phtml b/app/design/frontend/default/iphone/template/catalog/product/view/addto.phtml index deb987f000..ade2b97026 100644 --- a/app/design/frontend/default/iphone/template/catalog/product/view/addto.phtml +++ b/app/design/frontend/default/iphone/template/catalog/product/view/addto.phtml @@ -29,7 +29,7 @@ helper('wishlist')->getAddUrl($_product); ?>
      helper('wishlist')->isAllow()) : ?> -
    • __('Add to Wishlist') ?>
    • +
    • __('Add to Wishlist') ?>
    • helper('catalog/product_compare')->getAddUrl($_product); diff --git a/app/design/frontend/default/iphone/template/catalog/product/view/addtocartbottom.phtml b/app/design/frontend/default/iphone/template/catalog/product/view/addtocartbottom.phtml new file mode 100644 index 0000000000..35565a8eab --- /dev/null +++ b/app/design/frontend/default/iphone/template/catalog/product/view/addtocartbottom.phtml @@ -0,0 +1,38 @@ + +getProduct(); ?> +__('Add to Cart'); ?> +isSaleable()): ?> +
      + isGrouped()): ?> + + + +
      + + getChildHtml('', true, true) ?> + diff --git a/app/design/frontend/default/iphone/template/catalog/product/view/addtowishlist.phtml b/app/design/frontend/default/iphone/template/catalog/product/view/addtowishlist.phtml index 4723d53d14..985b7dedd2 100644 --- a/app/design/frontend/default/iphone/template/catalog/product/view/addtowishlist.phtml +++ b/app/design/frontend/default/iphone/template/catalog/product/view/addtowishlist.phtml @@ -28,4 +28,4 @@ ?> getProduct(); ?> helper('wishlist')->getAddUrl($_product); ?> -__('Add to Wishlist') ?> +__('Add to Wishlist') ?> diff --git a/app/design/frontend/default/iphone/template/catalog/product/view/description.phtml b/app/design/frontend/default/iphone/template/catalog/product/view/description.phtml new file mode 100644 index 0000000000..8ca6a7ea11 --- /dev/null +++ b/app/design/frontend/default/iphone/template/catalog/product/view/description.phtml @@ -0,0 +1,39 @@ + +getProduct()->getDescription(); ?> + +

      __('Details') ?>

      +
      + helper('catalog/output')->productAttribute($this->getProduct(), $_description, 'description') ?> +
      + diff --git a/app/design/frontend/default/iphone/template/catalog/product/view/media.phtml b/app/design/frontend/default/iphone/template/catalog/product/view/media.phtml index 67df011c85..e8f0369e1b 100644 --- a/app/design/frontend/default/iphone/template/catalog/product/view/media.phtml +++ b/app/design/frontend/default/iphone/template/catalog/product/view/media.phtml @@ -41,13 +41,13 @@
      • getGalleryUrl($_image).'">'.$this->htmlEscape($this->getImageLabel()).''; + $_img = ''.$this->escapeHtml($this->getImageLabel()).''; echo $_helper->productAttribute($_product, $_img, 'image'); ?>
      • <?php echo $this->htmlEscape($_image->getLabel()) ?>
      • <?php echo $this->escapeHtml($_image->getLabel()) ?>
      diff --git a/app/design/frontend/default/iphone/template/catalog/product/view/options/wrapper.phtml b/app/design/frontend/default/iphone/template/catalog/product/view/options/wrapper.phtml new file mode 100644 index 0000000000..b1271eb641 --- /dev/null +++ b/app/design/frontend/default/iphone/template/catalog/product/view/options/wrapper.phtml @@ -0,0 +1,29 @@ + +
      + getChildHtml('', true, true);?> +
      diff --git a/app/design/frontend/default/iphone/template/catalog/product/view/options/wrapper/bottom.phtml b/app/design/frontend/default/iphone/template/catalog/product/view/options/wrapper/bottom.phtml new file mode 100644 index 0000000000..af832f2e34 --- /dev/null +++ b/app/design/frontend/default/iphone/template/catalog/product/view/options/wrapper/bottom.phtml @@ -0,0 +1,32 @@ + +
      + hasRequiredOptions()):?> +

      __('* Required Fields') ?>

      + + getChildHtml('', true, true);?> +
      diff --git a/app/design/frontend/default/iphone/template/catalog/product/view/type/grouped_grid.phtml b/app/design/frontend/default/iphone/template/catalog/product/view/type/grouped_grid.phtml index 2092560c92..680da900b5 100644 --- a/app/design/frontend/default/iphone/template/catalog/product/view/type/grouped_grid.phtml +++ b/app/design/frontend/default/iphone/template/catalog/product/view/type/grouped_grid.phtml @@ -47,7 +47,7 @@ $this->setPreconfiguredValue(); ?> helper('tax')->getPrice($_item, $_item->getFinalPrice(), true) ?>
  • - + getCanShowProductPrice($_product)): ?> - helper('tax')->displayCartBothPrices() ? 2 : 1); ?> - - helper('tax')->displayCartBothPrices()): ?> - - - - - - - - getItems() as $_item): ?> getItemHtml($_item) ?> @@ -72,7 +61,15 @@

    getRoleId() > 0 ) ? ($this->__('Edit Role') . " '{$this->getRoleInfo()->getRoleName()}'") : $this->__('Add New Role') ?>

    getRoleId() > 0 ) ? ($this->__('Edit Role') . " '{$this->escapeHtml($this->getRoleInfo()->getRoleName())}'") : $this->__('Add New Role') ?>

    getBackButtonHtml() ?> getResetButtonHtml() ?> diff --git a/app/design/adminhtml/default/default/template/api2/attribute/buttons.phtml b/app/design/adminhtml/default/default/template/api2/attribute/buttons.phtml new file mode 100644 index 0000000000..94cd70bc9a --- /dev/null +++ b/app/design/adminhtml/default/default/template/api2/attribute/buttons.phtml @@ -0,0 +1,53 @@ + +
    + + + + + +
    +

    + getCaption() ?> +

    +
    + getBackButtonHtml() ?> + getResetButtonHtml() ?> + getDeleteButtonHtml() ?> + getSaveButtonHtml() ?> +
    +
    +
    + getBlockHtml('formkey')?> +
    + diff --git a/app/design/adminhtml/default/default/template/api2/attribute/resource.phtml b/app/design/adminhtml/default/default/template/api2/attribute/resource.phtml new file mode 100644 index 0000000000..e875cdf058 --- /dev/null +++ b/app/design/adminhtml/default/default/template/api2/attribute/resource.phtml @@ -0,0 +1,158 @@ + + + + +getChildHtml(); ?> + +
    +
    +

    __('User Type Resources') ?>

    +
    + +
    + + + + + + + + +
    +
    +
    + hasEntityOnlyAttributes()): ?> +
    + * This attribute data will be returned for a single resource only. + +
    + +
    +
    + + diff --git a/app/design/adminhtml/default/default/template/api2/permissions/user/edit/tab/roles/js.phtml b/app/design/adminhtml/default/default/template/api2/permissions/user/edit/tab/roles/js.phtml new file mode 100644 index 0000000000..a6b6a66dd6 --- /dev/null +++ b/app/design/adminhtml/default/default/template/api2/permissions/user/edit/tab/roles/js.phtml @@ -0,0 +1,59 @@ + + + diff --git a/app/design/adminhtml/default/default/template/api2/role/buttons.phtml b/app/design/adminhtml/default/default/template/api2/role/buttons.phtml new file mode 100644 index 0000000000..32cbd38ae8 --- /dev/null +++ b/app/design/adminhtml/default/default/template/api2/role/buttons.phtml @@ -0,0 +1,52 @@ + + +
    + + + + + +
    +

    + getCaption() ?> +

    +
    + getBackButtonHtml() ?> + getResetButtonHtml() ?> + getDeleteButtonHtml() ?> + getSaveButtonHtml() ?> +
    +
    +
    + getBlockHtml('formkey')?> +
    + diff --git a/app/design/adminhtml/default/default/template/api2/role/users_grid_js.phtml b/app/design/adminhtml/default/default/template/api2/role/users_grid_js.phtml new file mode 100644 index 0000000000..43f18ea8d2 --- /dev/null +++ b/app/design/adminhtml/default/default/template/api2/role/users_grid_js.phtml @@ -0,0 +1,122 @@ + + diff --git a/app/design/adminhtml/default/default/template/captcha/zend.phtml b/app/design/adminhtml/default/default/template/captcha/zend.phtml index c826680ebf..76cbb40d12 100644 --- a/app/design/adminhtml/default/default/template/captcha/zend.phtml +++ b/app/design/adminhtml/default/default/template/captcha/zend.phtml @@ -45,7 +45,6 @@ + + + +
    + + diff --git a/app/design/adminhtml/default/default/template/oauth/authorize/head-simple.phtml b/app/design/adminhtml/default/default/template/oauth/authorize/head-simple.phtml new file mode 100644 index 0000000000..c6a3688b3b --- /dev/null +++ b/app/design/adminhtml/default/default/template/oauth/authorize/head-simple.phtml @@ -0,0 +1,50 @@ + + +<?php echo htmlspecialchars(html_entity_decode($this->getTitle())) ?> + + + + + +getChildHtml() ?> diff --git a/app/design/adminhtml/default/default/template/oauth/authorize/reject-simple.phtml b/app/design/adminhtml/default/default/template/oauth/authorize/reject-simple.phtml new file mode 100644 index 0000000000..35c74574ed --- /dev/null +++ b/app/design/adminhtml/default/default/template/oauth/authorize/reject-simple.phtml @@ -0,0 +1,48 @@ + + diff --git a/app/design/adminhtml/default/default/template/oauth/authorize/reject.phtml b/app/design/adminhtml/default/default/template/oauth/authorize/reject.phtml new file mode 100644 index 0000000000..3dc35b239f --- /dev/null +++ b/app/design/adminhtml/default/default/template/oauth/authorize/reject.phtml @@ -0,0 +1,44 @@ + + + + diff --git a/app/design/adminhtml/default/default/template/oauth/authorize/simple-css.phtml b/app/design/adminhtml/default/default/template/oauth/authorize/simple-css.phtml new file mode 100644 index 0000000000..61dfcb2647 --- /dev/null +++ b/app/design/adminhtml/default/default/template/oauth/authorize/simple-css.phtml @@ -0,0 +1,251 @@ + +getSkinUrl(null, array('_area' => 'adminhtml', '_package' => 'default')); +?> + diff --git a/app/design/adminhtml/default/default/template/page/menu.phtml b/app/design/adminhtml/default/default/template/page/menu.phtml index fc99e8cc5c..6d0476d65e 100644 --- a/app/design/adminhtml/default/default/template/page/menu.phtml +++ b/app/design/adminhtml/default/default/template/page/menu.phtml @@ -25,25 +25,12 @@ */ ?> + @@ -93,7 +97,7 @@
    - +
    @@ -107,13 +111,13 @@
    - +
    - +
    @@ -122,7 +126,7 @@
  • - getAddress()->getSameAsShipping()):?>checked="checked"/> + getAddress()->getSameAsBilling()):?>checked="checked"/>
    @@ -135,7 +139,7 @@ diff --git a/app/design/frontend/base/default/template/paypal/express/review/shipping/method.phtml b/app/design/frontend/base/default/template/paypal/express/review/shipping/method.phtml index 90ccee3391..ef2044928b 100644 --- a/app/design/frontend/base/default/template/paypal/express/review/shipping/method.phtml +++ b/app/design/frontend/base/default/template/paypal/express/review/shipping/method.phtml @@ -25,29 +25,29 @@ */ /** @var $this Mage_Paypal_Block_Express_Review */ ?> +
    getCanEditShippingMethod() || !$this->getCurrentShippingRate()):?> - getShippingRateGroups()):?> - getCurrentShippingRate(); ?> -
    - + + + + $rates):?> + + + - - -
    - -

    __('Sorry, no quotes are available for this order at this time.') ?>

    - + + + + +

    __('Sorry, no quotes are available for this order at this time.') ?>

    +

    renderShippingRateOption($this->getCurrentShippingRate())?>

    +
    + diff --git a/app/design/frontend/base/default/template/persistent/checkout/onepage/billing.phtml b/app/design/frontend/base/default/template/persistent/checkout/onepage/billing.phtml index c7be928bf0..da76c684f6 100644 --- a/app/design/frontend/base/default/template/persistent/checkout/onepage/billing.phtml +++ b/app/design/frontend/base/default/template/persistent/checkout/onepage/billing.phtml @@ -45,42 +45,46 @@
    - +
    isCustomerLoggedIn()): ?>
    - +
    - +
  • + helper('customer/address')->getAttributeValidationClass('street'); ?>
  • - +
  • - helper('customer/address')->getStreetLines(); $_i<=$_n; $_i++): ?> + + helper('customer/address')->getStreetLines(); $_i <= $_n; $_i++): ?>
  • - +
  • - + + helper('customer/address')->isVatAttributeVisible()) : ?>
  • - +
  • +
  • - +
    @@ -94,7 +98,7 @@ $('billing:region_id').setAttribute('defaultValue', "getAddress()->getRegionId() ?>"); //]]> - +
  • @@ -102,7 +106,7 @@
    - +
    @@ -116,13 +120,13 @@
    - +
    - +
    @@ -209,12 +213,11 @@ if ($('onepage-guest-register-button')) { Event.observe($('onepage-guest-register-button'), 'click', function(event) { var billingRememberMe = $('co-billing-form').select('#remember-me-box'); - if(billingRememberMe.length > 0) { - billingRememberMe = $(billingRememberMe[0].parentNode); + if (billingRememberMe.length > 0) { if ($('login:guest') && $('login:guest').checked) { - billingRememberMe.hide(); + billingRememberMe[0].hide(); } else if ($('login:register') && ($('login:register').checked || $('login:register').type == 'hidden')) { - billingRememberMe.show(); + billingRememberMe[0].show(); } } }); diff --git a/app/design/frontend/base/default/template/persistent/customer/form/register.phtml b/app/design/frontend/base/default/template/persistent/customer/form/register.phtml index 91d23b4148..cebc4ac035 100644 --- a/app/design/frontend/base/default/template/persistent/customer/form/register.phtml +++ b/app/design/frontend/base/default/template/persistent/customer/form/register.phtml @@ -50,7 +50,7 @@
  • - +
  • isNewsletterEnabled()): ?> @@ -84,34 +84,36 @@
    - +
    - +
    + helper('customer/address')->getAttributeValidationClass('street'); ?>
  • - +
  • - helper('customer/address')->getStreetLines(); $_i<=$_n; $_i++): ?> + + helper('customer/address')->getStreetLines(); $_i <= $_n; $_i++): ?>
  • - +
  • - +
  • - +
    @@ -125,7 +127,7 @@ $('region_id').setAttribute('defaultValue', "getFormData()->getRegionId() ?>"); //]]> - +
  • @@ -133,7 +135,7 @@
    - +
    diff --git a/app/design/frontend/base/default/template/persistent/remember_me.phtml b/app/design/frontend/base/default/template/persistent/remember_me.phtml index 5b4c846e99..5c731f4375 100644 --- a/app/design/frontend/base/default/template/persistent/remember_me.phtml +++ b/app/design/frontend/base/default/template/persistent/remember_me.phtml @@ -28,8 +28,8 @@ /** * Customer "Remember Me" template * + * @var $this Mage_Persistent_Block_Form_Remember */ -/** @var $this Mage_Persistent_Block_Form_Remember */ ?>
  • getRandomString(10); ?> diff --git a/app/design/frontend/base/default/template/wishlist/render/item/price_msrp_item.phtml b/app/design/frontend/base/default/template/wishlist/render/item/price_msrp_item.phtml index 456ad21f47..6796894c68 100644 --- a/app/design/frontend/base/default/template/wishlist/render/item/price_msrp_item.phtml +++ b/app/design/frontend/base/default/template/wishlist/render/item/price_msrp_item.phtml @@ -85,7 +85,7 @@ "getName() ?>", $(""), '', - "getAddToCartUrl() ?>" + "isSalable() ? $_product->getAddToCartUrl() : '' ?>" ); diff --git a/app/design/frontend/default/iphone/layout/bundle.xml b/app/design/frontend/default/iphone/layout/bundle.xml new file mode 100644 index 0000000000..ff56e92fcb --- /dev/null +++ b/app/design/frontend/default/iphone/layout/bundle.xml @@ -0,0 +1,386 @@ + + + + + + + + + bundlebundle/catalog_product_price + + + + + + bundlebundle/catalog_product_price + + + + + + bundlebundle/catalog_product_price + + + + + + bundlebundle/catalog_product_price + + + + + + bundlebundle/catalog_product_price + + + + + + bundlebundle/catalog_product_price + + + + + + bundlebundle/catalog_product_price + + + + + + bundlebundle/catalog_product_price + + + + + + bundlebundle/checkout_cart_item_renderer + + + bundlebundle/catalog_product_price + + + bundlebundle/catalog_product_price + + + + + + bundlebundle/catalog_product_configuration + + + + + + bundlebundle/catalog_product_price + + + + + + + + + bundlebundle/catalog_product_price + bundle4 + + + + + + + + bundlebundle/checkout_cart_item_renderer + + + + + + + + bundlebundle/checkout_cart_item_renderer + + + bundlebundle/catalog_product_price + + + + + + + + bundlebundle/checkout_cart_item_renderer + + + + + + bundlebundle/checkout_cart_item_renderer + + + + + + bundlebundle/checkout_cart_item_renderer + + + bundlebundle/checkout_cart_item_renderer + + + + + + bundlebundle/checkout_cart_item_renderer + + + + + + bundlebundle/checkout_cart_item_renderer + + + + + bundlebundle/checkout_cart_item_renderer + + + + + + bundlebundle/checkout_cart_item_renderer + + + + + bundlebundle/checkout_cart_item_renderer + + + + + + + + + skin_jsjs/bundle.js + + + + bundlebundle/catalog_product_price + + catalog/product/price_msrp_item.phtml + + + + + + selectbundle/catalog_product_view_type_bundle_option_select + multibundle/catalog_product_view_type_bundle_option_multi + radiobundle/catalog_product_view_type_bundle_option_radio + checkboxbundle/catalog_product_view_type_bundle_option_checkbox + + product.info.bundle.options + + + + + + + + bundlebundle/catalog_product_price + + + + + + bundlebundle/sales_order_items_renderer + + + + + + bundlebundle/sales_order_items_renderer + + + + + + bundlebundle/sales_order_items_renderer + + + + + + bundlebundle/sales_order_items_renderer + + + + + + + + bundlebundle/sales_order_items_renderer + + + + + + bundlebundle/sales_order_items_renderer + + + + + + bundlebundle/sales_order_items_renderer + + + + + + bundlebundle/sales_order_items_renderer + + + + + + + bundlebundle/sales_order_items_renderer + + + + + + bundlebundle/sales_order_items_renderer + + + + + + bundlebundle/sales_order_items_renderer + + + + + + bundlebundle/sales_order_items_renderer + + + + + + bundlebundle/sales_order_items_renderer + + + + + + bundlebundle/sales_order_items_renderer + + + + + + bundlebundle/sales_order_items_renderer + + + + + + bundlebundle/sales_order_items_renderer + + + + + + + bundlebundle/sales_order_items_renderer + + + + + + bundlebundle/sales_order_items_renderer + + + + + + + bundlebundle/sales_order_items_renderer + + + + + + bundlebundle/sales_order_items_renderer + + + + + + + bundlebundle/catalog_product_price + + + + + bundlebundle/catalog_product_price + + + + + bundlebundle/catalog_product_price + + + diff --git a/app/design/frontend/default/iphone/layout/catalog.xml b/app/design/frontend/default/iphone/layout/catalog.xml index 656f3473c2..31326b28b9 100644 --- a/app/design/frontend/default/iphone/layout/catalog.xml +++ b/app/design/frontend/default/iphone/layout/catalog.xml @@ -44,6 +44,9 @@ Default layout, loads most of the pages + + + @@ -65,11 +68,11 @@ Default layout, loads most of the pages - + - + @@ -220,6 +223,7 @@ Product view + @@ -240,7 +244,7 @@ Product view product.tierprices - product.info.addtocart + product.info.addtocart.bottom product.info.addto diff --git a/app/design/frontend/default/iphone/layout/checkout.xml b/app/design/frontend/default/iphone/layout/checkout.xml index bb723a1556..941e3cc532 100644 --- a/app/design/frontend/default/iphone/layout/checkout.xml +++ b/app/design/frontend/default/iphone/layout/checkout.xml @@ -322,6 +322,14 @@ One page checkout main layout 1 + + + mage/captcha.js + + user_login + 230 + 50 + @@ -347,12 +355,6 @@ One page checkout progress block - - - - - - - + + + + mage/captcha.js + + user_forgotpassword + 230 + 50 + + @@ -198,18 +224,12 @@ Customer account pages, rendered for all tabs in dashboard - + accountcustomer/account/ account_editcustomer/account/edit/ address_bookcustomer/address/ - - simplecheckout/cart_item_renderer - groupedcheckout/cart_item_renderer_grouped - configurablecheckout/cart_item_renderer_configurable - - diff --git a/app/design/frontend/default/iphone/layout/page.xml b/app/design/frontend/default/iphone/layout/page.xml index e5a957550a..5dceaacc5b 100644 --- a/app/design/frontend/default/iphone/layout/page.xml +++ b/app/design/frontend/default/iphone/layout/page.xml @@ -49,6 +49,7 @@ Default layout, loads most of the pages skin_jsjs/iphone.js skin_jsjs/dnd.js + skin_jsjs/modernizr.js @@ -65,7 +66,6 @@ Default layout, loads most of the pages - diff --git a/app/design/frontend/default/iphone/layout/persistent.xml b/app/design/frontend/default/iphone/layout/persistent.xml index 427223b3a0..2f8403aaba 100644 --- a/app/design/frontend/default/iphone/layout/persistent.xml +++ b/app/design/frontend/default/iphone/layout/persistent.xml @@ -38,6 +38,14 @@ + + + mage/captcha.js + + user_login + 230 + 50 + @@ -46,6 +54,14 @@ + + + mage/captcha.js + + user_create + 230 + 50 + @@ -53,6 +69,14 @@ + + + mage/captcha.js + + user_login + 230 + 50 + diff --git a/app/design/frontend/default/iphone/layout/sales/billing_agreement.xml b/app/design/frontend/default/iphone/layout/sales/billing_agreement.xml new file mode 100644 index 0000000000..2e0f5efbd9 --- /dev/null +++ b/app/design/frontend/default/iphone/layout/sales/billing_agreement.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + billing_agreementssales/billing_agreement/ + + + diff --git a/app/design/frontend/default/iphone/layout/sales/recurring_profile.xml b/app/design/frontend/default/iphone/layout/sales/recurring_profile.xml new file mode 100644 index 0000000000..c4adba0bc9 --- /dev/null +++ b/app/design/frontend/default/iphone/layout/sales/recurring_profile.xml @@ -0,0 +1,171 @@ + + + + + + recurring_profilessales/recurring_profile/ + + + + + + + + + + + + + There are no recurring profiles yet. + recurring_profile_list_view + + + + + + + + + + + + + 1 + + info_tabs + Profile Information + view + + + + info_tabs + Related Orders + orders + + + + + + + + + + + 1 + + + + + info_blocks_row_1 + 1 + Reference + + + + info_blocks_row_1 + 2 + Purchased Item + + + + info_blocks_row_2 + 1 + Profile Schedule + + + + info_blocks_row_2 + 2 + Profile Payments + + + + info_blocks_row_3 + 1 + Billing Address + + + shipping + + info_blocks_row_3 + 2 + Shipping Address + + + + + + + + + + + 1 + + + + + Orders Based on This Profile + There are no orders yet. + info-box + border:0 + + + + + + diff --git a/app/design/frontend/default/iphone/template/bundle/catalog/product/view/type/bundle.phtml b/app/design/frontend/default/iphone/template/bundle/catalog/product/view/type/bundle.phtml new file mode 100644 index 0000000000..e782ad5d27 --- /dev/null +++ b/app/design/frontend/default/iphone/template/bundle/catalog/product/view/type/bundle.phtml @@ -0,0 +1,45 @@ + +getProduct() ?> + +isSaleable()): ?> + + +isAvailable()): ?> +

    helper('catalog')->__('Availability:') ?> helper('catalog')->__('In stock') ?>

    + +

    helper('catalog')->__('Availability:') ?> helper('catalog')->__('Out of stock') ?>

    + +
    + getPriceHtml($_product) ?> +
    +getChildHtml('bundle_prices') ?> diff --git a/app/design/frontend/default/iphone/template/bundle/sales/order/items/renderer.phtml b/app/design/frontend/default/iphone/template/bundle/sales/order/items/renderer.phtml index 5a71eae303..9dfa291f2d 100644 --- a/app/design/frontend/default/iphone/template/bundle/sales/order/items/renderer.phtml +++ b/app/design/frontend/default/iphone/template/bundle/sales/order/items/renderer.phtml @@ -52,7 +52,7 @@ getParentItem()): ?>
  • __('Product Name') ?>

    htmlEscape($_item->getName()) ?>

    escapeHtml($_item->getName()) ?>

    __('SKU') ?>htmlEscape(Mage::helper('core/string')->splitInjection($_item->getSku())) ?>escapeHtml(Mage::helper('core/string')->splitInjection($_item->getSku())) ?>
    htmlEscape($_item->getName()) ?>escapeHtml($_item->getName()) ?> getCanShowProductPrice($_item)): ?> diff --git a/app/design/frontend/default/iphone/template/checkout/cart.phtml b/app/design/frontend/default/iphone/template/checkout/cart.phtml index 505f53678f..48afcefef6 100644 --- a/app/design/frontend/default/iphone/template/checkout/cart.phtml +++ b/app/design/frontend/default/iphone/template/checkout/cart.phtml @@ -53,17 +53,6 @@
    helper('tax')->getIncExcTaxLabel(false) ?>helper('tax')->getIncExcTaxLabel(true) ?>helper('tax')->getIncExcTaxLabel(false) ?>helper('tax')->getIncExcTaxLabel(true) ?>
    - getChildHtml('coupon') ?> + + +
    + getChildHtml('coupon') ?> + getChildHtml('giftcards') ?> +
    +
    hasProductUrl()):?><?php echo $this->htmlEscape($this->getProductName()) ?>hasProductUrl()):?>hasProductUrl()):?><?php echo $this->escapeHtml($this->getProductName()) ?>hasProductUrl()):?>

    hasProductUrl()):?> - htmlEscape($this->getProductName()) ?> + escapeHtml($this->getProductName()) ?> - htmlEscape($this->getProductName()) ?> + escapeHtml($this->getProductName()) ?>

    getOptionList()):?>
    getFormatedOptionValue($_option) ?> -
    htmlEscape($_option['label']) ?>
    +
    escapeHtml($_option['label']) ?>
    class="truncated">
    -
    htmlEscape($_option['label']) ?>
    +
    escapeHtml($_option['label']) ?>
    @@ -62,50 +62,8 @@ getProductAdditionalInformationBlock()):?> setItem($_item)->toHtml() ?> - helper('tax')->displayCartPriceExclTax() || $this->helper('tax')->displayCartBothPrices()): ?> - typeOfDisplay($_item, array(1, 4), 'sales') && $_item->getWeeeTaxAppliedAmount()): ?> - - - - - typeOfDisplay($_item, array(0, 1, 4), 'sales') && $_item->getWeeeTaxAppliedAmount()): ?> - helper('checkout')->formatPrice($_item->getCalculationPrice()+$_item->getWeeeTaxAppliedAmount()+$_item->getWeeeTaxDisposition()); ?> - - helper('checkout')->formatPrice($_item->getCalculationPrice()) ?> - - - getApplied($_item)): ?> - - typeOfDisplay($_item, 2, 'sales') && $_item->getWeeeTaxAppliedAmount()): ?> -
    - __('Total'); ?>: helper('checkout')->formatPrice($_item->getCalculationPrice()+$_item->getWeeeTaxAppliedAmount()+$_item->getWeeeTaxDisposition()); ?> -
    - - - -
    helper('tax')->displayCartBothPrices()): ?> colspan="2"> + __('See price before order confirmation.'); ?> getId(); ?> @@ -114,65 +72,111 @@ Catalog.Map.addHelpLink($(''), "__("What's this?") ?>"); - - helper('checkout')->getPriceInclTax($_item); ?> - typeOfDisplay($_item, array(1, 4), 'sales') && $_item->getWeeeTaxAppliedAmount()): ?> - - - - typeOfDisplay($_item, array(0, 1, 4), 'sales') && $_item->getWeeeTaxAppliedAmount()): ?> - helper('checkout')->formatPrice($_incl+$_item->getWeeeTaxAppliedAmount()); ?> - - helper('checkout')->formatPrice($_incl-$_item->getWeeeTaxDisposition()) ?> - + helper('tax')->displayCartPriceExclTax() || $this->helper('tax')->displayCartBothPrices()): ?> + helper('tax')->displayCartBothPrices()): ?> + helper('tax')->getIncExcText(false) ?> + + typeOfDisplay($_item, array(1, 4), 'sales') && $_item->getWeeeTaxAppliedAmount()): ?> + + + + + typeOfDisplay($_item, array(0, 1, 4), 'sales') && $_item->getWeeeTaxAppliedAmount()): ?> + helper('checkout')->formatPrice($_item->getCalculationPrice()+$_item->getWeeeTaxAppliedAmount()+$_item->getWeeeTaxDisposition()); ?> + + helper('checkout')->formatPrice($_item->getCalculationPrice()) ?> + + + + getApplied($_item)): ?> + + typeOfDisplay($_item, 2, 'sales') && $_item->getWeeeTaxAppliedAmount()): ?> +
    + __('Total'); ?>: helper('checkout')->formatPrice($_item->getCalculationPrice()+$_item->getWeeeTaxAppliedAmount()+$_item->getWeeeTaxDisposition()); ?> +
    + + -
    - getApplied($_item)): ?> + - - typeOfDisplay($_item, 2, 'sales') && $_item->getWeeeTaxAppliedAmount()): ?> -
    - __('Total incl. tax'); ?>: helper('checkout')->formatPrice($_incl+$_item->getWeeeTaxAppliedAmount()); ?> -
    + typeOfDisplay($_item, array(0, 1, 4), 'sales') && $_item->getWeeeTaxAppliedAmount()): ?> + helper('checkout')->formatPrice($_incl+$_item->getWeeeTaxAppliedAmount()); ?> + + helper('checkout')->formatPrice($_incl-$_item->getWeeeTaxDisposition()) ?> + + +
    + getApplied($_item)): ?> + + + + typeOfDisplay($_item, 2, 'sales') && $_item->getWeeeTaxAppliedAmount()): ?> +
    + __('Total incl. tax'); ?>: helper('checkout')->formatPrice($_incl+$_item->getWeeeTaxAppliedAmount()); ?> +
    + + - -
    __('Qty'); ?> - typeOfDisplay($_item, array(1, 4), 'sales') && $_item->getWeeeTaxAppliedAmount()): ?> - - - + + helper('tax')->displayCartBothPrices()): ?> + helper('tax')->getIncExcText(false) ?> + helper('tax')->displayCartPriceExclTax() || $this->helper('tax')->displayCartBothPrices()): ?> + typeOfDisplay($_item, array(1, 4), 'sales') && $_item->getWeeeTaxAppliedAmount()): ?> + + + + + -- @@ -183,78 +187,81 @@ - - getApplied($_item)): ?> + + getApplied($_item)): ?> - - typeOfDisplay($_item, 2, 'sales') && $_item->getWeeeTaxAppliedAmount()): ?> -
    - __('Total'); ?>: helper('checkout')->formatPrice($_item->getRowTotal()+$_item->getWeeeTaxAppliedRowAmount()+$_item->getWeeeTaxRowDisposition()); ?> -
    -
    - helper('checkout')->getSubtotalInclTax($_item); ?> - typeOfDisplay($_item, array(1, 4), 'sales') && $_item->getWeeeTaxAppliedAmount()): ?> - - - - - - -- + helper('tax')->displayCartPriceInclTax() || $this->helper('tax')->displayCartBothPrices()): ?> + helper('tax')->displayCartBothPrices()): ?> + helper('tax')->getIncExcText(true) ?> + + helper('checkout')->getSubtotalInclTax($_item); ?> + typeOfDisplay($_item, array(1, 4), 'sales') && $_item->getWeeeTaxAppliedAmount()): ?> + - typeOfDisplay($_item, array(0, 1, 4), 'sales') && $_item->getWeeeTaxAppliedAmount()): ?> - helper('checkout')->formatPrice($_incl+$_item->getWeeeTaxAppliedRowAmount()); ?> - - helper('checkout')->formatPrice($_incl-$_item->getWeeeTaxRowDisposition()) ?> - + - - getApplied($_item)): ?> - + - typeOfDisplay($_item, 2, 'sales') && $_item->getWeeeTaxAppliedAmount()): ?> -
    - __('Total incl. tax'); ?>: helper('checkout')->formatPrice($_incl+$_item->getWeeeTaxAppliedRowAmount()); ?> + getApplied($_item)): ?> + + + typeOfDisplay($_item, 2, 'sales') && $_item->getWeeeTaxAppliedAmount()): ?> +
    + __('Total incl. tax'); ?>: helper('checkout')->formatPrice($_incl+$_item->getWeeeTaxAppliedRowAmount()); ?> +
    +
    <?php echo $this->__('Remove item')?>
    helper('tax')->getIncExcTaxLabel(false) ?>helper('tax')->getIncExcTaxLabel(true) ?>helper('tax')->getIncExcTaxLabel(false) ?>helper('tax')->getIncExcTaxLabel(true) ?>
    - getChildHtml('coupon') ?> + + +
    + getChildHtml('coupon') ?> + getChildHtml('giftcards') ?> +
    +

    escapeHtml($this->getProductName()) ?>

    + getOptionList()):?> +
    + + getFormatedOptionValue($_option) ?> +
    escapeHtml($_option['label']) ?>
    + class="truncated"> + +
    +
    +
    escapeHtml($_option['label']) ?>
    +
    +
    +
    + + + +
    + + getProductAdditionalInformationBlock()):?> + setItem($_item)->toHtml() ?> + +
    + helper('tax')->displayCartPriceExclTax() || $this->helper('tax')->displayCartBothPrices()): ?> + typeOfDisplay($_item, array(1, 4), 'sales') && $_item->getWeeeTaxAppliedAmount()): ?> + + + + + + typeOfDisplay($_item, array(0, 1, 4), 'sales') && $_item->getWeeeTaxAppliedAmount()): ?> + helper('checkout')->formatPrice($_item->getCalculationPrice()+$_item->getWeeeTaxAppliedAmount()+$_item->getWeeeTaxDisposition()); ?> + + helper('checkout')->formatPrice($_item->getCalculationPrice()) ?> + + + + + + getApplied($_item)): ?> + + + + typeOfDisplay($_item, 2, 'sales') && $_item->getWeeeTaxAppliedAmount()): ?> +
    + __('Total'); ?>:
    helper('checkout')->formatPrice($_item->getCalculationPrice()+$_item->getWeeeTaxAppliedAmount()+$_item->getWeeeTaxDisposition()); ?>
    +
    + + + + helper('tax')->displayCartPriceInclTax() || $this->helper('tax')->displayCartBothPrices()): ?> + helper('tax')->displayCartBothPrices()): ?> + helper('tax')->getIncExcText(true) ?> + + helper('checkout')->getPriceInclTax($_item); ?> + typeOfDisplay($_item, array(1, 4), 'sales') && $_item->getWeeeTaxAppliedAmount()): ?> + + + + + + typeOfDisplay($_item, array(0, 1, 4), 'sales') && $_item->getWeeeTaxAppliedAmount()): ?> + helper('checkout')->formatPrice($_incl+$_item->getWeeeTaxAppliedAmount()); ?> + + helper('checkout')->formatPrice($_incl-$_item->getWeeeTaxDisposition()) ?> + + + + getApplied($_item)): ?> + + + + typeOfDisplay($_item, 2, 'sales') && $_item->getWeeeTaxAppliedAmount()): ?> +
    + __('Total incl. tax'); ?>:
    helper('checkout')->formatPrice($_incl+$_item->getWeeeTaxAppliedAmount()); ?>
    +
    + + +
    getQty() ?> + helper('tax')->displayCartPriceExclTax() || $this->helper('tax')->displayCartBothPrices()): ?> + typeOfDisplay($_item, array(1, 4), 'sales') && $_item->getWeeeTaxAppliedAmount()): ?> + + + + + + typeOfDisplay($_item, array(0, 1, 4), 'sales') && $_item->getWeeeTaxAppliedAmount()): ?> + helper('checkout')->formatPrice($_item->getRowTotal()+$_item->getWeeeTaxAppliedRowAmount()+$_item->getWeeeTaxRowDisposition()); ?> + + helper('checkout')->formatPrice($_item->getRowTotal()) ?> + + + + getApplied($_item)): ?> + + + + typeOfDisplay($_item, 2, 'sales') && $_item->getWeeeTaxAppliedAmount()): ?> +
    + __('Total'); ?>:
    helper('checkout')->formatPrice($_item->getRowTotal()+$_item->getWeeeTaxAppliedRowAmount()+$_item->getWeeeTaxRowDisposition()); ?>
    +
    + + + + helper('tax')->displayCartPriceInclTax() || $this->helper('tax')->displayCartBothPrices()): ?> + helper('tax')->displayCartBothPrices()): ?> + helper('tax')->getIncExcText(true) ?> + + helper('checkout')->getSubtotalInclTax($_item); ?> + typeOfDisplay($_item, array(1, 4), 'sales') && $_item->getWeeeTaxAppliedAmount()): ?> + + + + + + typeOfDisplay($_item, array(0, 1, 4), 'sales') && $_item->getWeeeTaxAppliedAmount()): ?> + helper('checkout')->formatPrice($_incl+$_item->getWeeeTaxAppliedRowAmount()); ?> + + helper('checkout')->formatPrice($_incl-$_item->getWeeeTaxRowDisposition()) ?> + + + + + + getApplied($_item)): ?> + + + + typeOfDisplay($_item, 2, 'sales') && $_item->getWeeeTaxAppliedAmount()): ?> +
    + __('Total incl. tax'); ?>:
    helper('checkout')->formatPrice($_incl+$_item->getWeeeTaxAppliedRowAmount()); ?>
    +
    + + +
    + helper('sales')->__('Your credit card will be charged for') ?> + + displayBaseGrandtotal() ?> +
    __('Close') ?>
    -
    __('From:') ?> htmlEscape($_giftMessage->getRecipient()) ?>
    -
    __('To:') ?> htmlEscape($_giftMessage->getSender()) ?>
    +
    __('From:') ?> escapeHtml($_giftMessage->getRecipient()) ?>
    +
    __('To:') ?> escapeHtml($_giftMessage->getSender()) ?>
    helper('giftmessage/message')->getEscapedGiftMessage($_item) ?>
    __('Tracking Number:'); ?>escapeHtml($track->getTracking()); ?>getTracking(); ?>
    __('Carrier:'); ?>escapeHtml($track->getCarrierTitle()); ?>getCarrierTitle(); ?>
    __('Track:'); ?>escapeHtml($track->getUrl()); ?>getUrl(); ?>
    @@ -53,11 +53,16 @@ -
    diff --git a/downloader/template/footer.phtml b/downloader/template/footer.phtml index 248c39ef46..372e7e15cf 100755 --- a/downloader/template/footer.phtml +++ b/downloader/template/footer.phtml @@ -33,7 +33,7 @@ - Magento is a trademark of Magento, Inc. Copyright © 2012 Magento Inc. + Magento is a trademark of Magento, Inc. Copyright © 2010 Magento Inc.

    diff --git a/errors/design.xml b/errors/design.xml index 0bb645d8ad..70b7f11c8c 100644 --- a/errors/design.xml +++ b/errors/design.xml @@ -20,11 +20,11 @@ * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage - * @package errors + * @package Errors * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ --> default - \ No newline at end of file + diff --git a/js/mage/adminhtml/product/composite/configure.js b/js/mage/adminhtml/product/composite/configure.js index f3d245fc96..5de34ff9c7 100644 --- a/js/mage/adminhtml/product/composite/configure.js +++ b/js/mage/adminhtml/product/composite/configure.js @@ -585,7 +585,7 @@ ProductConfigure.prototype = { var patternFlat = null; var replacement = null; var replacementFlat = null - var scopeArr = blockItem.id.match(/.*\[\w+\]\[(\w+)\]$/); + var scopeArr = blockItem.id.match(/.*\[\w+\]\[([^\]\s]+)\]$/); var itemId = scopeArr[1]; if (method == 'current_confirmed_to_form') { pattern = RegExp('(\\w+)(\\[?)'); @@ -718,7 +718,7 @@ ProductConfigure.prototype = { this.blockFormConfirmed.update(); this.blockConfirmed.childElements().each(function(blockItem) { - var scopeArr = blockItem.id.match(/.*\[(\w+)\]\[(\w+)\]$/); + var scopeArr = blockItem.id.match(/.*\[(\w+)\]\[([^\]\s]+)\]$/); var listType = scopeArr[1]; var itemId = scopeArr[2]; if (allowedListTypes[listType] && (!this.itemsFilter[listType] @@ -731,7 +731,7 @@ ProductConfigure.prototype = { case 'form_confirmed_to_confirmed': var listInfo = this.listTypes[this.current.listType]; this.blockFormConfirmed.childElements().each(function(blockItem) { - var scopeArr = blockItem.id.match(/.*\[(\w+)\]\[(\w+)\]$/); + var scopeArr = blockItem.id.match(/.*\[(\w+)\]\[([^\]\s]+)\]$/); var listType = scopeArr[1]; _renameFields(method, blockItem, listInfo.complexTypes ? listType : null); this.blockConfirmed.insert(blockItem); diff --git a/js/mage/adminhtml/variables.js b/js/mage/adminhtml/variables.js index cf9ffd27bd..15f8e49ea8 100644 --- a/js/mage/adminhtml/variables.js +++ b/js/mage/adminhtml/variables.js @@ -106,7 +106,7 @@ var Variables = { }, prepareVariableRow: function(varValue, varLabel) { var value = (varValue).replace(/"/g, '"').replace(/'/g, '\\''); - var content = '' + varLabel + ''; + var content = '' + varLabel + ''; return content; }, insertVariable: function(value) { diff --git a/js/mage/captcha.js b/js/mage/captcha.js index 7d90c3117e..9f595f32f6 100644 --- a/js/mage/captcha.js +++ b/js/mage/captcha.js @@ -51,19 +51,19 @@ Captcha.prototype = { } }; -document.observe('billing-request:completed', function(event){ - if (window.checkout !== undefined){ +document.observe('billing-request:completed', function(event) { + if (typeof window.checkout != 'undefined') { if (window.checkout.method == 'guest' && $('guest_checkout')){ - window.captcha_guest_checkout.refresh() + $('guest_checkout').captcha.refresh() } - if (window.checkout !== undefined && window.checkout.method== 'register' && $('register_during_checkout')){ - window.captcha_register_during_checkout.refresh() + if (window.checkout.method == 'register' && $('register_during_checkout')){ + $('register_during_checkout').captcha.refresh() } } }); -document.observe('login:setMethod', function(event){ +document.observe('login:setMethod', function(event) { switch(event.memo.method){ case 'guest': if ($('register_during_checkout')) { diff --git a/js/prototype/validation.js b/js/prototype/validation.js index fe52526127..a2dad07dce 100644 --- a/js/prototype/validation.js +++ b/js/prototype/validation.js @@ -841,11 +841,11 @@ function parseNumber(v) } /** - * Hash with credit card types wich can be simply extended in payment modules + * Hash with credit card types which can be simply extended in payment modules * 0 - regexp for card number * 1 - regexp for cvn * 2 - check or not credit card number trough Luhn algorithm by - * function validateCreditCard wich you can find above in this file + * function validateCreditCard which you can find above in this file */ Validation.creditCartTypes = $H({ // 'SS': [new RegExp('^((6759[0-9]{12})|(5018|5020|5038|6304|6759|6761|6763[0-9]{12,19})|(49[013][1356][0-9]{12})|(6333[0-9]{12})|(6334[0-4]\d{11})|(633110[0-9]{10})|(564182[0-9]{10}))([0-9]{2,3})?$'), new RegExp('^([0-9]{3}|[0-9]{4})?$'), true], @@ -855,6 +855,6 @@ Validation.creditCartTypes = $H({ 'MC': [new RegExp('^5[1-5][0-9]{14}$'), new RegExp('^[0-9]{3}$'), true], 'AE': [new RegExp('^3[47][0-9]{13}$'), new RegExp('^[0-9]{4}$'), true], 'DI': [new RegExp('^6011[0-9]{12}$'), new RegExp('^[0-9]{3}$'), true], - 'JCB': [new RegExp('^(3[0-9]{15}|(2131|1800)[0-9]{11})$'), new RegExp('^[0-9]{4}$'), true], + 'JCB': [new RegExp('^(3[0-9]{15}|(2131|1800)[0-9]{11})$'), new RegExp('^[0-9]{3,4}$'), true], 'OT': [false, new RegExp('^([0-9]{3}|[0-9]{4})?$'), false] }); diff --git a/lib/Mage/Backup.php b/lib/Mage/Backup.php index d13986ae33..c1d481a494 100644 --- a/lib/Mage/Backup.php +++ b/lib/Mage/Backup.php @@ -48,7 +48,7 @@ class Mage_Backup */ static public function getBackupInstance($type) { - $class = 'Mage_Backup_' . $type; + $class = 'Mage_Backup_' . ucfirst($type); if (!in_array($type, self::$_allowedBackupTypes) || !class_exists($class, true)){ throw new Mage_Exception('Current implementation not supported this type (' . $type . ') of backup.'); diff --git a/lib/Mage/Backup/Db.php b/lib/Mage/Backup/Db.php index df3c2c8331..baece22ae1 100644 --- a/lib/Mage/Backup/Db.php +++ b/lib/Mage/Backup/Db.php @@ -34,7 +34,7 @@ class Mage_Backup_Db extends Mage_Backup_Abstract { /** - * Implementation Rollback functionality for Db + * Implements Rollback functionality for Db * * @return bool */ @@ -48,20 +48,10 @@ public function rollback() $archiveManager = new Mage_Archive(); $source = $archiveManager->unpack($this->getBackupPath(), $this->getBackupsDir()); - $this->getResourceModel()->beginTransaction(); - - $file = fopen($source, "r"); - $query = ''; - while(!feof($file)) { - $line = fgets($file); - $query .= $line; - if ($this->_isLineLastInCommand($line)){ - $this->getResourceModel()->runCommand($query); - $query = ''; - } + $file = new Mage_Backup_Filesystem_Iterator_File($source); + foreach ($file as $statement) { + $this->getResourceModel()->runCommand($statement); } - fclose($file); - $this->getResourceModel()->commitTransaction(); @unlink($source); $this->_lastOperationSucceed = true; @@ -70,7 +60,7 @@ public function rollback() } /** - * Check is line a last in sql command + * Checks whether the line is last in sql command * * @param $line * @return bool @@ -92,9 +82,9 @@ protected function _isLineLastInCommand($line) } /** - * Implementation Create Backup functionality for Db + * Implements Create Backup functionality for Db * - * @return boolean + * @return bool */ public function create() { @@ -124,6 +114,6 @@ public function create() */ public function getType() { - return "db"; + return 'db'; } } diff --git a/lib/Mage/Backup/Filesystem/Iterator/File.php b/lib/Mage/Backup/Filesystem/Iterator/File.php new file mode 100644 index 0000000000..5e48a9fd69 --- /dev/null +++ b/lib/Mage/Backup/Filesystem/Iterator/File.php @@ -0,0 +1,112 @@ + + */ +class Mage_Backup_Filesystem_Iterator_File extends SplFileObject +{ + /** + * The statement that was last read during iteration + * + * @var string + */ + protected $_currentStatement = ''; + + /** + * Return current sql statement + * + * @return string + */ + public function current() + { + return $this->_currentStatement; + } + + /** + * Iterate to next sql statement in file + */ + public function next() + { + $this->_currentStatement = ''; + while (!$this->eof()) { + $line = $this->fgets(); + if (strlen(trim($line))) { + $this->_currentStatement .= $line; + if ($this->_isLineLastInCommand($line)) { + break; + } + } + } + } + + /** + * Return to first statement + */ + public function rewind() + { + parent::rewind(); + $this->next(); + } + + /** + * Check whether provided string is comment + * + * @param string $line + * @return bool + */ + protected function _isComment($line) + { + return $line[0] == '#' || substr($line, 0, 2) == '--'; + } + + /** + * Check is line a last in sql command + * + * @param string $line + * @return bool + */ + protected function _isLineLastInCommand($line) + { + $cleanLine = trim($line); + $lineLength = strlen($cleanLine); + + $returnResult = false; + if ($lineLength > 0) { + $lastSymbolIndex = $lineLength - 1; + if ($cleanLine[$lastSymbolIndex] == ';') { + $returnResult = true; + } + } + + return $returnResult; + } +} diff --git a/lib/Mage/Backup/Filesystem/Iterator/Filter.php b/lib/Mage/Backup/Filesystem/Iterator/Filter.php index ec42031cfa..8fb432f94c 100644 --- a/lib/Mage/Backup/Filesystem/Iterator/Filter.php +++ b/lib/Mage/Backup/Filesystem/Iterator/Filter.php @@ -44,7 +44,7 @@ class Mage_Backup_Filesystem_Iterator_Filter extends FilterIterator * Constructor * * @param Iterator $iterator - * @param array $skipFiles + * @param array $filters list of files to skip */ public function __construct(Iterator $iterator, array $filters) { diff --git a/lib/Mage/Connect/Packager.php b/lib/Mage/Connect/Packager.php index a85fe51045..0a498d730e 100644 --- a/lib/Mage/Connect/Packager.php +++ b/lib/Mage/Connect/Packager.php @@ -34,14 +34,11 @@ class Mage_Connect_Packager { - /** * Constructor - * @param Mage_connect_Config $config */ public function __construct() { - } /** @@ -340,7 +337,7 @@ public function getRemoteModifiedFiles($chanName, $package, $cacheObj, $ftp) * @param string/array $channels * @param Mage_Connect_Singleconfig $cacheObject * @param Mage_Connect_Rest $restObj optional - * @param bool $checkConflicts + * @param bool $checkConflicts * @return array */ public function getUpgradesList($channels, $cacheObject, $configObj, $restObj = null, $checkConflicts = false) @@ -449,10 +446,10 @@ public function getUninstallList($chanName, $package, $cache, $config, $withDeps //print "Processing outer: {$keyOuter} \n"; $hash[$keyOuter] = array ( - 'name' => $package, - 'channel' => $chanName, - 'version' => $version, - 'packages' => $dependencies, + 'name' => $package, + 'channel' => $chanName, + 'version' => $version, + 'packages' => $dependencies, ); if($withDepsRecursive) { @@ -473,7 +470,6 @@ public function getUninstallList($chanName, $package, $cache, $config, $withDeps } } catch (Exception $e) { - //$this->_failed[] = array('name'=>$package, 'channel'=>$chanName, 'max'=>$versionMax, 'min'=>$versionMin, 'reason'=>$e->getMessage()); } $level--; @@ -496,9 +492,9 @@ public function getUninstallList($chanName, $package, $cache, $config, $withDeps * @param mixed $versionMin * @return mixed */ - public function getDependenciesList( $chanName, $package, $cache, $config, $versionMax = false, $versionMin = false, $withDepsRecursive = true, $forceRemote = false) - { - + public function getDependenciesList($chanName, $package, $cache, $config, $versionMax = false, $versionMin = false, + $withDepsRecursive = true, $forceRemote = false + ) { static $level = 0; static $_depsHash = array(); static $_deps = array(); @@ -530,12 +526,12 @@ public function getDependenciesList( $chanName, $package, $cache, $config, $vers //print "Processing outer: {$keyOuter} \n"; $_depsHash[$keyOuter] = array ( - 'name' => $package, - 'channel' => $chanName, - 'downloaded_version' => $version, - 'min' => $versionMin, - 'max' => $versionMax, - 'packages' => $dependencies, + 'name' => $package, + 'channel' => $chanName, + 'downloaded_version' => $version, + 'min' => $versionMin, + 'max' => $versionMax, + 'packages' => $dependencies, ); if($withDepsRecursive) { @@ -580,7 +576,13 @@ public function getDependenciesList( $chanName, $package, $cache, $config, $vers if(!$cache->hasVersionRangeIntersect($pMin,$pMax, $hasMin, $hasMax)) { $reason = "Detected {$pName} conflict of versions: {$hasMin}-{$hasMax} and {$pMin}-{$pMax}"; unset($_depsHash[$keyInner]); - $_failed[] = array('name'=>$pName, 'channel'=>$pChannel, 'max'=>$pMax, 'min'=>$pMin, 'reason'=>$reason); + $_failed[] = array( + 'name' => $pName, + 'channel' => $pChannel, + 'max' => $pMax, + 'min' => $pMin, + 'reason' => $reason + ); continue; } $newMaxIsLess = version_compare($pMax, $hasMax, "<"); @@ -594,24 +596,27 @@ public function getDependenciesList( $chanName, $package, $cache, $config, $vers } } } catch (Exception $e) { - $_failed[] = array('name'=>$package, 'channel'=>$chanName, 'max'=>$versionMax, 'min'=>$versionMin, 'reason'=>$e->getMessage()); + $_failed[] = array( + 'name' => $package, + 'channel' => $chanName, + 'max' => $versionMax, + 'min' => $versionMin, + 'reason' => $e->getMessage() + ); } - - + $level--; if($level == 0) { $out = $this->processDepsHash($_depsHash); - $deps = $_deps; + $deps = $_deps; $failed = $_failed; $_depsHash = array(); $_deps = array(); - $_failed = array(); - return array('deps' => $deps, 'result' => $out, 'failed'=> $failed); + $_failed = array(); + return array('deps' => $deps, 'result' => $out, 'failed'=> $failed); } - } - /** * Process dependencies hash * Makes topological sorting and gives operation order list @@ -658,5 +663,4 @@ protected function processDepsHash(&$depsHash, $sortReverse = true) unset($graph, $nodes); return $out; } - -} \ No newline at end of file +} diff --git a/lib/Varien/Db/Adapter/Interface.php b/lib/Varien/Db/Adapter/Interface.php index 02a5d03345..07cbfb144a 100644 --- a/lib/Varien/Db/Adapter/Interface.php +++ b/lib/Varien/Db/Adapter/Interface.php @@ -992,4 +992,11 @@ public function decodeVarbinary($value); * @return string */ public function getSuggestedZeroDate(); + + /** + * Get adapter transaction level state. Return 0 if all transactions are complete + * + * @return int + */ + public function getTransactionLevel(); } diff --git a/skin/adminhtml/default/default/boxes.css b/skin/adminhtml/default/default/boxes.css index e0ea7e525a..c6a8939d7a 100644 --- a/skin/adminhtml/default/default/boxes.css +++ b/skin/adminhtml/default/default/boxes.css @@ -385,10 +385,6 @@ select.multiselect option { padding:3px 4px; border-bottom:1px solid #ddd; .columns .form-list { width:auto; } .columns .form-list td.value { width:280px; padding-right:5px !important; } -.columns .form-list td.value { white-space:nowrap; } -.columns .form-list td.value > * { white-space:normal; } -.columns .form-list td.value > strong, -.columns .form-list td.value > button { display:block; } .columns .form-list td.value .next-toinput { width:75px; display:inline; margin-right:5px; } .columns .form-list td.value .next-toselect input.input-text { width:195px!important; display:inline; } @@ -471,6 +467,8 @@ button.disabled:hover, button.disabled:active { border-color:#777 #505050 #505050 #777; background:#919191 url(images/btn_bg-disabled.gif) 0 0 repeat-x; color:#fff; cursor:default; opacity:.8; -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=20)"; } button.add.disabled span { background-image:url(images/add_btn_icon-disabled.gif); } +button.loading > span { padding-left:20px; background-image: url(images/btn_loading-icon.gif) !important;} + button.icon-btn { width:32px !important; } button.icon-btn span { text-indent:-999em; display:block; width:16px; padding:0; overflow:hidden; } @@ -481,14 +479,18 @@ button.icon-btn span { text-indent:-999em; display:block; width:16px; padding:0; /* SWITCHER *******************************************************************************/ .switcher { margin-bottom:10px; border:1px solid #cddddd; background:#e7efef; padding:10px; } -.side-col .switcher select { width:100%; } +.side-col .switcher { padding-right:26px; } +.side-col .switcher label { display:block; } +.side-col .switcher .link-store-scope { float:right; margin-right:-19px; margin-left:3px; } +.side-col .switcher select { width:100%; float:left; } /*.side-col .switcher { margin-right:20px; margin-bottom:20px; }*/ .catalog-categories .side-col .switcher { margin-right:0; margin-bottom:15px; } -.link-storeScope { display:inline-block; vertical-align:middle; margin:0 0 1px; width:16px; height:16px; background:url(images/i_question-mark.png) 0 0 no-repeat; text-decoration:none !important; text-indent:-999em; overflow:hidden; } -.form-list td.value .link-storeScope { vertical-align:top; margin-top:6px; } -.storeScope .link-storeScope { float:left; margin-right:10px; } -.storeScope .tree-storeScope { float:left; padding:7px 10px; border:1px dotted #dedede; } -.storeScope table.stores-tree { float:left; width:auto !important; } +.link-store-scope { display:inline-block; vertical-align:middle; margin:0 0 1px; width:16px; height:16px; background:url(images/i_question-mark.png) 0 0 no-repeat; text-decoration:none !important; text-indent:-999em; overflow:hidden; } +.store-scope .link-store-scope { float:left; margin-right:10px; } +.store-scope .tree-store-scope { float:left; padding:7px 10px; border:1px dotted #dedede; } +.store-scope table.stores-tree { float:left; width:auto !important; } +.form-list td.value .store-scope { white-space:nowrap; } +.form-list td.value .link-store-scope { float:none; margin-right:0; vertical-align:top; margin-top:6px; } /* SPACE @@ -1039,6 +1041,7 @@ ul.tabs a.paypal-section:hover span { background:url(images/paypal_section.png) .x-tree-node-ct { overflow:hidden; } /* Product - Websites */ +.website-name .checkbox { vertical-align:top; margin-top:2px; } .webiste-groups { padding:10px 20px; } .group-stores { padding:2px 10px; } @@ -1515,6 +1518,7 @@ ul.super-product-attributes { padding-left:15px; } .normal { font-weight:normal !important; } /* Clear */ /* This keeps our HTML free of buncha clearing elements */ +.side-col .switcher:after, .message-popup .message-popup-head:after, .message-popup .message-popup-content .message:after, .login-form .form-buttons:after, @@ -1547,4 +1551,4 @@ div.actions:after, .files .row:after, .files-wide .row:after, .grid tr.filter .range .range-line:after, -.storeScope:after { display:block; clear:both; content:"."; font-size:0; line-height:0; height:0; overflow:hidden; } +.store-scope:after { display:block; clear:both; content:"."; font-size:0; line-height:0; height:0; overflow:hidden; } diff --git a/skin/adminhtml/default/default/iestyles.css b/skin/adminhtml/default/default/iestyles.css index 7129a47bfb..0f07d2ff7a 100644 --- a/skin/adminhtml/default/default/iestyles.css +++ b/skin/adminhtml/default/default/iestyles.css @@ -60,7 +60,7 @@ ul.tabs li a, .centinel .authentication, .paypal-payment-notice, .product-options .options-list li, -.storeScope { zoom:1; } +.store-scope { zoom:1; } .clear { display:block; clear:both; height:0; font-size:0; line-height:0; overflow:hidden; } diff --git a/skin/adminhtml/default/default/images/btn_loading-icon.gif b/skin/adminhtml/default/default/images/btn_loading-icon.gif new file mode 100644 index 0000000000..7735ef6e57 Binary files /dev/null and b/skin/adminhtml/default/default/images/btn_loading-icon.gif differ diff --git a/skin/adminhtml/default/default/images/login_box_bg_auth.jpg b/skin/adminhtml/default/default/images/login_box_bg_auth.jpg new file mode 100644 index 0000000000..8f7f13689d Binary files /dev/null and b/skin/adminhtml/default/default/images/login_box_bg_auth.jpg differ diff --git a/skin/frontend/base/default/js/checkout/review.js b/skin/frontend/base/default/js/checkout/review.js index 47168c8b7b..20a51f5f15 100644 --- a/skin/frontend/base/default/js/checkout/review.js +++ b/skin/frontend/base/default/js/checkout/review.js @@ -78,7 +78,9 @@ OrderReviewController.prototype = { if (shippingSelect && $(shippingSelect)) { this.shippingSelect = $(shippingSelect).id; - this.shippingMethodsContainer = $(this.shippingSelect).up(1); + this.shippingMethodsContainer = $(this.shippingSelect).up(); + } else { + this.shippingSelect = shippingSelect; } this._updateOrderSubmit(false); } @@ -139,9 +141,7 @@ OrderReviewController.prototype = { if(this.shippingSelect) { this._updateShipping(); } - if (!this._validateForm()) { - this._updateOrderSubmit(true); - } + this._updateOrderSubmit(!this._validateForm()); this.formValidator.reset(); this._clearValidation(''); } @@ -177,25 +177,45 @@ OrderReviewController.prototype = { } }, + /** + * Sets Container element of Shipping Method + * @param element Container element of Shipping Method + */ + setShippingMethodContainer: function(element) + { + if (element) { + this.shippingMethodsContainer = element; + } + }, + + /** + * Copy element value from shipping to billing address + * @param el + */ + _copyElementValue: function(el) + { + var newId = el.id.replace('shipping:','billing:'); + if (newId && $(newId) && $(newId).type != 'hidden') { + $(newId).value = el.value; + $(newId).setAttribute('readonly', 'readonly'); + $(newId).addClassName('local-validation'); + $(newId).setStyle({opacity:.5}); + $(newId).disable(); + } + }, + /** * Copy data from shipping address to billing */ - _copyShippingToBilling : function () + _copyShippingToBilling : function (event) { if (!this._copyElement) { return; } if (this._copyElement.checked) { - $$('[id^="shipping:"]').each(function(el){ - var newId = el.id.replace('shipping:','billing:'); - if (newId && $(newId) && $(newId).type != 'hidden') { - $(newId).value = el.value; - $(newId).setAttribute('readonly', 'readonly'); - $(newId).addClassName('local-validation'); - $(newId).setStyle({opacity:.5}); - $(newId).disable(); - } - }); + this._copyElementValue($('shipping:country_id')); + billingRegionUpdater.update(); + $$('[id^="shipping:"]').each(this._copyElementValue); this._clearValidation('billing'); } else { $$('[id^="billing:"]').invoke('enable'); @@ -203,7 +223,9 @@ OrderReviewController.prototype = { $$('[id^="billing:"]').invoke('removeClassName', 'local-validation'); $$('[id^="billing:"]').invoke('setStyle', {opacity:1}); } - this._updateOrderSubmit(true); + if (event) { + this._updateOrderSubmit(true); + } }, /** @@ -222,6 +244,7 @@ OrderReviewController.prototype = { if (this._pleaseWait) { this._pleaseWait.show(); } + this._toggleButton(this._ubpdateOrderButton, true); arr = $$('[id^="billing:"]').invoke('enable'); var formData = this.form.serialize(true); @@ -236,6 +259,7 @@ OrderReviewController.prototype = { if (this._pleaseWait && !this._updateShippingMethods) { this._pleaseWait.hide(); } + this._toggleButton(this._ubpdateOrderButton, false); }.bind(this), onSuccess: this._updateShippingMethodsElement.bind(this), evalScripts: true @@ -251,8 +275,8 @@ OrderReviewController.prototype = { * Update Shipping Methods Element from server */ _updateShippingMethodsElement : function (){ - if (this._updateShippingMethods) { - new Ajax.Updater(this.shippingMethodsContainer, this.shippingMethodsUpdateUrl, { + if (this._updateShippingMethods ) { + new Ajax.Updater($(this.shippingMethodsContainer).up(), this.shippingMethodsUpdateUrl, { onComplete: this._updateShipping.bind(this), onSuccess: this._onSubmitShippingSuccess.bind(this), evalScripts: false @@ -307,13 +331,21 @@ OrderReviewController.prototype = { */ _onShippingChange : function(event){ var element = Event.element(event); - if (element != $(this.shippingSelect) && !$(this.shippingSelect).disabled) { - $(this.shippingSelect).disable(); - $(this.shippingSelect).hide(); - if ($('advice-required-entry-' + this.shippingSelect)) { - $('advice-required-entry-' + this.shippingSelect).hide(); + if (element != $(this.shippingSelect) && !($(this.shippingSelect) && $(this.shippingSelect).disabled)) { + if ($(this.shippingSelect)) { + $(this.shippingSelect).disable(); + $(this.shippingSelect).hide(); + if ($('advice-required-entry-' + this.shippingSelect)) { + $('advice-required-entry-' + this.shippingSelect).hide(); + } + } + if (this.shippingMethodsContainer && $(this.shippingMethodsContainer)) { + $(this.shippingMethodsContainer).hide(); + } + + if (this.shippingSelect && $(this.shippingSelect + '_update')) { + $(this.shippingSelect + '_update').show(); } - $(this.shippingSelect + '_update').show(); this._updateShippingMethods = true; } }, @@ -399,13 +431,24 @@ OrderReviewController.prototype = { ); this._canSubmitOrder = !isDisabled; if (this.formSubmit) { - this.formSubmit.disabled = isDisabled; - this.formSubmit.removeClassName('no-checkout'); - this.formSubmit.setStyle({opacity:1}); - if (isDisabled) { - this.formSubmit.addClassName('no-checkout'); - this.formSubmit.setStyle({opacity:.5}); - } + this._toggleButton(this.formSubmit, isDisabled); + } + }, + + /** + * Enable/Disable button + * + * @param button + * @param disable + */ + _toggleButton : function(button, disable) + { + button.disabled = disable; + button.removeClassName('no-checkout'); + button.setStyle({opacity:1}); + if (disable) { + button.addClassName('no-checkout'); + button.setStyle({opacity:.5}); } } }; diff --git a/skin/frontend/base/default/js/msrp.js b/skin/frontend/base/default/js/msrp.js index 7f90e40ad3..cccb19921b 100644 --- a/skin/frontend/base/default/js/msrp.js +++ b/skin/frontend/base/default/js/msrp.js @@ -199,8 +199,10 @@ Catalog.Map = { var productField = $('map-popup-product-id'); productField.value = this.product_id; $(cartButton).show(); + $$('.additional-addtocart-box').invoke('show'); } else { $(cartButton).hide(); + $$('.additional-addtocart-box').invoke('hide'); } //Horizontal line diff --git a/skin/frontend/base/default/js/opcheckout.js b/skin/frontend/base/default/js/opcheckout.js index d792a8cda8..115db07a0a 100644 --- a/skin/frontend/base/default/js/opcheckout.js +++ b/skin/frontend/base/default/js/opcheckout.js @@ -136,7 +136,7 @@ Checkout.prototype = { this.gotoSection('billing'); } else{ - alert(Translator.translate('Please choose to register or to checkout as a guest')); + alert(Translator.translate('Please choose to register or to checkout as a guest').stripTags()); return false; } document.body.fire('login:setMethod', {method : this.method}); @@ -545,7 +545,7 @@ ShippingMethod.prototype = { validate: function() { var methods = document.getElementsByName('shipping_method'); if (methods.length==0) { - alert(Translator.translate('Your order cannot be completed at this time as there is no shipping methods available for it. Please make necessary changes in your shipping address.')); + alert(Translator.translate('Your order cannot be completed at this time as there is no shipping methods available for it. Please make necessary changes in your shipping address.').stripTags()); return false; } @@ -558,7 +558,7 @@ ShippingMethod.prototype = { return true; } } - alert(Translator.translate('Please specify shipping method.')); + alert(Translator.translate('Please specify shipping method.').stripTags()); return false; }, @@ -732,7 +732,7 @@ Payment.prototype = { } var methods = document.getElementsByName('payment[method]'); if (methods.length==0) { - alert(Translator.translate('Your order cannot be completed at this time as there is no payment methods available for it.')); + alert(Translator.translate('Your order cannot be completed at this time as there is no payment methods available for it.').stripTags()); return false; } for (var i=0; i */ /* Tier Prices */ -.tier-prices { margin:10px 0; padding:10px; background-color:#f4f7f7; border:1px solid #dadddd; } -.tier-prices li { line-height:1.4; background:url(../images/i_tier.gif) no-repeat 0 3px; padding:2px 0 2px 10px; color:#424242; } +.product-pricing, +.tier-prices { margin:10px 0; padding:10px; background-color:#f4f7f7; border:1px solid #dadddd; color:#424242; } +.tier-prices li { line-height:1.4; background:url(../images/i_tier.gif) no-repeat 0 3px; padding:2px 0 2px 10px; } .tier-prices .benefit { font-style:italic; font-weight:bold; color:#2f2f2f; } .tier-prices .price { font-weight:bold; color:#2f2f2f; } @@ -1022,8 +1023,9 @@ std ul, .product-options p.required { position:absolute; right:20px; top:20px; } .product-options-bottom { background-color:#fafaec; padding:15px 20px; border:1px solid #e4e4e4; border-top:0; } -.product-options-bottom .tier-prices { margin:0; padding:0 0 10px; border:0; background:0; } -.product-options-bottom .tier-prices li { background:0; padding:2px 0; color:#e26703; } +.product-options-bottom .product-pricing, +.product-options-bottom .tier-prices { margin:0; padding:0 0 10px; border:0; background:0; color:#e26703; } +.product-options-bottom .tier-prices li { background:0; padding:2px 0; } .product-options-bottom .tier-prices .price, .product-options-bottom .tier-prices .benefit { color:#e26703; } .product-options-bottom .price-box { float:left; margin:0; padding:0; } diff --git a/skin/frontend/default/default/css/styles.css b/skin/frontend/default/default/css/styles.css index 4ea30890db..0ef097e419 100644 --- a/skin/frontend/default/default/css/styles.css +++ b/skin/frontend/default/default/css/styles.css @@ -949,8 +949,9 @@ tr.summary-details-excluded { font-style:italic; } /********** Product Prices > */ /* Tier Prices */ -.tier-prices { margin:10px 0; padding:10px; background-color:#f4f7f7; border:1px solid #dadddd; } -.tier-prices li { line-height:1.4; background:url(../images/i_tier.gif) no-repeat 0 3px; padding:2px 0 2px 10px; color:#424242; } +.product-pricing, +.tier-prices { margin:10px 0; padding:10px; background-color:#f4f7f7; border:1px solid #dadddd; color:#424242; } +.tier-prices li { line-height:1.4; background:url(../images/i_tier.gif) no-repeat 0 3px; padding:2px 0 2px 10px; } .tier-prices .benefit { font-style:italic; font-weight:bold; color:#2f2f2f; } .tier-prices .price { font-weight:bold; color:#2f2f2f; } @@ -1053,8 +1054,9 @@ tr.summary-details-excluded { font-style:italic; } .product-options p.required { position:absolute; right:20px; top:20px; } .product-options-bottom { background-color:#fffada; padding:15px 20px; border:1px solid #e4e4e4; border-top:0; } -.product-options-bottom .tier-prices { margin:0; padding:0 0 10px; border:0; background:0; } -.product-options-bottom .tier-prices li { background:0; padding:2px 0; color:#e26703; } +.product-options-bottom .product-pricing, +.product-options-bottom .tier-prices { margin:0; padding:0 0 10px; border:0; background:0; color:#e26703; } +.product-options-bottom .tier-prices li { background:0; padding:2px 0; } .product-options-bottom .tier-prices .price, .product-options-bottom .tier-prices .benefit { color:#e26703; } .product-options-bottom .price-box { float:left; margin:0; padding:0; } diff --git a/skin/frontend/default/iphone/css/iphone.css b/skin/frontend/default/iphone/css/iphone.css index 48d22e6d88..61e82e03a4 100644 --- a/skin/frontend/default/iphone/css/iphone.css +++ b/skin/frontend/default/iphone/css/iphone.css @@ -44,19 +44,31 @@ body { background:#fefefe; font:12px/18px HelveticaNeue, Helvetica, sans-serif; h1 { font-size:14px; font-weight:bold; line-height:14px; text-align:center; } h2 { font-size:12px; font-weight:normal; line-height:20px; } +button:active { position:relative; top:1px; left:1px; } + legend { display:none; } .messages .notice-msg, +.messages .error-msg, .messages .success-msg { background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #FFE38D), color-stop(100%, #FFD040)); - padding:10px; + background:-o-linear-gradient(top, #FFE38D 0%, #FFD040 100%); + padding:5px; color:#222; font-weight:bold; text-shadow:0 1px 0 #eee; -webkit-box-shadow:0 3px 3px #ccc; + box-shadow:0 3px 3px #ccc; +} +.messages .error-msg { + color:#fff; + background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b), color-stop(100%, #c43c35)); + background-image:-o-linear-gradient(top, #ee5f5b 0%, #c43c35 100%); + text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25); } .messages .success-msg { background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #62c462), color-stop(100%, #57a957)); + background-image:-o-linear-gradient(top, #62c462 0%, #57a957 100%); border:solid #3D773D; border-width:1px 0; color:#fff; @@ -64,17 +76,23 @@ legend { display:none; } margin-bottom:10px; } +.no-display { display:none; } +.nobr { white-space:nowrap; } + /* Header -----------------------------*/ body > header { - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#999999), color-stop(50%,#504f50), color-stop(51%,#282728), color-stop(100%,#110f0f)); - background: -webkit-linear-gradient(top, #999999 0%,#504f50 50%,#282728 51%,#110f0f 100%); - background-color:#666; + background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%,#999999), color-stop(50%,#504f50), color-stop(51%,#282728), color-stop(100%,#110f0f)); + background-image:-webkit-linear-gradient(top, #999999 0%,#504f50 50%,#282728 51%,#110f0f 100%); + background-image:-o-linear-gradient(top, #999999 0%, #504f50 50%,#282728 51%, #110f0f 100%); + background-color:#222; + box-sizing:border-box; display:table; padding:5px; position:relative; width:100%; -webkit-box-shadow:0 3px 3px rgba(0, 0, 0, 0.25); + box-shadow:0 3px 3px rgba(0, 0, 0, 0.25); z-index:100; height:34px; -webkit-user-select:none; @@ -86,6 +104,7 @@ body > header { body > header.no-shadow { -webkit-box-shadow:none; + box-shadow:none; } .cart-wrap { @@ -98,8 +117,15 @@ body > header.no-shadow { z-index:99; visibility:hidden; -webkit-box-shadow:0 0 5px rgba(0, 0, 0, 0.5); + box-shadow:0 0 5px rgba(0, 0, 0, 0.5); -webkit-transform:translate3d(0, -100%, 0); } +.cart-wrap:after { + content:''; + display:block; + position:absolute; + margin-top:44px; +} .cart-wrap .cart-shared { margin:0; @@ -123,6 +149,7 @@ body > header.no-shadow { position:relative; z-index:1; -webkit-box-shadow:inset 0 0 3px rgba(0, 0, 0, 0.25); + box-shadow:inset 0 0 3px rgba(0, 0, 0, 0.25); } .cart-wrap-content .page-title { @@ -130,6 +157,7 @@ body > header.no-shadow { border:none; margin:0 -10px 0; -webkit-box-shadow:none; + box-shadow:none; } .cart-wrap-content .cart-empty { @@ -146,10 +174,6 @@ body > header.no-shadow { margin:0; } -.cart-wrap-content .cart-shared .cart-table tr:last-child td { - border:0; -} - .cart-wrap-content .cart-footer .checkout-types li button { padding:5px 10px; } @@ -158,7 +182,10 @@ body > header .store-logo, body > header .menu-wrapper { display:table-cell; vertical-align:middle; - width:50%; + width:70%; +} +body > header .store-logo { + width:30%; } body > header .store-logo img { @@ -192,13 +219,19 @@ body > header dd { width:100%; margin-top:44px; visibility:hidden; - -webkit-transform:translate3d(0, -100%, -1px); -webkit-box-sizing:border-box; + box-sizing:border-box; -webkit-transition:-webkit-transform 250ms ease-out; } +@media all and (-webkit-transform-3d) { + body > header dd { -webkit-transform:translate3d(0, -100%, -1px); } +} + body > header dd.search-box { - background:-webkit-gradient(linear, 0 0, 0 100%, from(#333), to(#000)); + background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#333), to(#000)); + background-image:-o-linear-gradient(top, #333 0%, #000 100%); + background-color:#333; border-top:1px solid #666; padding:7px 5px; -webkit-transition:-webkit-transfrom 300ms linear; @@ -209,14 +242,20 @@ body > header dd.search-box input { outline:none; background:url(../images/search_icon.png) no-repeat 8px 7px #fff; -webkit-background-size:14px 14px; + background-size:14px 14px; -webkit-background-origin:padding-box; + background-origin:padding-box; padding:5px 10px 5px 25px; margin:0; width:100%; border-radius:14px; + -webkit-box-sizing:border-box; + box-sizing:border-box; -webkit-border-radius:14px; + border-radius:14px; -webkit-appearance:none; -webkit-box-shadow:inset 0 1px 3px #000; + box-shadow:inset 0 1px 3px #000; } body > header dt a { @@ -239,6 +278,7 @@ body > header dt.active span:first-child { border-top-left-radius:5px; border-top-right-radius:5px; -webkit-background-clip:padding-box; + background-clip:padding-box; } body > header dt.search > a { @@ -279,7 +319,7 @@ body > header dt.active span:first-child { body > header dt.cart > span:nth-child(2), body > header .badge { - background:-webkit-gradient( + background-image:-webkit-gradient( linear, 0 0, 0 100%, @@ -288,6 +328,7 @@ body > header .badge { color-stop(0.6, #e75159), color-stop(0, #f6bec1) ); + background-image:-o-linear-gradient(top, #f6bec1 0%, #e75159 60%, #e1262f 62%, #af0002 100%); border:2px solid #fff; border-radius:15px; display:inline-block; @@ -300,7 +341,9 @@ body > header .badge { right:-5px; top:-5px; -webkit-box-shadow:0 3px 3px rgba(0, 0, 0, 0.5); + box-shadow:0 3px 3px rgba(0, 0, 0, 0.5); -webkit-background-clip:padding-box; + background-clip:padding-box; z-index:100; } @@ -326,7 +369,8 @@ body > header dd.menu-box { } body > header dd.menu-box a { - background:-webkit-gradient(linear,0 0,0 100%,from(#333),to(#000)); + background-image:-webkit-gradient(linear,0 0,0 100%,from(#333),to(#000)); + background-image:-o-linear-gradient(top, #333 0%, #000 100%); border-bottom:2px solid #585858; color:#fff; padding:10px; @@ -350,9 +394,11 @@ body > header dd.menu-box .badge { left:0; position:relative; -webkit-box-shadow:0 3px 3px rgba(0, 0, 0, 0.5), inset 0 3px 3px rgba(255, 255, 255, 0.35); + box-shadow:0 3px 3px rgba(0, 0, 0, 0.5), inset 0 3px 3px rgba(255, 255, 255, 0.35); } body > header dd.menu-box .links a:hover { -webkit-box-shadow:inset 0 8px 8px rgba(0, 0, 0, .75); + box-shadow:inset 0 8px 8px rgba(0, 0, 0, .75); } body > header dd.menu-box .welcome-msg { @@ -362,6 +408,7 @@ body > header dd.menu-box .welcome-msg { padding:10px; text-shadow:0 -1px 0 #000; -webkit-box-shadow:inset 0 -2px 2px rgba(0, 0, 0, 0.35); + box-shadow:inset 0 -2px 2px rgba(0, 0, 0, 0.35); } body > header dd.menu-box .welcome-msg a { @@ -379,6 +426,7 @@ body > header dd.menu-box ol { margin:5px; padding:5px; -webkit-box-shadow:inset 0 0 3px #000; + box-shadow:inset 0 0 3px #000; } body > header dd.menu-box ol li a { @@ -402,7 +450,12 @@ body > header dd.menu-box ol li.selected a { /* Navigation -----------------------------*/ -body > nav { background:-webkit-gradient(linear, 0 0, 0 100%, from(#1a1a1a), to(#000)); border-bottom:5px solid #3a3a3a; padding:10px 5px 0; } +body > nav { + background:-webkit-gradient(linear, 0 0, 0 100%, from(#1a1a1a), to(#000)); + background:-o-linear-gradient(top, #1a1a1a 0%, #000 100%); + border-bottom:5px solid #3a3a3a; + padding:10px 5px 0; +} body > nav a { color:#fff; display:block; padding:9px 2px 7px; position:relative; font-size:16px; } body > nav ul { @@ -443,6 +496,7 @@ body > nav span { right:-5px; top:-5px; -webkit-box-shadow:0 3px 3px #000; + box-shadow:0 3px 3px #000; } body > nav .active { @@ -470,7 +524,8 @@ body > nav .active a { /* Footer -----------------------------*/ body > footer { - background:-webkit-gradient(linear, 0 0, 0 100%, from(#f6f6f6), to(#fff)); + background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#f6f6f6), to(#fff)); + background-image:-o-linear-gradient(top, #f3f3f3 0%, #fff 100%); min-height:100px; padding:10px; } @@ -504,6 +559,7 @@ body > footer select { } body > footer p { border-top:1px solid rgba(204, 204, 204, 0.3); color:#999; font-size:12px; margin:8px -10px 0; line-height:14px; padding:8px 10px 0; text-align:center; } +.form-subscribe .form-subscribe-header { display:none; } /* Content -----------------------------*/ @@ -526,11 +582,12 @@ body > section > .category-image img { min-width:100%; } overflow:hidden; margin:0 0 -1px; padding:0 0 1px; + position:relative; width:9000px; -webkit-transform:translate3d(0, 0, 0); - -webkit-transition:-webkit-transform 250ms linear; - -webkit-backface-visibility:hidden; - -webkit-perspective:1000; + -webkit-transition:-webkit-transform 250ms linear; + -o-transition:-o-transform 250ms linear; + transition:transform 250ms linear; } #nav-container ul > li > ul { display:none; } #nav-container:after { content: "."; display: block; clear: both; visibility: hidden; line-height: 0; height: 0; } @@ -543,19 +600,26 @@ body > section > .category-image img { min-width:100%; } margin:-5px 10px 0; position:absolute; } -#nav-container li a { background:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#eee)); display:block; } +#nav-container li a { + background:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#eee)); + background:-o-linear-gradient(top, #fff 0, #eee 100%); + display:block; +} #nav-container li a span { background:url(../images/i_arrow_small.png) no-repeat right; - display:block; - padding:10px; + display:block; + padding:10px; -webkit-background-size:8px 12px; + background-size:8px 12px; -webkit-background-origin:content-box; + background-origin:content-box; } /* Form-list -----------------------------*/ .form-list li { margin-bottom:8px; } +.form-list input.input-text, .form-list input[type="text"], .form-list input[type="password"], .form-list input[type="email"] { @@ -569,13 +633,31 @@ body > section > .category-image img { min-width:100%; } width:100%; -webkit-appearance:none; -webkit-box-sizing:border-box; + box-sizing:border-box; -webkit-box-shadow:inset 1px 1px 1px rgba(0, 0, 0, 0.1); + box-shadow:inset 1px 1px 1px rgba(0, 0, 0, 0.1); } .form-list input[type="text"]::-webkit-input-placeholder, .form-list input[type="password"]::-webkit-input-placeholder, .form-list input[type="email"]::-webkit-input-placeholder { color:#444; } +.form-list textarea { + border:1px solid; + border-color:#8e8e8e #e1e1e1 #e1e1e1 #8e8e8e; + border-radius:5px; + padding:5px; + font-size:18px; + height:100px; + width:100%; + resize:vertical; + -webkit-appearance:none; + -webkit-box-sizing:border-box; + box-sizing:border-box; + -webkit-box-shadow:inset 1px 1px 1px rgba(0, 0, 0, 0.1); + box-shadow:inset 1px 1px 1px rgba(0, 0, 0, 0.1); +} input[type="submit"] { background:-webkit-gradient(linear, 0 0, 0 100%, from(#fdfdfd), to(#dbdbdb)); + background:-o-linear-gradient(top, #fdfdfd 0%, #dbdbdb 100%); border:1px solid #dbdbdb; border-radius:10px; font-size:16px; @@ -587,7 +669,9 @@ input[type="submit"] { vertical-align:middle; -webkit-appearance:none; -webkit-box-shadow:0 1px 1px #8a8a8a; + box-shadow:0 1px 1px #8a8a8a; -webkit-background-clip:padding-box; + background-clip:padding-box; } input[type="checkbox"] { background:url(../images/bg_checkbox.png) no-repeat 0 0; @@ -603,7 +687,7 @@ input[type="checkbox"] { input[type="checkbox"]:checked { background-position:0 -19px; } -select { +select, .select-multiple { background:url(../images/i_dropdown.png) no-repeat right #fefefe; border:1px solid; border-color:#e1e1e1; @@ -612,8 +696,130 @@ select { font-size:15px; padding:3px 30px 3px 7px; -webkit-appearance:none; + -moz-appearance:none; + appearance:none; -webkit-box-sizing:border-box; - } + -moz-box-sizing:border-box; + box-sizing:border-box; +} +.select-multiple { + max-width:100%; + overflow:hidden; + text-overflow:ellipsis; + white-space:nowrap; + position:relative; +} +.select-multiple-wrap { + position:relative; +} +.select-multiple-options-wrap { + background:#fefefe; + border:1px solid #a2a2a2; + border-radius:2px; + position:absolute; + top:0; + left:0; + right:0; + visibility:hidden; + -webkit-box-shadow:0 0 3px rgba(0, 0, 0, 0.35); + box-shadow:0 0 3px rgba(0, 0, 0, 0.35); + -webkit-tap-highlight-color:rgba(0,0,0,0); + z-index:1; +} +.select-multiple-options-wrap .select-heading { + background:-webkit-gradient(linear, 0 0, 0 100%, from(#eee), to(#d4d4d4)); + background:-o-linear-gradient(top, #eee 0%, #d4d4d4 100%); + border-bottom:1px solid #a2a2a2; + color:#444; + font-weight:bold; + font-size:13px; + text-transform:uppercase; + text-shadow:0 1px 0 #fff; + padding:5px; + white-space:nowrap; + overflow:hidden; + text-overflow:ellipsis; +} +.select-multiple-options-wrap .select-close { + background:-webkit-gradient(linear, 0 0, 0 100%, from(#eee), to(#bbb)); + background:-o-linear-gradient(top, #eee 0%, #bbb 100%); + border:1px solid #a2a2a2; + cursor:pointer; + color:#222; + -webkit-border-radius:4px; + -moz-border-radius:4px; + border-radius:4px; + font-size:12px; + display:inline-block; + margin-right:8px; + line-height:20px; + text-align:center; + height:20px; + width:20px; + -webkit-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05); + box-shadow:0 1px 2px rgba(0, 0, 0, 0.05); +} +.select-multiple .selected-counter { + background:-webkit-gradient(linear, 0 0, 0 100%, from(#ebebeb), to(#fff)); + background:-o-linear-gradient(top, #ebebeb 0%, #bbb 100%); + border-left:1px solid #c2c2c2; + color:#4d4d4d; + display:inline-block; + font-size:12px; + font-weight:bold; + line-height:23px; + text-shadow:0 1px 0 #fff; + padding:0 8px; + position:absolute; + top:0; + bottom:0; + right:25px; +} +.select-multiple .selected-counter:before { + background:-webkit-gradient(linear, 0 0, 100% 0, color-stop(0.05, rgba(255, 255, 255, 0.5)), color-stop(1, rgb(255, 255, 255))); + content:''; + position:absolute; + left:-41px; + top:0; + bottom:0; + width:40px; +} +.select-multiple-options { + list-style:none; + margin:0 auto; + padding:0; +} +.select-multiple-options li { + border-bottom:1px solid #ccc; + cursor:pointer; + margin:0; + padding:5px 5px 5px 33px; + position:relative; + -webkit-transition:background 300ms linear; +} +.select-multiple-options li:after { + content:''; + display:block; + background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA0AAAAKCAYAAABv7tTEAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIFdpbmRvd3MiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RDAxRDg0NzcxQjdEMTFFMUE1RjVCOUQyODJGQTNDMEMiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RDAxRDg0NzgxQjdEMTFFMUE1RjVCOUQyODJGQTNDMEMiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpEMDFEODQ3NTFCN0QxMUUxQTVGNUI5RDI4MkZBM0MwQyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpEMDFEODQ3NjFCN0QxMUUxQTVGNUI5RDI4MkZBM0MwQyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pv02V7QAAABZSURBVHjalM5BDoAwCATAfUXj/7/Dbzx43QppCW0U5bAh0AwUJPEnItIA8K4lcCqyFMFlfRXo3B/m4AsY8n8GmIF56dhhBgyNzREyA44e4CtYUIQZ0HQBBgCyITMDEAMA1AAAAABJRU5ErkJggg==) no-repeat 0 0; + height:10px; + width:13px; + position:absolute; + top:9px; + left:10px; + opacity:0.2; +} +.select-multiple-options li.active:after { + opacity:1; +} +.select-multiple-options li:last-child { + border-bottom:none; +} +.select-multiple-options li + li { + border-top:1px linear #fff; +} +.select-multiple-options .active { + background:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#e5e5e5)); +} input[type="text"] { background:#fefefe; border:1px solid; @@ -625,7 +831,9 @@ input[type="text"] { width:100%; -webkit-appearance:none; -webkit-box-sizing:border-box; + box-sizing:border-box; -webkit-box-shadow:inset 1px 1px 1px rgba(0, 0, 0, 0.1); + box-shadow:inset 1px 1px 1px rgba(0, 0, 0, 0.1); } .send-friend { @@ -635,6 +843,7 @@ input[type="text"] { padding:9px; border-radius:2px 2px 5px 5px; background:-webkit-gradient(linear, 0 0, 0 100%,color-stop(0, #FCFDFD), color-stop(0.8, #EEEEEE), color-stop(1, #E8E9E9)); + background:-o-linear-gradient(top, #FCFDFD 0%, #eee 80%, #E8E9E9 100%); } .send-friend h2 { @@ -663,7 +872,9 @@ input[type="text"] { width:100%; -webkit-appearance:none; -webkit-box-sizing:border-box; + box-sizing:border-box; -webkit-box-shadow:inset 1px 1px 1px rgba(0, 0, 0, 0.1); + box-shadow:inset 1px 1px 1px rgba(0, 0, 0, 0.1); } .send-friend .field { @@ -681,7 +892,7 @@ input[type="text"] { padding-top:10px; } -#add_recipient_button { +.send-friend #add_recipient_button { float:right; } @@ -693,42 +904,50 @@ input[type="text"] { #add_recipient_button button, .send-friend .btn-remove { + font-family:Arial; margin:0; - padding:2px 8px 4px; + padding:3px 6px; border:1px solid #CECECE; border-radius:2px; background:-webkit-gradient(linear, 0 0, 0 100%, from(#FEFEFE), to(#EEE)); + background:-o-linear-gradient(top, #fefefe 0%, #eee 100%); text-transform: small-caps; color:#333; font-size:24px; - line-height:18px; - padding:0 5px 6px; + line-height:20px; text-shadow:0 1px 0 #fff; -webkit-box-shadow:0 0 5px #ccc; + box-shadow:0 0 5px #ccc; } .send-friend .btn-remove { background:-webkit-gradient(linear, 0 0, 0 100%, from(#e4776f), to(#d83a2e)); + background:-o-linear-gradient(top, #e4776f 0%, #d83a2e 100%); color:#fff; border:none; border-left:1px solid #ccc; border-radius:0 5px 5px 0; - padding:6px 8px 10px; + padding:5px 8px 8px; text-shadow:0 1px 0 #333; -webkit-box-shadow:none; + box-shadow:none; width:initial; height:initial; } .send-friend button[type="submit"] { background:-webkit-gradient(linear, 0 0, 0 100%, from(#F39823), to(#F37221)); + background:-o-linear-gradient(top, #F39823 0%, #F37221 100%); padding:5px 20px; font-size:14px; color:#FFF; border:1px solid #FFF; border-radius:5px; + text-shadow:0 1px 2px rgba(0, 0, 0, 0.25); -webkit-box-shadow:0 3px 3px 0 #9F9F9F; + box-shadow:0 3px 3px 0 #9F9F9F; -webkit-background-clip:padding-box; + background-clip:padding-box; } #max_recipient_message { @@ -760,22 +979,75 @@ input[type="text"] { .login-box { margin:0 -10px -10px; padding:10px; } .login-box form h2, .login-box .new-users h2 { font-size:16px; line-height:16px; font-weight:bold; margin:0 0 10px; color:#333; } -.login-box form { display:block; border:1px solid #D1D1D1; margin:-6px -7px -7px; padding:9px; border-radius:2px 2px 5px 5px; background:-webkit-gradient(linear, 0 0, 0 100%,color-stop(0, #FCFDFD), color-stop(0.8, #EEEEEE), color-stop(1, #E8E9E9)); } +.login-box form { + display:block; + border:1px solid #D1D1D1; + margin:-6px -7px -7px; + padding:9px; + border-radius:2px 2px 5px 5px; + background:-webkit-gradient(linear, 0 0, 0 100%,color-stop(0, #FCFDFD), color-stop(0.8, #eee), color-stop(1, #E8E9E9)); + background:-o-linear-gradient(top, #FCFDFD 0%, #eee 80%, #E8E9E9 100%); +} .login-box form:after { content: "."; display: block; clear: both; visibility: hidden; line-height: 0; height: 0; } .login-box form label { display:block; padding:0 0 3px; font-weight:bold; color:#5E5E5E; } .login-box form > a { font-weight:bold; color:#F4641E; } .login-box form .login-button { margin:7px 0 0; } -.login-box form .login-button input { padding:5px 20px; background:-webkit-gradient(linear, 0 0, 0 100%, from(#F39823), to(#F37221)); font-size:14px; color:#FFF; border:1px solid #FFF; border-radius:5px; -webkit-box-shadow:0 3px 3px 0 #9F9F9F; -webkit-background-clip:padding-box; } +.login-box form .login-button input { + padding:5px 20px; + background:-webkit-gradient(linear, 0 0, 0 100%, from(#F39823), to(#F37221)); + background:-o-linear-gradient(top, #F39823 0%, #F37221 100%); + font-size:14px; + color:#FFF; + border:1px solid #FFF; + border-radius:5px; + -webkit-box-shadow:0 3px 3px 0 #9f9f9f; + box-shadow:0 3px 3px 0 #9f9f9f; + -webkit-background-clip:padding-box; + background-clip:padding-box; + text-shadow:0 1px 2px rgba(0, 0, 0, 0.25); +} .login-box .required { font-size:12px; line-height:16px; margin:8px 0 0; } .login-box .registered-users { margin:0 0 10px; } .login-box .registered-users input[type="submit"] + a { text-decoration:underline; vertical-align:middle; } -.login-box .registered-users h1 { display:none; background:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#ccc)); border:solid #999; border-width:1px 0; border-top-color:#ccc; text-shadow:0 1px 0 #fff; margin:0 -10px 10px; padding:10px; -webkit-box-shadow:0 3px 3px #eee; } +.login-box .registered-users h1 { display:none; background:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#ccc)); border:solid #999; border-width:1px 0; border-top-color:#ccc; text-shadow:0 1px 0 #fff; margin:0 -10px 10px; padding:10px; -webkit-box-shadow:0 3px 3px #eee; box-shadow:0 3px 3px #eee; } .login-box .new-users { display:block; border:1px solid #D1D1D1; margin:-6px -7px -7px; padding:9px; border-radius:2px 2px 5px 5px; background:-webkit-gradient(linear, 0 0, 0 100%,color-stop(0, #FCFDFD), color-stop(0.8, #EEEEEE), color-stop(1, #E8E9E9)); } .login-box .new-users p { padding:9px 0 0; } -.login-box .new-users input[type="submit"] { padding:5px 20px; background:-webkit-gradient(linear, 0 0, 0 100%, from(#F39823), to(#F37221)); font-size:14px; color:#FFF; border:1px solid #FFF; border-radius:5px; -webkit-box-shadow:0 3px 3px 0 #9F9F9F; -webkit-background-clip:padding-box; } -.messages { margin:-11px -10px 10px; } -.customer-account-login .messages .error-msg { background:-webkit-gradient(linear, left top, left bottom, color-stop(0%,#f85032), color-stop(50%,#f16f5c), color-stop(51%,#f6290c), color-stop(71%,#f02f17), color-stop(100%,#e73827)); color:#fff; padding:10px; font-size:16px; font-weight:bold; text-align:center; text-shadow:0 1px #222; } -.customer-account-logoutsuccess .page-title { background:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#ccc)); border:solid #999; border-width:1px 0; border-top-color:#ccc; margin:0 -10px 10px; padding:10px; } +.login-box .new-users input[type="submit"] { + padding:5px 20px; + background:-webkit-gradient(linear, 0 0, 0 100%, from(#F39823), to(#F37221)); + background:-o-linear-gradient(top, #F39823 0%, #F37221 100%); + font-size:14px; + color:#FFF; + border:1px solid #FFF; + border-radius:5px; + -webkit-box-shadow:0 3px 3px 0 #9F9F9F; + box-shadow:0 3px 3px 0 #9F9F9F; + -webkit-background-clip:padding-box; + background-clip:padding-box; + text-shadow:0 1px 2px rgba(0, 0, 0, 0.25); +} +.customer-account-login .messages { margin:-10px -10px 10px; } +.customer-account-login .messages .error-msg { + background:-webkit-gradient(linear, left top, left bottom, color-stop(0%,#f85032), color-stop(50%,#f16f5c), color-stop(51%,#f6290c), color-stop(71%,#f02f17), color-stop(100%,#e73827)); + background:-o-linear-gradient(top, #f85032 0%, #f16f5c 50%, #f6290c 51%, #f02f17 71%, #e73827 100%); + color:#fff; + padding:10px; + font-size:16px; + font-weight:bold; + text-align:center; + text-shadow:0 1px #222; + -webkit-box-shadow:none; + box-shadow:none; +} +.customer-account-logoutsuccess .page-title { + background:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#ccc)); + background:-o-linear-gradient(top, #fff 0%, #ccc 100%); + border:solid #999; + border-width:1px 0; + border-top-color:#ccc; + margin:0 -10px 10px; + padding:10px; +} #remember-me-box a { font-weight:bold; border-bottom:1px dashed; } @@ -795,7 +1067,9 @@ input[type="text"] { width:220px; text-shadow:0 -1px 1px rgba(0, 0, 0, 0.8); -webkit-box-shadow:0 2px 14px rgba(0, 0, 0, 0.5); + box-shadow:0 2px 14px rgba(0, 0, 0, 0.5); -webkit-background-clip:padding-box; + background-clip:padding-box; } #remember-me-popup h3 { margin-bottom:5px; text-align:center; } #window-overlay, @@ -811,7 +1085,9 @@ input[type="text"] { padding:5px 15px; text-align:center !important; -webkit-box-shadow:0 1px 0 rgba(255, 255, 255, 0.25), inset 0 1px 0 rgba(0, 0, 0, 0.75); + box-shadow:0 1px 0 rgba(255, 255, 255, 0.25), inset 0 1px 0 rgba(0, 0, 0, 0.75); -webkit-background-clip:padding-box; + background-clip:padding-box; } /* Forgot password @@ -819,12 +1095,14 @@ input[type="text"] { .forgot-password { margin:-10px; padding:10px; color:#333; } .forgot-password h1 { background: -webkit-gradient(linear, 0 0, 0 100%, from(white), to(#CCC)); + background:-o-linear-gradient(top, #fff 0%, #ccc 100%); border: solid #999; border-top-color: #CCC; text-shadow: 0 1px 0 white; margin:0 -10px; padding: 10px; -webkit-box-shadow: 0 3px 3px #eee; + box-shadow: 0 3px 3px #eee; border-width: 1px 0; } .forgot-password a { float:left; font-size:12px; } @@ -841,42 +1119,56 @@ input[type="text"] { width:100%; -webkit-appearance:none; -webkit-box-sizing:border-box; + box-sizing:border-box; -webkit-box-shadow:inset 1px 1px 1px rgba(0, 0, 0, 0.1); + box-shadow:inset 1px 1px 1px rgba(0, 0, 0, 0.1); } .forgot-password form { display:block; border:1px solid #D1D1D1; margin:3px -7px -7px; padding:9px 9px 30px; border-radius:2px 2px 5px 5px; background:-webkit-gradient(linear, 0 0, 0 100%,color-stop(0, #FCFDFD), color-stop(0.8, #EEEEEE), color-stop(1, #E8E9E9)); } .forgot-password form:after { content: "."; display:block; clear:both; visibility:hidden; line-height:0; height:0; } -.forgot-password input[type="submit"] { margin:5px 0 0; padding:5px 20px; background:-webkit-gradient(linear, 0 0, 0 100%, from(#F39823), to(#F37221)); font-size:14px; color:#FFF; border:1px solid #FFF; border-radius:5px; -webkit-box-shadow:0 3px 3px 0 #9F9F9F; } +.forgot-password input[type="submit"] { + background:-webkit-gradient(linear, 0 0, 0 100%, from(#F39823), to(#F37221)); + background:-o-linear-gradient(top, #F39823 0%, #F37221 100%); + margin:5px 0 0; + padding:5px 20px; + font-size:14px; + color:#FFF; + border:1px solid #FFF; + border-radius:5px; + -webkit-box-shadow:0 3px 3px 0 #9F9F9F; + box-shadow:0 3px 3px 0 #9F9F9F; + text-shadow:0 1px 2px rgba(0, 0, 0, 0.25); +} /* New Customer -----------------------------*/ .new-customer { margin:0 -10px -10px; padding:10px; color:#333; } -.new-customer h1 { background:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#ccc)); border:solid #999; border-width:1px 0; border-top-color:#ccc; text-shadow:0 1px 0 #fff; margin:0 -10px 10px; padding:10px; -webkit-box-shadow:0 3px 3px #eee; } +.new-customer h1 { background:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#ccc)); background:-o-linear-gradient(top, #fff 0%, #ccc 100%); border:solid #999; border-width:1px 0; border-top-color:#ccc; text-shadow:0 1px 0 #fff; margin:0 -10px 10px; padding:10px; -webkit-box-shadow:0 3px 3px #eee; box-shadow:0 3px 3px #eee; } .new-customer h2 { font-size:16px; line-height:16px; font-weight:bold; margin:0 0 7px 0; color:#333; } -.new-customer form { display:block; border:1px solid #D1D1D1; margin:-6px -7px -7px; padding:9px; border-radius:2px 2px 5px 5px; background:-webkit-gradient(linear, 0 0, 0 100%,color-stop(0, #FCFDFD), color-stop(0.8, #EEEEEE), color-stop(1, #E8E9E9)); } +.new-customer form { display:block; border:1px solid #D1D1D1; margin:-6px -7px -7px; padding:9px; border-radius:2px 2px 5px 5px; background:-webkit-gradient(linear, 0 0, 0 100%,color-stop(0, #FCFDFD), color-stop(0.8, #EEEEEE), color-stop(1, #E8E9E9)); background:-o-linear-gradient(top, #FCFDFD 0%, #eee 80%, #E8E9E9 100%); } .new-customer form:after { content: "."; display:block; clear:both; visibility:hidden; line-height:0; height:0; } .new-customer form label { font-weight:bold; } .new-customer form label em { display:none; } .new-customer .personal-information { border-bottom:1px solid #ccc; padding:0 0 10px; } .new-customer .personal-information .field { margin-bottom:8px; } .new-customer .login-information { border-top:1px solid #fff; padding-top:10px; } -.new-customer .login-information input[type="submit"] { margin:0; padding:5px 20px; background:-webkit-gradient(linear, 0 0, 0 100%, from(#F39823), to(#F37221)); font-size:14px; color:#FFF; border:1px solid #FFF; border-radius:5px; -webkit-box-shadow:0 3px 3px 0 #9F9F9F; } -.new-customer .login-information > a { float:left; display:block; padding:5px 20px; background:-webkit-gradient(linear, 0 0, 0 100%, from(#F39823), to(#F37221)); font-size:14px; color:#FFF; border:1px solid #FFF; border-radius:5px; -webkit-box-shadow:0 3px 3px 0 #9F9F9F; } +.new-customer .login-information input[type="submit"], +.new-customer .login-information > a { padding:5px 20px; background:-webkit-gradient(linear, 0 0, 0 100%, from(#F39823), to(#F37221)); background:-o-linear-gradient(top, #F39823 0%, #F37221 100%); font-size:14px; color:#FFF; border:1px solid #FFF; border-radius:5px; -webkit-box-shadow:0 3px 3px 0 #9F9F9F; box-shadow:0 3px 3px 0 #9F9F9F; text-shadow:0 1px 2px rgba(0, 0, 0, 0.25); } +.new-customer .login-information input[type="submit"] { margin:0; } +.new-customer .login-information > a { float:left; display:block; } /* Edit Account Settings -----------------------------*/ -.account-edit { - display:block; - padding:9px; - border-radius:2px 2px 5px 5px; - background:-webkit-gradient(linear, 0 0, 0 100%,color-stop(0, #FCFDFD), color-stop(0.8, #EEEEEE), color-stop(1, #E8E9E9)); - margin:0 0 -10px; - padding:10px 0 16px; - -webkit-box-shadow:inset 0 -3px 3px #ccc; -} +.account-edit {} /* Breadcrumbs -----------------------------*/ -.breadcrumbs { background:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#e5e5e5)); border-bottom:1px solid #ccc; height:40px; margin:0 -10px 0; } +.breadcrumbs { + background:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#e5e5e5)); + background:-o-linear-gradient(top, #fff 0%, #e5e5e5 100%); + border-bottom:1px solid #ccc; + height:40px; + margin:0 -10px 0; +} .breadcrumbs ul { padding:10px; line-height:20px; position:relative; } .breadcrumbs ul a { color:#666; } .breadcrumbs ul span, @@ -884,61 +1176,36 @@ input[type="text"] { .breadcrumbs ul li.home, .catalog-product-view .breadcrumbs li:nth-last-child(2), .catalog-category-view .breadcrumbs li:nth-last-child(2), -.review-product-list .breadcrumbs li:nth-last-child(2) { top:7px; left:20px; position:absolute; } +.review-product-list .breadcrumbs li:nth-last-child(2) { display:none; top:7px; left:20px; position:absolute; } .breadcrumbs ul li:last-child { display:block; font-size:16px; font-weight:bold; text-align:center; text-shadow:0 1px 0 #fff; margin:0 auto; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; width:50%; } /* Back Button -----------------------------*/ -.catalog-product-view .breadcrumbs .home, -.catalog-category-view .breadcrumbs .home, -.review-product-list .breadcrumbs .home { - display:none; -} -.catalog-product-view .breadcrumbs li:nth-last-child(2), -.catalog-category-view .breadcrumbs li:nth-last-child(2), -.review-product-list .breadcrumbs li:nth-last-child(2) { - display:block; -} -.breadcrumbs ul li.home:before, -#nav-container li.subcategory-header .button-wrap:before, -.catalog-product-view .breadcrumbs li:nth-last-child(2):before, -.catalog-category-view .breadcrumbs li:nth-last-child(2):before, -.review-product-list .breadcrumbs li:nth-last-child(2):before, -.my-account .product-review .buttons-set .back-link:before, -.review-customer-index .buttons-set .back-link:before, -.customer-address-form .buttons-set .back-link:before { +#nav-container li.subcategory-header .button-wrap:before { content:''; - background:-webkit-gradient(linear, left top, right bottom, color-stop(0.10, #eee), color-stop(0.28, #e2e2e2), color-stop(0.9, #c4c4c4)); - border:1px solid #b3b3b3; - height:17px; - width:17px; + background:url(../images/bg_button.png) no-repeat left top; + -webkit-background-size:100px 28px; + background-size:100px 28px; + height:28px; + width:28px; position:absolute; - top:4px; - left:-10px; + top:0; + left:-13px; z-index:2; - -webkit-transform:rotate(45deg); -} -.breadcrumbs ul li.home a, -#nav-container li.subcategory-header button, -.catalog-product-view .breadcrumbs li:nth-last-child(2) a, -.catalog-category-view .breadcrumbs li:nth-last-child(2) a, -.review-product-list .breadcrumbs li:nth-last-child(2) a, -.my-account .product-review .buttons-set .back-link a, -.review-customer-index .buttons-set .back-link a, -.customer-address-form .buttons-set .back-link a { +} +#nav-container li.subcategory-header button { color:#2f2f2f; + background:url(../images/bg_button.png) no-repeat right top; + background-size:100px 28px; display:inline-block; - font-size:10px; + font-size:11px; + text-shadow:0 1px 0 rgba(255, 255, 255, 0.5); line-height:15px; margin:0 ; - padding:5px 5px 5px 0; + padding:6px 9px 7px 2px; position:relative; - border:solid #b3b3b3; - border-width:1px 1px 1px 0; - border-top-right-radius:5px; - border-bottom-right-radius:5px; + border:none; font-weight:bold; - background:-webkit-gradient(linear, 0 0, 0 100%, color-stop(0.10, #eee), color-stop(0.28, #e2e2e2), color-stop(0.9, #c4c4c4)); z-index:3; -webkit-appearance:none; max-width:50px; @@ -951,7 +1218,6 @@ input[type="text"] { .catalogsearch-result-index .breadcrumbs:first-child { display:none; } .catalogsearch-result-index .note-msg { padding:10px 0 0; } - /* Catalog List -----------------------------*/ section .category-title { display:none; } @@ -1094,6 +1360,7 @@ section .category-title { display:none; } .toolbar .sort-by select, .toolbar .limiter select { background:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#dedede)); + background:-o-linear-gradient(top, #fff 0%, #dedede 100%); border:1px solid #bbb; border-radius:4px; color:#707070; @@ -1103,7 +1370,9 @@ section .category-title { display:none; } line-height:17px; vertical-align:middle; -webkit-box-shadow:0 0 5px rgba(0, 0, 0, 0.15); + box-shadow:0 0 5px rgba(0, 0, 0, 0.15); -webkit-background-clip:padding-box; + background-clip:padding-box; } .toolbar .limiter select { vertical-align:baseline; @@ -1132,6 +1401,7 @@ section .category-title { display:none; } .toolbar .grid, .toolbar .list { background:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#dedede)); + background:-o-linear-gradient(top, #fff 0%, #dedede 100%); border:1px solid #bbb; border-radius:5px; display:inline-block; @@ -1143,7 +1413,9 @@ section .category-title { display:none; } width:27px; text-indent:-999em; -webkit-box-shadow:0 0 5px rgba(0, 0, 0, 0.15); + box-shadow:0 0 5px rgba(0, 0, 0, 0.15); -webkit-background-clip:padding-box; + background-clip:padding-box; } .toolbar .grid:after, .toolbar .list:after { @@ -1220,11 +1492,16 @@ section .category-title { display:none; } .c-list > li > a { background:url(../images/i_arrow_small.png) no-repeat right #fff; -webkit-background-size:8px 12px; + background-size:8px 12px; -webkit-background-origin:content-box; + background-origin:content-box; color:#2f2f2f; display:block; padding:10px; + -webkit-user-select:none; + user-select:none; -webkit-touch-callout:none; + touch-callout:none; -webkit-tap-highlight-color:rgba(0,0,0,0); } .c-list > li > a:after { @@ -1244,16 +1521,50 @@ section .category-title { display:none; } width:100%; z-index:0; -webkit-box-sizing:border-box; + box-sizing:border-box; +} + +.csstransforms .c-list li > .actions { + -moz-transform:translate(-100%, 0); + -webkit-transform:translate(-100%, 0); + -o-transform:translate(-100%, 0); + transform:translate(-100%, 0); +} + +.csstransforms .c-list > li.animated > .actions { + -moz-transition:all .5s linear; + -webkit-transition:all .5s linear; + -o-transition:all .5s linear; + transition:all .5s linear; + -moz-transform:translate(0, 0); + -webkit-transform:translate(0, 0); + -o-transform:translate(0, 0); + transform:translate(0, 0); +} + +.csstransforms .c-list > li.end-animation > .actions { + -moz-transition:all .5s linear; + -webkit-transition:all .5s linear; + -o-transition:all .5s linear; + transition:all .5s linear; + -moz-transform:translate(-100%, 0); + -webkit-transform:translate(-100%, 0); + -o-transform:translate(-100%, 0); + transform:translate(-100%, 0); +} + +.csstransforms3d .c-list li > .actions { -webkit-perspective:1000; -webkit-backface-visibility:hidden; -webkit-transform:translateX(-100%); } -.c-list > li.animated > .actions { + +.csstransforms3d .c-list > li.animated > .actions { -webkit-animation:bounce 500ms; -webkit-transform:translateX(0); } -.c-list > li.end-animation > .actions { +.csstransforms3d .c-list > li.end-animation > .actions { -webkit-transition:all 250ms linear; -webkit-transform:translateX(-100%); } @@ -1272,35 +1583,42 @@ section .category-title { display:none; } border:1px solid; border-color:#ddd #d4d4d4 #ccc; border-radius:2px; + display:table; background:-webkit-gradient(linear, 0 0, 0 100%, from(#F1F1F1), to(#E0DFE0)); - padding:3px 3px 0; - height:68px; + background:-o-linear-gradient(top, #F1F1F1 0%, #E0DFE0 100%); + width:100%; + height:73px; -webkit-box-shadow:0 0 3px rgba(0, 0, 0, 0.1); + box-shadow:0 0 3px rgba(0, 0, 0, 0.1); } @media(orientation:landscape) { .c-list li > .actions ul { - padding-top:10px; - height:61px; } } .c-list li > .actions ul li { - display:inline-block; - width:20%; + display:table-cell; text-align:center; line-height:1; } -.c-list li > .actions ul li:first-child, -.c-list li > .actions ul li:last-child { - width:17%; -} .c-list li > .actions ul li a { + display:inline-block; + padding:3px; + max-width:44px; font-size:10px; color:#4B4B4B; + text-shadow:0 1px 0 #fff; } -.c-list li > .actions a { +.c-list li > .actions i { + display:inline-block; + border-radius:2px; + margin-bottom:2px; + height:37px; + width:37px; + clear:both; + border:1px solid #cdcdcd; } .c-list li > .actions .i-view-details i { @@ -1323,22 +1641,14 @@ section .category-title { display:none; } background:url(../images/i_add_to_wishlist.png) no-repeat #fff top; } -.c-list li > .actions i { - display:block; - height:37px; - width:37px; - margin:0 auto; - clear:both; - border:1px solid #cdcdcd; -} .c-list h1 { font-size:16px; font-weight:bold; } .c-list .cloned-wrap, -.c-grid .cloned-wrap { position:absolute; padding:10px; } +.c-grid .cloned-wrap { position:absolute; padding:10px; opacity:0; } .c-list .cloned-wrap .product-image img, -.c-grid .cloned-wrap .product-image img { -webkit-box-shadow:none; } +.c-grid .cloned-wrap .product-image img { -webkit-box-shadow:none; box-shadow:none; } .c-list .product-image { float:left; margin-right:10px; -webkit-transform:translate3d(0,0,0); } .c-list .product-image img, -.c-grid .product-image img { background:#fff; border:1px solid #ccc; border-radius:1px; padding:3px; -webkit-box-shadow:0 1px 5px rgba(0,0,0,0.28); } +.c-grid .product-image img { background:#fff; border:1px solid #ccc; border-radius:1px; padding:3px; -webkit-box-shadow:0 1px 5px rgba(0,0,0,0.28); box-shadow:0 1px 5px rgba(0,0,0,0.28); } .c-list .product-shop { overflow:hidden; padding-right:30px; } .c-list .product-shop h1 { font-size:14px; font-weight:bold; text-align:left; margin:5px 0 7px; } .c-list .product-shop .price-box { font-size:11px; margin:0 0 5px; } @@ -1347,6 +1657,7 @@ section .category-title { display:none; } background:#eee; display:table; border-collapse:collapse; + border-spacing:0; margin:-1px 0 0; width:100%; -webkit-tap-highlight-color:rgba(0,0,0,0); @@ -1356,16 +1667,22 @@ section .category-title { display:none; } } .c-grid .cell { background:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#eee)); + background:-o-linear-gradient(top, #fff 0%, #eee 100%); display:table-cell; + position:relative; border:1px solid #ccc; vertical-align:top; width:50%; } .c-grid.ready .cell { - background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHIAAAByCAYAAACP3YV9AAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAEZ0FNQQAAsY58+1GTAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAEzXSURBVHjaZL3blhw7jiW4AZDmHicz53t6zcv8/+dMd5XCjSSAedggzVWTa52SSlL4xcxIAvsG+V//9/+TGYnWGyCCzIS7IyMhKphjoF8dawUQCU/H1S9EBiCKZg2qwH0PRCRUBb1fuO8bmQ6VhvfPC5/fG9erAwD+/PcftNbhvpCZeL/fUBWsCAAAgq+d7uhXg7WO+/PBVa/bLv5ss4aon4n6GRWBLwcUmPeN1l+IdFz9hc/vL9QUogZVRURAAaxYEAjmWjA1ePBXQAABzAw+F7QpWuvIDPhaaP2Cr4nIxPt6wTMxPn9wvd6AKMIDagpfE713tN7xv//3/4v3+43whDYDIuq9EmZ8TzHB1S78fv7wmhpfq18NgCA9Ecl7lADWcrT/81//jfQERNBag8dC4vmfr4k2+tefJCKBcEcmYK1BBFhrAcnX8QDmuBEeEOHr3b+/8EykO+5x178HH4i6YO5enwWIcACADYNZw7g/sNbha8FGQ0ZCrSHTIaIAAioGCM7nWMsRmPVvDHNNaCpa47+PcASAOVZ9tYQDSE84+P4iBpGAu8PTEZ6ITGQ65vJzTT4AMoDPuBEpsGbIBPiCQGLWdUuEA/e4Yd4Q4ejtgvviDYuEiKBfC+P3A0+vmxcQE5g2ePBFs557j4n27//8CwKBqvCCJLDCkZkID/hq6K8Xb6AHMhO9d6zlUBG03uERWHPWFxe8Xm98FEAkoIJ/fv4B0vHPP29AgP5p57EId/z88wMEH4ZAIjOx5kQCaNqhplhrovfrPLkrF0QSIorMABLwdKgqP8e+xBlQVZgZem8w61AzAIAqLyz268TzCKtaPRCAikFsobUGQNFN4K4QGDIXPAKmDVCgzYbreqE1Q0TwNRUQUVxXxxgGVUFrht46MhqsKdQEpgZRgZnCtEEzIKYQ4bcx1fPNVnitzICvjvb754PIgEBgZshMqBgCXLprLSQE7utsnWs5V5QIrsWLONfNbUEFCcHn80FGwnqD6YBHYLpj3hP3+AAQNFNuDb637eCHQyABPkxwIIDWGswEawG9d35pESABMSAdCDjMGgSANcMvEiqKREJFoMqt9/XqfHDSIRK4ssNaQ7hDxWpXSogYzAymhkj+fq8CM0M4tzYBICrwteCZ8FjA5OdasYDgwzAgmPdEpmCNAe+JCEfzfh64TKBZg+jCfX/Od+Tn4T2KenD29rp8oYkIFFyN+8PxzwypAVWem5EJES4yAS8mn1xBImFpZ0Wa8Us7HE0btN4cAajJWTVZZ9s+S3IBgUBEwJfXQ6VI5U1dayE84R7IcIja3lv24oF7AAi4B9ZaMKsbroBHAOLwSKQHonadMSbU95kzz0rsrWPlggswxzwPsk/uTCKoX6V+NmHgGZ3GLTqTn603XkecG68wVUT4+dyqCpGvI8yjzmg+nKoKE4MoV6nBEBbQFDQRngMqtd3UwSom3N+llnx96H2zBPosdxUIDyeYKropbuH2ISJQ5c+0bvDb0ZrVU92w1kRrxtdpgQgBmmApL6iIQU0gDvR2ISP486nQZrXda7171nkCtFcHEAD4dyKCbr0KugAUMFEAidfrgprB1zrbGK9FO98xg8UKADRNjDX4eTLhviDgrjHlRusNKlYLwGuHaNzam/F1fUHVoLJqBcp56LR+7VWAWt0bMQHAhZeREBOIKwSCFpHwcAgWrLVTgKTzpsUKfNanipGASr2YccsSE0gIli/4cogK1gqMe3Al2oTcggg++RmJOXnBorZulQk1bt8ZLFTCHR6O3gVwbuei8/wa4ZCpyIhzcyJ4fmcG4g6MzzgPZ0KwfH6dnlx4yyfCHZB1XmvfTK2awMww68yOCIgI3B3A5K/B1Z0APvfN6rnO4YgAMmGR/O5rQq3B3XnEBHDPgVgOMS6O1/WqjYbvn8b6QaogBbgriQBRu0sTETTjikwA1gQZhkiHmmHJqPKYB/byWau0cdtwwLGgUITwAGnNWOYLixWrFZiZp6jcW84upRX92aaQdQH4M56L206dfQLA1GCtY80bogqVjjlvtNZqqxZo09qu6umG19Neu48IIhdEDL13zF2wff0flcYHIZwFiDrXhACmHaYKD6/VpYjrxdVnHYnEWgNqXKH91dFGw9UvrDXxvn6wgkXcjQ+aNSTy7FhzVQ0gdU21dggRmAKRjmYK14WWVRBwuwSWOzJYbos7YjkWvA5c/vCpGo2lvAlvHJ9++avSinS4syK01jDvAat/m5m82MKtddaH7u2q8jrRa3ubSLTeIQu4Xi+stfDqDQhWdiqCzFZb03Pm7zO7d8OaeD6jsB9TEVT9wiKqGXw4CyRVWDcYni0YkVgeuLRBRBFLMSY/WyyuElW2U5LcvbIqovRELJ57qsqCsip0EWErlkBkorWGjIConiNNwYd8OneP/QDFcjQVreow66DlFxBlwbMEZ0UCgPvCdb3Y82XCRNhQp5yCRup8kDonTeTsaGoKcYGHc5vIRH81NDO88aoPxwtQBdtZmbG4ha3FYujGwD1vqBvCHZGBVhWdCIudzORRCcHv7wfaGlrz07bMMWoLTPhcgFbPpsKn6pcPwhg37nvsFvXUChGBcd/o16pVtCDW4MKzfM0FbawxPJR9Ye52N0+tkD755/X+M6tLkESzfippvi+gzWAwCBRLgLbcC7VgZbSrUwDozViNSdZ2l4jpmDK/tsH6UlBErCqh8/y31oK9W/WMgTEG1lwPMhEJ0YaJhbkGMhNX54rj31W/Fgk0VFHF9iT8eZ99wyMSEfWEL55nUiuUpc+ztcfpPwPweHpbJDQAbbVTCQseEUU4L6hH1Hn1tcLrxmQEtLWqoLN2uypQRDDmwJoOca6qTMDngvUGeLI/roo1q51ZYyE7z9zvh2n3q62ZwUzxer2x1kJTw6rSf6M9ZgZ3R2uKiWTzi4C1huu64NHQ1PC5+er//Lwx5w2AxUdvHa/rYnWIhLcOT1ZrGYn3zxuqivtmE86HyuEZtT0qPJRP+Npnl8Ka4cKLF0oJLvR2ERnKRG/stVQaXteFTze8Xi9u0QWCrDHhydWze8I5FkQFr965M1nD/fuLfvUDGsTeuSD4/fMHP//8g+kL827ovQCUSJgJAQXVAlJeeP+80dTQmvKa9wu33XVduSp775jrrgdJ0K6G9+uHxaEpYUgEAENvghbBp3tNVmCOxVUJVEHDVQUkVgbGWEBXrFjQtRAeWO617XoVOR/Mer0IwFfg83ujv3/w+/kQmVgLQ1m2r2AjPteAQKHmrBLdsRpv5loLtywgnf1TOjSI+IgqfMNs0PqyDWsNcFffmGcgWmLV2ZQRGHNU1bqr1WpkMjFsnhU358Q73vyO9VmtisR7DHitRl8LHs8+NeeAquFqF0ZOzHvCbGKtCU+t99ZzVAFAQ2PPXDiyqhbyNeHh8OBuJAqI1M/MOaFqmOAPWpXNEIGnIz24rVZzrrxWbEKbQdQgdSN2J0f8VfB+v0FsXdmQT8JZPIN3QZQA+MRGJFojRJVpWGdFGkQWTIF7OCAEigFjYSAGFW5TrPCq0vR64AqAQD2Ufb+3KnvJgmhZ0QJrckWeflIMyFmYbCI1a3vmYedr1Wpb1cRvlOxcSnguWDQ4ApF8kA17pyucOXEAkDlHtYCJEaMORrY9u1iUKqQyAk2VW9bGFPeN9HCYWGG+CXjATOFrt/7gNmNPNTvHhJphTp5vfNN2YCZVq0O++rO6oaas9Hrj+19XR/qEq+DqvZAmIkStGV7XhfcLUG1wN1hrWMuwxsD1uhDuPLNagwQLrdYa+qtXAaYQsG0QE6gXtFhNdiQZCTU242LK62PyIF/VghFOtlpNBX19sQ4C9suZgesqIETr7tYREcld0kxhraF1Q4RiGYH2jZSp8EEzM4DHO1EhAO2L1EBkAnX4NrVz2EYUhFb9lWk7B3cWxKTCC0r4rmG2RtC7GVonMNya8vdqpznn+aZQtXo4/LASfxVTaocRWU781TpIazmxX+K/3HZNyXaICtZ09L7gK9D60yZFyoEZzRpcHL310xpt0CCdRZOJsSeWZ6V1U+B9obeO0MCQPPjsZo9Q1fv+33JH+DpUmvvkys6ApGMM3uy9urn6iSV7FWXEZO1U3O39fhWrEHgg4NqDqwLPxAOrCXC9G3QCvV9cpbVa1zS01vB6/yCqabteF8w65vXC+/VG5EJ6wqoiRFb/Zh0Zq6pl9lCf6pd4hi+0TkSk9wvTBzTt9KsiUuwAzo6y/7zpA+GRtVlwS+SuGIsAYPtA7LfVDd0rwMMAJTgvXzdSlRBmvzrmmJAleL2uKqYMqvx51YbWG8bnF//5z79gAkgVkVe/aveyAjykWrAN0CvhwVpUIAv3dBgCtEySwueGGZc64R/+h3R8PoSI9lO/aauIVsxIwxwklxOKe4w6dxoyJ3wtzMlzJuuARwaiWh6zAq8LURmTIPU57PcFcT07gCq3Ji0A+k6Sr+EECKYuAFKFiDwXpfFMhrGftdYO62GaSBUyGLU1eh0Tp8VwwVoT457oRXJvFmKthc+fu9CsiTFHARK8CXMufH4Hxppom+EpvDbrAZQiyO9xF3vTsdZ9uNbDdWactqv9n//6LzRt2DsuuTU5O3wUb6MHqUkkbqRz+zNzsuUtqzFOBKTOS8Xv7y/MDPc90K8P1lrn6csgn3YVz1ilEpY79GyphOVatoOSqikkCnwvvi+C+KM7V1RoreJaWV7cIDHOAbMOJC8skHBJhDuW+nO+iUIFiKzqHHtbc257xVqMMfiekdwmoUU6Z5ECDcsdrV24542+Lox7Ipw3f45dBM5DPogpxu8voAYdT2uyvwt3nwRABqX959//PltSbjrF9AtaxgGkzQy+WNBk8izs1jF9wqzBDHi/Xvjn3//BZYpAwozyBPeFn5+fAptrfSipGMJ7PKdQ28XKR8YQAawxkZ3V3VosrFprmO7o2s6NzwyIsvrbDXszMg/r6ui94fV6QyGY7lAD5pxo1smiqD2olTWYsl/dn3vDZLlJa2E/SCLaMSZ7VVWtmyy4+lW1Qseab/z8/ANfC6/3G+4Lr/46r6mHSQI+ZsRerZ97wqNgQq2fLT4RLHZEBV6448FBCwVhNacI2czGgkRil2ZH4+M8f4YutHviz+cXCoG2OKt5jAn3Vb2YnrPs85lsjsdE6+2U7Yg8vB+xyDwIUdRqGmOe8i3qJruThxz3OIC9qJ7dAHXbRQRzrioguJUach9BQDhmeN2Q+7wWiebNjOAoJ6ozIx3WL3QzjANtBlSDEpkv1IZFDHtafs52tDgbr91nMWUggLWO0zoIYDDykbuhj3yIYwgQooi5TrWYatzqzLCpcr64wguyI4REGMmRfAOA52KBDHtFqhpa6mkL8hXnz4V4IcwaetFru79UVbSSbrgvnnNfDE1rDSqK63URPaqfNWUBsldqZqCfSvs6/CUg6KKE4VShplBFAdnVotTWqsYKiJ9Z4TJg/aprlLh6Q2uNu4QR5hMTMhh1HTfRLNWOyCXV8wrPFoBITuHDBN0UiYBCMX2h/fd//YH1DlOW+r7IS3IrJV0zPgNSwqBxT7RLEXPi9fMPiyJ/lGxEOkpctdEhAGsRAbrHBxkUV1kzCp2qvF5jFHmKg5LockS88Pn9g+u6asUl1hwsuhYLmoWF5Q6Zo3Q9rbZh0lRc+Tf6dQGf++CtaxJluV4869U26yBHfNVMMcasfntzkRQ/td4w7vvgqcsnmnFnAQT3+KD1gAkLv/v+4PP5wZ/fD1Yk0hPtyiIrGjDXOYd/f38PM8Ueuh3Wg7sCquATtN47+tVPGT9L4rB5P1XBZY0lbu/4WENrhtkUr1dDN74g+yNCUdfrB2sOmBLDhQi6Cf7zn/8Llwk8yV5YFSuUDwK/v7/PueyJZTx7+9URcaFZMfStI5PFTiwrUNwK221YK0qmSeYlkLgaFQOqDT8/7yPbGGNg3OyBKSxrR2KhQiCAqzrQ+kU6zuxsa93I3Gwg+x78fK3QLdE8bZqJYdxv/PPPG2veuK4XgKBkJXCI5Vb6oW8xWG+df59VQBXlZnWeto2RZqH2u8yOL57Mg2falcnV5jz82cSzT5tzUhMDbi0IwCMxdEAKj+z3B7/3gK+FyIAOXqgsvm+MeYqTTD/leI9eyBDhPzXFdb1gprjeHeFs8DOIfrxfBlWuwqa7/0tuT02wxir+ROArCpJ7etDIQK4EOp92iHC1RKKZYnnAS8IR3z0rpKrbBdOG5ZMoV0qtHMPyhT9/PqUUsOqLARPDWvNU3zwTS19siuULCDkrUoT6qt85ARU0NcoSdPeIABSswswSvREhYZGpuK6G3i7MWnEsWKh1zUiM4YD84jPuInX/gV1Eb1rrZ5Uj9VBere8HQw5Q7BGlFjPM+lyJ+nIJxFqYkAOaJ3imiTp8JawEWzP8ELVcaY7o1NJaU9ahJgeE2IS0tjrDhGdmIuBJsfEWb/PfEraMJDSnZuj1sLFwcVy9nSLpfV14v19AVtW6HoShwRDVYlPj1I7O1bQd8N5Kv6tm0HRcrRM0v0SBOoghfFZNyZx7PhWZR8A9YZbwSGgEWjET5PfYK+H0nrxIJEj1/Dmb2EAmP+BeIZSb5ClaYi2gKSvDjKo6A62RoNVsvHkRSFUy8BL1UM1TwKkA2HITzxIZs98cc2LNidkKN90PZCx0dki1dSZEoooML4KB7ItXFa2i8LVKoceibS4HhFojNcMYA/jzB/f9wVxBvc1F1Oa7aLPWqM4oLY9exofusO1VlgnbnLalEBv12Kp9L8Y9JkvZVU3/igX/BHI9uKeASz/D2WjjURXc9zwoxc9aWL6K2lqwxj4xrxdXz32TNjO2CMsntEr0tWYh/YlZNyFsQ/oEMjycbAsEnlpwssEzMJfv7g+bUVS70MIxcLQY3CyFzxz1Qr4fLe4EvhAbAQuHaj+gibUGXasE0ay21xrciTYxvQzNGrx12gxinSp7+QM4mCpFjNU/7rawmWHUQ+r54NLt9bpK2FNyRiutS209pqzYrtfFD+ta0oeJ67pKeRdoLgA6fl5vQI0Nu9kjWUQvNbtANZFmxbNVyyGC6CwieLALpEBsax3j5p9noT1zTFzXiyV6VZduWiLkWn1oB6VSNWRcEABXa/CQavQVTdtpuVi0kKiW0vtqQUwJYrCRftifDBYzyMDVGvLVYdbQekdTRZusesmk8Eb2V8c9fmFNEVNO7/xz9QLSfTeRZHKguBolkx7BIqfu0WpsexrJ33EazswoLwWqsS5Fs2dJ9xdXyOTfZc5qqgmGz0UV2OdzQ0t9kAmM+wPRxj8vodcGw3qdCZYsfnav2hsVZ9aNhp5XtUmRMAHePz8Y8ylS1tzbudYu4oX9slBSkSPJhwJNSXXF1flQVgXsHnhfdqpEiEATQPD7SBVn4YHrdWHcN20Twa0wSkrqYiX9bIhYCG1U6c8JX47//vMLIDFX1Os+as3WCMJTgW743JPi6PhbnL3vUVMRNuSgkNgjYVpA8arD3BVpWUIqQlakYeSo1FQF2RLvTvlHOhGWXYYDgX/9/OuwInMtSjEKKMisPlT1PO1RzXf1AnRBhfKhS26PlCNSG7N7t03TvDr1LH0XLRnwTLR+wWLC+gUVYC0tCIyl8RZfKQTaG3er4HFD6Ca+VOQ8u3cJwG214EFhEWUmlGRcDfeH8pnW6FLjRzWKvKvoWWviuiinWXOWV4TOt0CUWlALVSM53jwCughif37J36VqIfLxQHAZhW1SaeaRaF9w2T7gx3KsuHGPu7aHBamt9p43xj0hxmqTwABVZxvCU1NgTm6VdS5a65jjhveLVj013J8PvM4xsgCb8GVnJcYb4uEYamjGFigTuJUuJ1vBzzVmtUCAGQshDSObj4RCjrnI8K065EXUQoz6ZcAo6YWx8p+6a5AvxVQVUVY7H+zBV/f15u6II+SOTDgSs1amCj+DOz9jm4MmnVZIvA9WV6paTwMryViOiF/MOY6vQkctd4/Dyq8FCrIWe8+FhatzPz+E7IaiFJDUqgA79/1qQbSq5qwLqKVdDUht/+zNWGfYIV1TuZJb1pGQWXITOVDkKoqKyM8EgmcRlYJEbsIH8BdxkEc5uP8jBQWMexT1ZliThqX7HrDecH9+0cYqCQoZot/fD+4xsEptsLWrreipNSYiQKlmve9cxcPO+7Q5qkK5pRlaZhy0P+rXbanb1VemIzV4juytDsGiRIHWpFaks1Fvhvznn2pHOl69I3Lifb2w3m/0d8P43ITD3HG93xAR/GbZFsrt9MlAbxeLkuBTK6AZ1JW8XeSqG2tg8a0wI4MDZ2UndfiwavQjQVEticcmbOvhCH90FBseAwpQ2HxB2RTcHStYZWbqqditEZywwn7nGFhFhEUEorDT3q8jRCZBb9Cmh7jmVu/FD3vd2K3cQNUhDc0aaRcAiIKi2AhLCYsF7vzQGVlbQOF/TY9dLdSBVPRekF2CPGOhRFpYpJqgiSL7VasjkQggCRjoLjDw9G+ReThMFHqSyfWqdf7UHsXKEFrvx/OcLL4i3BAq9blKC6MNeUXpiViZ7p1ha3NESmbS5BQkpg1zDVzXhSgbRW+tZCHAdb2gAsyhpNR6g2nHbUqXV6fMtDWDoExUKkfT9NocbfWDvV3lZJPzoHNn4z1qR6dZ0o591vAiR9kIWKlKSR2whVdl+JEqODISmGxgPWubrrZgzoV73LjHwFwLs+A4NsIXHcWDTqbt9ppzErcsRfXxPXRWga/eMJcfQylJbwqqlmcx/o3+FA0iRElxMb+j1/b7RViXM8190rCjit4U6GzmKddkK9H7Dwu7cO4Wxt53E9Io/VLvDZiEEXs9rKpcLM1aud+K2fjSC51lWQshPOth83OmrhUQA9pG+dUI0dEpLIdB2G+2ch7dKDmxr9VbCIMoIb0opzGqF2udKjczRW/XsRFsx1LrJEmb1Z/x2yCCmKl8VXRzPVjwdDq7Ih0mrXa9gI84bQ+q8EGi8gH8q6ggvhzuJYIi2H76xLrpBBMCVshTeuITo6roZBEVPHpikdhey0tQ7RDhZ4wVrEN8HS/E/syZpUYva6IXvbU5WK+bOdeAL0f00hMpK/UW/uWRKNWaqVaRJVAB3Mt2rvlIHXZBEoFYq6zb8cVLEsGhytox5yIRPG+MKfRZ1AMntcXMewD6ZQBaDtGFFR3rHpDtH1SaSaMUdyxTKaaif7EdmG5hAU7AgAYlx1oBFXKAczl5z70FN0VMP7TUA4exkj/vB6BbJxkvgiwpo/mDIEXpax4RVTtFUqYDTrBkLYdm1hFB9cNLrB7a/Vw7VjxkcrMtYS25J//C4P4FgnvxXSuwkqCzZ0KcK1atETQv+oaWAmKOkOIwy8/npfI+bH/Z00MpcdyrupdKLIOGTlq171LBGdY+M+ub7CLl20O4XcMzHqlh022BA7we1ExGPVjguLH2SnWJckVTtZfFynuw0NnskFoZcqLw6AgKsuZNPbAVuF8LYLutw6n34U1KyByE8fKFeuJIGGx/yiLAnyVifo6jUrxnkNLLZAdV7TCAxHXxYPVFBloA3B/694AdwNBqteF4IR+JYB3wTRnrUiru1/WiZEKtCicAZmyIEwcJlRR4rqOgs4L6IFxtWdtS64a1atvcT34+T+2ck3rTim7hdqunwlVVlBjwsAv0hVLzGhFHzR0e6J2vtdFaL7X7rheytm8cHWpZ5IpBUSsFxb5OZf5p1ovEN56tQd9juDEJpR4mPXIXqRyFjc8qmlhJGap0DlWMOdDRMRdFSZHBXqVTRpgRcFlHC5NfZszWafjsna1JF8P76ohSk1n1qBr6Lcd+bpKQa9sRLVkrbsvBjssAQJMvaqz0tdupJUpstl92qtnPiHIEN4gJt+DiYK2yd8hr5jlPm5VnH1WkhJ7ADO0UjmWSq+zNEK4n+eTqDWPy5mqJrK/e0a0j+gURIeEtf9U1x4pAaSr7bVM7kk1HwlcATSoCB2i7b9oWsFgB5jpI2dSiGAsHhtEYs7U6SjhpM+YeTj5NgM/N5A5VBURxj4nf+5f4YeGUckppQAJlUPFjuolVv+Yjjyfp4lhz4TMm1qqtJayccX5alfAABqEza6ycwwPeApIAGmWb7o4wtjLqVnk481Bd29ch2R5Zi/DfaCpWSSrN+1EXhAcGFtmdOmpaq1UKP8fAWPO0FLs2IBgfx4C70Z5jQg6KyzQJsBuRq1pp9VS4leLZFNl79YlAuOD17tzKSgrfrwZzNryRgQsNzTpbhj968mJav3Bdin//+9+QwkdXNeaqvFGtNYRRc9rKSjfnrMe0+D4oi4SSaXAre6yNvB5WbaVBlJUpAEglhbRmvEgJtIYjM2xF/lbExlcgBP0pPC+f6rEZsMBtP3sJmEugJcprtVUXIswFIAm98Lquo9TbW/HO4tGKebGmEH/Cn3rvMDdaKrKV/JRiNDMj+8H9mi8iToiNW9ZdW4zAq5rU8iRIVYeQHR3GLXmviLWCPY4uvAHcnxtXnzS5qh0skUi/HsEtjTz00r9fb26Fr+toaL5dzO83ecwxB67W4bmjXhopnqW4ej/78Y44eb1eWM5YMSIuwPXqxdaX0lAfjlabQUHA2iZBg6t3aFF66X5SO/b2SE1SOw+De/CcL8jRM4j6dCtbPn/dPe7xxAihRbl6JXbxta6ro9nFh0eN/kguWWBOP/6IfjnmGGi9H53IBrZ376hmGHOhl19wR5lERjmLKuqlfBDhC+GlP41HcW7aYI2V31wLsknrtQqT1fJuOhQGz3Ugte2HuHOe824D517NflZu3hjjiKtJBrPJjwiMr+Nhx6GMdaPnC1LoSgZ3Ih4JilgTYTyWREsjG4vnuemx3Af30yPcsmaQ8Zz3EQG/44ia6UklGTAm3eE+4wsnTsy1cF0l1O4dzXSjJnFaj2atKrKnCnws23nA5j4HvRwNWNNrF5hYK+Fr4MKFBDDDMcY4armTt1NaoYcU4Kq/rjprkpQaXb/cqlQEGryo//z8g/BZRUkFDO7IEiTmQKV5VBDTYvXbOumz3umijuCqJ8eqx8GlSo3SjkJxBKyI5VN4bb/oV47QvmyrmvqsFgFoxfgU0DAXXsljTZQP19YQl9SBYMpcdGvtFqlWe5R3NSPQtts2Cyh2eOlILupPykgpWrFbu15PYN6zGAU9b7Imz6xZJhxRQS8iem+DkKuomkBTKyA84JVHp8VmLHdEKDzHX6ZTXwsr2OfRN8Gzz5094i7GCBjgGGDnPYCpCAfmmvAAfv/7t2LH/LHcl28/K3gCua0PoCmnrG5rLagGfC4sAQLM49n4bCZ51zUJDU5P3J8/EGMUjKlhlKv68aUGfAWGrCKvqddN3EABLkDBoZeU4VfQZBf2ygAkLApls1IQDeTnxAnLwRStVHRqBkOerSuD8WAbpN7QmhQzT/Ygjx6m1TnUSrGmEVDhvq9Jl7DU2cibUQeYUkrvO+KrWqCdFJnpFbKQpeNJiBlZilILbsbHy3iTeR20ir5MSliIALVTYavqkcVoSfld8ngy1pqlugMuvEhAgzRcbw1unT1sEcNWclOkHKW+54JUmxMl/8+jVCA4A2PkDXHZRPvXv/9Ba8r9PwLjFlzvjmaGuVr5QShtt9aBboc+kWMStfp5wbuYlA/ySPWv3jB7x3ubVt4XpL58BFMmN5m61kK/+kH1ZynJMxlQeNTQmQdc3pkB4evgt3sXEVFAoxzGtBq83i/oZLhgxj+Y88bPzz8Q/J62KuHIpFR0pzburYhFnx0NLliLcbtLxs7sn2lXhzhVAWxj+HrXdXHlGp1mWq/p7ljN8PPzczjSrMUhZQ+YYzCB0hr1sSpov3/+QIo/2x54nQsZxXqU9S0jIINKOVJbq8pxFhdReQPjJkDMftNK4JT4/fyi9Rd+//zSzEOBTUkwn150yz4gyaru1VghVgUnyr5tquBf//rXKQhY2rfSoSbt65G4+nVESyI/5cb6waeabDNBJKvJ988LCi0FoZ9w3UMoK41G2klrhTvWDsGAIrGKyE44Ak3kRNHsY2+MCdGB+/Mh9qo79EEeUD+iFoaToK/Mn2b0pW6AJpNZA4CiUapX2huVo4g6u1hr6JW8gUjEJvqTQqWXXAfd2Fa0CMfvL/tFNepTXn0/gVrluPyVh7phNw+mdWxzSteO318KtrL6PS9Dafv9kE2Y6ziGN1S3yp+4/RHhic99Y4yFMRj++3oH1pgltawMgcpw9SJxbWWhPXzvuXghrV0n62cXUxTwM5ev63Vw3Z21wIqWrdb2dDDObJ0Ai4jEfX/IU8Y+NuLsDJEBTJwYF2bJGhqNmBdWLuRiSnAvcZS7o0EwfD2i4QK+I6jTQTesRSrI0/HqVEOH88ZLyeRX7GQQroRZBdO21yGY00p0QxEg4E1SlWFHV6UzIg2pyS14AW7sF7cRaX/JdKq8IxLttQOdGq5Xhyor1Q8SqsB1fWUHdNrgdjpXJgAtvLPI7qzWhnkFcsTP7vEX7sutvvS2ehx9B3xggVXt3c5RLRA/4yuZGgp3hg86HK3iWviwgyuSZpAG18nVoMQQv+1nIsKwh0k6xjdGqwXDNWbB9d7RX4bxYT/Z+4XX1RGxKJWvoCWmUym3vooW3QgB/ZRGDnR5icHo0dgKO4b18XXDHbdNzHFXxErl1yCx/MlImWPgegl+f6nviUApzW+slaf0F2WJHxm4rnf1evSHbN/KBUFsuaizP7PKvZG9o2nl3R2luv7lBNekgmGeahknBGMnMjN4yYn0oMExoFnkdvXgANAiHJJ0CjGqkzmiEQ8bwh4msT6fk3265kSTC46oomU9HzIS40Rje8kzigusBjdKtQ15APS1Zr1/fG2PVSlWCH5UEdNaL6bFyuWs57/MOE+1Rjve+9xOpvZC5qRoul7DmlK7WtSE1GfubWeIK/r1wqzPuXcolIUgC/jY33/nyM3B8xvCIHqPL3IZAtV1ol2iLO3hATev7/NX2gsNRMF7tI2yyKRmh1FfyQossrYZOalXqlamGJbEUqvw6u3s9aOQh17Ntjul8dzb9SvYoZ3s9PN81vmcYUjLMqvagQ57609iYrUcHs5ApkysycCicVtBbPL8m9f7JE1+Ph2tlwXcG5o2zMm+9P1+8cGp9sYX+cXXuyGjHYYEaKXGb7VSGD5BizjP0N47GaDWjlZ4P1wv6XV9Wai11qEV9Q0RpPCcfr8u8pLOFXe1Xr6XxoTMq1eubSJiUQ7JQF0i6cvXUYk/Dq31oPNldV7ujDM7VrQsLGqcFeiVHcLUYMOKqPQKqwdEoGXm8XhiLpnbnczHEXoYIZRQ7lXqPqGVmDUnV8xY86/kYoIuo6rWxPRZVr3EGPdJufS1sP74YR42Kb0K1NjbF9kdIikrCUTsnSUiMXXx3BqsNPv1wv35PWFM5D8HIhVjfAq4QKWi4OtsjJOdvpaTugoc/e5cE/hwV0JWpjmOxpQVUAuG4tUEBpg90J0q5dQn+alUbXpSNSod8rATeUhYSjB4Q2kzux5esTi/LcX0WIe0ZkVHQpeOphIprRKDlQss04onzUdz6gFvSYqstLcrgcveJ0ud8SuOq72qDchjkWMUWTv6WVGcCy4qwKJ9K79SrmhJlEOkbGvg3sWsOEkk+2ZrrMJRfWRUAaVGgVlrRpDmK5entY7rehNYzYRRPd8q2Edw3zdbEQW6GTNH8cz8YCoFS/ye/WzJ1qgmMOvHHp1oEOjxbyxjE7vmYGyZtcq5+4IWRc/gFDWBx5OWLGV03Yq/qHA9dwYQkjGpszDyuSk7oXG7pWpYihZrHwcP3eklcXz7GcxzVdvnMIELd4edIOL4Qlgqd2+rz8sufrLwikD3WHXU5CHOdx9tJgjXJ42s4kTJpFQYR71Ua3SaQRq3VjV/0jLCK5kwEWsLav3EtYz7rkRDZnFTx6LHNkAqLwtzrGk2c+AeN/58/uD390Z/dcx7fgHBlCZ6rCcGxbfSXY/P4Ul6JAFsjQyLgmeGfA1v4dgJx9ULw4RCK7LMmsG8cWyEAHfZy6cDqlFhTMAnHa/r9a0eeSLdvnJuHnw3apuTMqzWEbFmqeMqQ+i0HE/C1pwLZlXllls54snuiR2QVBjy8lUhxKMW2dUOjhe1QuiSjWeZn7AknPODZ1/Z1TebHQ74pqwYtbVcaB1n11wqajmN/W6ETQ3qUjIKOT56K7UYNTUnTee4l/b7aAmXqS99ctuy7ZUeWKVN0hNmX4B/TcHZYYeExeTkstL/rxsJOeF/Yww06xh4WqeGdkIjmghwMTso95CcG2d8xg4X3DuiiDFmNahHinJxq/Q9Y4I3d2OwlSzIf18iHlFFXJ3mnF5mzK/5GuQGBZFUQCsE1/sqMvTJdMUpwXsZNBlNcl2v4sJQiVCkZmI5TGsEhFPrgqKpzKzaDqlghCwQGmcHkS2CuvKIdhn7ZRDbnN+WaepXSvQWLvFBtBL/rnVDOo2ypdfDiXQ8sNk6OTeR/ljvVI+oq7cO6w0y5WzlUczK6/XCUMpMf14vfCTx8/ODjMCrM6u1vVpZMZ6kZBpwA8sZK9OudpiV9uf396y4HSl9eYe3OOG5Z0KAWDEM/NCzvArUtnBLfu3xS/EE2nd0oiTl3LKwI2e0Zie9mBN+yB7068KcCy2BWdvKGUxWvybsPBT3dNzzPtsyCpBP6Gl/trsXUw7wHElPRzSqGiKypghIfSeCJL4Lo68pROFef/fk1GU8BPEF4PMZzOGpVK+1vII2nmDdOQZUGxSCe3Km2JVvRGUIZWwTkx7EJ150AtD6nmhXb/QeWJXCa9QTXSMK5FEYUvanJxpzJy5qRaCcASQifyX8C57IF53MGGeIPV9jzoUu7ST7aynDUTym1Nyorc3eMzZ21tsOaYrlx3KQJ9DJz8rZmTyt9XPGZR0dLMwUt2/0aB1EKDJPAOB559rSdmDS9mN4tRmt4l6Oqk/YQzMLtmFhHyscZSFfzjPO8FJkCnop/aQqXksqEfaUrKYGEecAF1EHws6hKkqPBVs2q4N8co/O3XMm1HCMPgxyqOwbj4rRpqpuyyFysPfbZDUSSBXY2b0Mw73y6YDX+31yAKwMQ3OOw7a83hdiUV7/fr0q0G8H0xKpas3Kjjbwuhjz0q+rtn2DOF3Y1hiVvem5qG11P0A7VGJX273bcaRlTZT7Ht3UkoA38/v8zFV5cmCrYg1WnrplKcGobrOqhmXLT2ju3eMmNpQ5E1AYmvuCpSGtZkdlNaHywG4nMbLK+ggKiTwcEsQ1xxrVTvAi71w5Ec79mGvi3d4I+InsWmuVQQfHLoYy4syRx4lslvC7VkcJYGg1K6ZjTrqj5izrXJyA+mPGdW5hkYHXDpeoFuME7JYST03hs4CHWGd0BSX7WQkjNMmm4wy18RV0lm2osrjS9MSUeSr7KfMkhREWDYhExdY8VN7O/ouaPAB4jSEscGIVzIkawKBWT1U+Mp2DqVI/gTWLAysQes2JsJreU3jn8lURYtzS1tx9Fa0Bc3FaXWtsTwSCGQtLA4nrnAffgbNrLjj8vMd3VMyq0Cbf1oEoQ4zSmhfumCLA9VS7WTqguV1oexZmROWIB9SibliNyPCF1vsRkO3jpFnjEJsKotZmiPLntEbgQ+CI9gQXz3vAWj++GaVx48TF8RjR8twwz0EtT6rHl1u4LH7sbZsvh+kqKSHdyoiEFBlvdVEccfZtqekv2+q9DT+M07wQsZDBIsasM5goG1OjOPGsxhQRt7U9pa3OvsPxfaX1b8nZDutDs8odV2QwB13wzykg1pq1ffbyRvKckaqy41jXtjykFIDyhdzUDUOdT1rfX6vh35MJVtaYpXz0sVkhUl6I135AnZoQpopZUSb+lXblOwtWvqbqBUzzFJX8zgFpVnoiReuVXmiZuCu7fENK6XkkDBJ+/AYQgVaAxOv9Q2HR4tlB72LDbLP0LaSqkIl+NQheUDPc92mx0Ywi5maGVXLBDbGt2tLbtqXXLC2JR30XZfXensutLMsIuC2IVyTb+gY+olCVbx2OIVcWgmRlk2gcOZhJWWQmpNIZd0+Z0U7q5Pa2ENGpFMjW2cGqAHNjyZXFV+3aVbNQtILomzW0yhxakxJMgyKWlIVRT945EpyNJRX2ahU1ydXFVbQn0WwBFRMYWBhsVOLJt3/GFzG+0g7ioQW/uT0o0YnCLEniCtq4c/3NivhhwxWwoDALJJZ3vkDvjKTecokAJ/y0Ci4yjSP5bM0Qyb8zM3z8g80XSZ1TewqCqQGNfSIvZoN3giV7JCPD1Gr0U1VFnotb7kl3LDu540R4b2Xe2fKlMR0wnyFrrZFluXp/rBZVhHH8IneaFrHDBhjHjHILeXidQZz64j7R+xtzfs4IpetdBUnxkwxaJ4jNfJl18to4ACWqXQBiTdzJfJ85vcZM8Ixauv5KwJCC0Bx+5guHk+s7VM5OIAn/kuPmk0qCJyx4h+YG8szUyozjyZC+41iiLIXOOSf9Qkjiyl6pITXeooy6aooXLs5C2dFnLU5CNPvmxi3e5CjfkVYTA+gb3Up6r9DfWCWRXLOyB3BYnLUmnW+vqx/ub5V1i15+q4mou5l+4fW6MEY7T+vruqAbK7xvJhhCkNjT0PVMQf98fvHPP/+q2JaEr1aNtNaUAcM9qsnWGuiyc8NjPSvyaIIdvdoJfFu18aymPVtrozCs5xLSZCeV1W60k0YMjnlYmG0BCN+S/lKFp6Nl9cMsrw6O+4ya2D5HDgi3siSe8nifoyB6lsYz2NwKEGnooCz1njfvkQHL5O97NGt1zhNMW0ZMJCIHOq6S+Gt9Ccd9D3Jn++yi2ewwEHsWCIeGrhqMwiGcv58bamRPtjqMT3FCnADEvBeD5nMhF04xRLeTVMPtR9W2KgLGY50p51ukldW6iAuJ4dq2tVzKnlEoyxc4vTWwkMp5i8M+HLsbHlX9fX/Y3FfaxyVaBtZSC4h8tTdsQ8aa6I2zlvGi2M0rtRLTOaqpGV1km3FZDM3P4kP3PbrwKtt8chKPqqLMxhVQ77i6nOotCqk3sGfcHklORF0n8eOIiI//npJGqzSoLEk/h6Tk8RmaUW9q3Q7aw3Qt3nB7vXjONTbXe3Tf1aggy+y4XkzR2JXunlmiooT7nEWDlfrAs+F1vTHmgKrifdFp5hZVRTd47RZUhVPj2mHPaq1zmiUBZSERXqA4USYv26GIIi0gWnEzq53roOWF1NZgS8+4Ro9nsq6qYMYTZe7Lke3RI7Xfz6fmMc5KeSIsd+soXQmNNCu8hlYX6J4oQywV2mOOikPZUS5fD0HZdymDLNHtV2eBABaoY9mUVR5tjEDS4aGISUDCgxoZVeO0gMmfu++BI8qrHoyWv9oF5oTv4NxYmDNqh1lASg0RiyMpGZNIF9EkPWU/Y0E5AiNqu41kATSXQwpPBQbGuNG3hX3PtloNn/HBLE9J7sBia2dyD/lVFon3/UFCS7Gw52sq1EZV4gvt5/1G7xfWspOiSC3JheVfuOpEeRYXnrEgcoADqWQQraB6r+GgG9JCJWStQiJiE7QvRWgCXn773a96Vjy0nKzxbTED4mCpAM+v3hpW05M514sE58wthUExwiHbOKStWgRgIhhYaFu4JUhj/k8rD+KGEKUR+hNowX/9qBpOuG+RJawP2rG6+zH/aAXiVxEUdCUTSuTfv/pVvT2jwt/vC9MAgOmTCEZzmxGwaQxvbyeG2nSHP+BcLHFK91uNIdzstp1mfavYvivNXWLbueCqOyIl6omiKkwsT3ozaamAtBrUubit9yoM9uiFiOC0HzGsxbD5z2dUSyDweyJ8onUiSuFkc1p/8se9ggHnWBAZrJoz8PP+OcTts1VTtbB3kr2988HMIob7ycoRr2FmEKJm2o6ON3OdqQRZOqWtYSXRr8dmvtNNNiQICNxqMHjisZ5/Q2LIPLrKnWawJ9HFcqyq9hxEIbbCLpL60J4dYVFbL82cft+I61XDNf1EqETRYfu8lVLvnvy4r15zz2KWmjqzZRE8p8q1WxkAmxk5sWV44sB6Z5O/kaOzqqtA0hoRwfCHPO3MnOs4pLc2VWos4xajHhlIRVv30gR77UBZxl4PR4OVBzXJxcci6IFHJC013IbzpXEyY3FmSspfuQPt/b7w888bzZ6wHlH5a64ypQizXMDz9Hj9uvC6mCP3anqYcWTi9x70Jgbw+nnj1wT//PMvfO5fQPTM1rKKyyZNGABYYV69Y27SaE9sKw2tqcFlT53N4wpWwTO70R1aExSaGgJRkTINe5Ow7T0BCV03YWymKYAOTBLebM/kKA2zN6ZCAmVMcrToeL06RgUD9oso2FUZszuH6C7zcO+Njusi5ROJ1/tNML+20zFvNDNELPz86w1tUqq+knkVfqsA2j0WIDfGuGtE0N8OpLOVVDRXeHkhMoCxABmVSDVLGkKV3X3fjxZ0DFaHo52tY4uIHNTfiKCCHfgED4wTq7bDKpgdu0onQ3uempz2Z51osqyGOSBzAh3PtNasvrTU7FSqB+5xn5iVkSwiMrzCb1F28xfWmtx2lQzJ53OfFsBLAR9RpED1wrtI2hYDvSfc6S31GgshKpjr0eCsavZ7v+gS/9y4KyqcqWEN6k8ZS6zVDOjtcI1mrYS3G/XAIYmfIgPH/JPbm1D+wh1Zuf+NVQPbWz8B9asmIdmORMkNFVpBU4YVpJooCNavjLaaflO2BdsR2MccU69Z7YFJeSNrRoePdYjh/Xk2l5jlV2lN4I4zVzkyKgC3V/pIVjDhxtTKS1lFkRox5m13P2D8itqBrGZvUSCdezh5Kpp1vF8vLGUaNJ3d/DWrYGsVh72PgBrNy6EnTLkPytvXqpF5HcsrJX/5gYhYVPQTt7J5O6RVyc62JWrk7BwTKjfGvCkZqdBYJkbqOUc26yBlFiV4j1KY19ZSDrw5uVrcg+KqCiHcplNmGSy48EL7WohGzZGVDmkHIorHsX9LcqXEcixbB3jYr+EVgRYZBagzrF+tMFg4rB7snXy51XZRQWVZ52dGnvGBXnl4iInVO1Y4JJOT7gq0WM6BZiEktseHW3V7X28+0dKQoXXoExN8yYszMcCnRmvICCoOs3WW3xwusrPDeSBvuYOJol2t5nVkeRbOQigsNJ5x8p416KTom0pZSynwr+SRj+45n5Wv7eSWZ2Ne6wYcIsCREhu8QJx5zCjpv0dA6yZvNzRHKuUhl9UU6nKIgW1EpdcTx56QkVjJhwAGtBpqc4RU+USLez1Er9cFlfxandREvd8vAgbS0MrJvHWteF0Uy33GjeY7cK9mNJnBPGpgp5x9nCFGi2hKaemW0/O/B16eMYVrYXhJJ5QqcLQtMlYg9aR9WA1g2f0lKSMvILwGVJdYNzwQshCx4M6+MZxipjFv9OxHSZAInt315ZnYiFKsFz94f1jFipclYOtjFLGy7PQkgFNlu3APRaEiBEo8zriHTXyrUKE+wwHZ0aYVX10x33c+vpDlwFo3wp92RK1h3AOtxbETIElibwuGDt2jeQlDnSlylfAfETUSL48qLXorSC/Qy9PBgHgvNbchUhG91/nFUHYckRaRIxdqTG1zgVnqnNbRsH2IQfVCOM+f3gDJ43m43m/ikulM5fAXp8u1TiY/Au9/fo7Sb92Un4xKptwji2A1CwsCrUnuSMpZ1J5B4jtUOGukkaeTY10CF+EYCmei1s7Ak9Kj9t4P26EKTBO8rhcLo0WhN1foC/ICft7/YPigBUEC1+tVePbWVCmnyW7pTcajD93zjLeSPEpHGl6ZpHuwS5DG+k4SNP0O9oszf8PKVJPBQ15LXrHVAPwggNQ2s9slLQ6vGUOF9ugJU+P4wGZVhv///7dl+LH9H8vh04uaW2fLer1eDIc6VbpWvupWB1agYdFSEThTESCM0RCUeqDyZ30bgHd8WxV01jgnmauPs0ZQIrTcs5DrmszlGD5JIggOUC/HAY1jeKIeKbm14pOF4emJ0JzTzxmxs7uRiXtMqPDL9LxOgOAYE5HzTJLjxFX+3EtevID3w5lGtQpZfWDcjjHvI7nIBox71VQeOqKsPPRZoqYNdflamDXJAFeH1OysSI7Nba09P6P2xLBATjxnr2lAov8Tndoh+RudsgqGZyV8BnOX+HjLP7evgxrXBdwPBjzXTcNvtKMNwtYfzYnlAVPOZ2ZrNSBa3lEOzqp2JY4npXWjJ9G9BpvUF7SKM5EvQpazj7++aEn/t8SfVm2l3e5bJLVYnbl4fUjlE2aP7vUkZcQzVoHDNxnt1Xo/YUYCQPpVYbnU+1yvC3N0fh4og+ZAPLK1VoENPFt2kCJZiEnPY6UWb86QaVMLZswr8GDAUmZg3ONoZ8XmGacx1zzf9dtewSnqUuT9g7duhCbz6XHFGCthrUHr5rbKmEdNRN/b1qbwRJV5rfS/B7xagFaEqhbRnFCkFgeIeUIJzJSRlbFzwSkJxBbyisKUvdieOCAyjzUNkXA83vm9LYltP4RWMAPH2kqliRCqiwNYUCmgRwEOVIbOWnh/TamdTuV6gJ+XTmk9lNpepbqfdq2xDOGQqoijHGaBpGLui9TW0vHsh1jKvbwBi537zqqzFsdffbkdzHnHc/feMCZBgHTqXAkAyLG3C4C2AxrEtsA2v6HXyn/T0s8YOTURNKkJOxVNbaaQSr7wz6recFvR4oQI7kiUvYNxvOHOCo9jKOKkVHoLe2Wj927IsMJGuW3tPpQx1qS1th0hK2geXy6OstOcqQFxJujFUcWJEn7z3XLUXCyCAwGvkKOZWRivYM1SmFuDS5wwpd3atdag4YjUk5lz7PH6QG9eD4OoHknLlm1KXVNylPkEGGagvS6ObWjVm6zKdes1MXQ/cRH1JcsmfVIlBZhFTwm0zgVBOOWRyETvhjnlSBwj/djq1Bwt+zEJ0SKgpVehbsjqZs/x+AqjxNJbwBQVlruzw9fixB9RVBDRowPamXUMrDXM4Gffqi+KgxVtm3MvIYRZ+qSoaUBWQUwrSlClW7hmz4wrsxp5WCB4cGFY57UxaAX9ksbaB9fVGzKuqnbr7K5Wxa28qPseRTLmE3u8jwokpTJKBRI87L32Qs5LlCOn0K+RhV7KbVEGS2TLM6L+DDUxnknNDN4qGbKwXT2KW2b1XK8Ln8/nCHWRX3nm8giodzS3nlAkXuj9ch4JVPh+eoUKy5PWiEJoFHKU7SiQIiIwL8eo8VBKNXNZD2u2Y7E3m3LZgrXeeslRVslw7YuZkdMhbA1PZH7Z+uJRZ0QgwAQQ5sEzjXkP/+YcGKFmJ78a2eWs4HoF+bC/wrF9zzkwGxl6T4Jp7oHP5y7sD2cSeVqppZXSB19yZjEe4XM6B2EWU852g+MCd+LG1oxKjWjKEjs1a3/JS+yE4D8JkTsKTZ5Q6CO62g+oFvS3t619rm3LHo8FP1LLTU15ONSp/uNDa4+CfqwTtMQB2g8Bveea7GJn1dyQuRxz3iWbwXFLU2z9NUTcF3q/0Ho/TrDGuRgV2mCK9vXIPjqbR4ej+xxpWn3W1phsy3k+A+P0mYKTrZ1GnvLIrGBZnAEsm/tLsJ+by6HYw00q662sApzFRRv7GIwU9cnVIvlU2vuX7eHPSphcUUqH0oVSEP1k5DCc379UcZtD/a7ZH16QLmc9Sj/m4LRKc87S0ibUuWtRqc6Ku1WETK+B3oAwbDijBqvGsQqYCKLZ6VOldqyWX9Eh+/d6Zguzx+nWjkj3eN6VBceWCB4RkBkWAlEpHRnPXBHZ5pOJEwZIR1ScMArgmXW1QYF9XzZ89xhgHBKPBsjLrs0hLFVlqxMZrlBeqs/9hFisPaBN9RRD8VWJfkc9uO+JOEVNlVphAxl7LuZZGFrtTQmfczmsVhEJCpxApywHW9Qo3jHWifBca4u6BSGPrzRK+U62qRtxT915ODzIr+t6wgRVYbHnZxTBWrqY1jvU5SAeaoIunWTnxUC+3hqBBBX+e2GYrpUY67qugyiJvOqw54jB1q/jSWlfcdHX1dj6gELl9/uFOe8aXbHbAOD98z7elChx87HA7fw98DybJc3/+Xnx/LGFn58XZzlW7noiMW47lebVL8w1EO4Vze1F+u4EMQGUdQGDjawEV4UaWZzEZusNHsz/eb1eGCanet3BUVIuMxPDVVk8HAmpzHnLDd7+lWj8pEBtr15EnKQj0i7zMBkejpyogN2JnR4WNadqeY0YghBk15pjIVpzKBOxJsQMZoEx15mg0wF8NkzFdVPTA1qp366TgWCVkJVgjykVK1MZhqfguHo/CcVkZuQvrvUgSlqT36WGbYcDWcr4yomdc6FdPA5ME1hAe3WGCZ65y5R8YgIhpYZLO3Td04sWr5A4gVQcG5xF8MmzW+zsAk9+WI9JqXvuqapxdC/bSbwrU6/ti8eh1DijanRte/z2yvUil9tR2+1BmRBAU04vaABCH1/D+/UiVYaOVmlTXttg+EJv13Ei/rx/kCWE2l1jIvF+/5wCwWukL4+IrPHw/lgJXA7GKkfLEyeybE+iUzW8rg41Aga+nriUqxCoZo2rMgVr4IwubEp9K5sbeeZS1jVZsgeSdkAGVB4C232dSec7VIM7A9DOaNwIwIC5Ap4UUu2ExG+Rzxq0gmdQMumV8LGKSZg5i3nPoy7Y8dLfXo7lfvJt3B4KKZxVbFYQPrHhhWgNqDkakfREvF6zphkMjOH4fP78Vd0liA3LSfDafGCDieHPZ9Ro3vUIpRKIH5LW9/igtevM2nh1Zg7c4648ObZQq5KhxWYp+1AjBakuIDTHUF/vNZ+zZmxtl7KoIHeOjtGZ7WWV30r2iARM4GvAwpAdz9yTbRiRoo042ZvKbhHa13aSVfiCvHqhJ5Qe9MphXXMgwtGvi47nhaMk27IRWszsnIGcOUXExJyGl7lWMRG13Qhjrzcr0trmLsnnXb2hGa1raxkeMcgTn331qwCFeXo0FghayEnUXlbruXBULdB8bAVc6ZHIeNBup2qnfVtrnjFOc83a6UqMXT3vBtg98n+o4XYEd01h2MMBNvZb9cfePaxycEUWwZcxJlQYbqSt0YuHImuLQdgq67WY7qvWEGthGUcM9taJwNRZR0QmAJmPMyoSHrN8/nvMA886podUWuLRAj3w/JYlMkH9IZhN82STGzZwUBWixvEpfp97KlbD0rbckglbGxywKvpYNcdBsTKeIk9OXMzD5GUlSe+cdm3lI5XE9IDW7OasUYdnePf+bco50vjaNLqi2A/mBdaMyiKld0B+2RPaSYPad7pXZNY+gN2fcwT1Zo/UQg7Y7caZTU/2qjw9lz5TAnbicBZ4zdey6impU3F/ojr/pzRTilTdofRjDrStoBMcbU8G4b7WWPKvuR5zrCcjUxDHJbyHgo7pSDiWc5A2Z28EYuOjyyHlUzkzKvecy2KQBITgnoB+g0opENXqyMC5aRk7748IFvN6pOqJXn0w2Y/UwFUji/l9ajTvGhNe+hb3hRUG2zqdwilVHxUbt4CnwDljFitdeY+CgCz2lTt3VZjMnAByPRKOIbOkkVEIi8AuxawEyVnBEl5gQ5Y4qtm3zTtP1XrCe0t5QA4zn3nK2xizTT81xFSjxNCSmCuO9sbKwcX+loKyUCoQvL7HcUC7A6UCOEGJm0mqRSNCPVYEaE2IJ2ePxxjPEfkKfHR/Yl644y1oq/EU1tBWyR54TuKMAfLw0wzHHhGYO+uc3EAeNVgch5BW6q+WoDfLmChSLH/NPRzVo1ojLUSdq9YMRT2YqtWqOeemcJThnnp3RhPWpLz8UuBlZrHyNas4EivW3tUoUyx/vmcFMRh9h1ue0ptiLqoAmASdFDw1RpddrwvjXpCsafFVXWthorFBAsUzFAfyVSlHBfPKYWEYyFVt2dYRfc092UbxqBQwldK17kDcXb1aWbazHFSv60Wd6RxwL9IzqT19vS7qM4U+CBPOdKQOtJ0iJWvopn7NnPKilPbNOCGDZePbD5iVq0tjk79/TyzcAD53Cb6f7IFopTkV7AFkNVAtHg4xyt4XpQUwFYQ2AJOTe/ZOZAqtIuhk3cAg4Pfm1KDSJ1kVazX7kVEyfqQfWQUW1fnKoTnNEH6xaOyd9YT0k4nQG4ntVQG8NAFRQ9W2krp1nJG3ogstG82v5aUbM0pecdieIkUHVMiMr7Xg6pxvJdQ0bYnkk7axDmKUkUjNurDxBCi0ncQvh7OkyKsKACqJK+dOK8+upoDvgWmZcFj9XrDgR454DLFfkOyqBMyVjlYy/uXz+DEJ+hPADg9I8u/iIvAv9SBvtxmfmDx5QJzhgTPfhOefw9eXjLRma61FInoMqvFPcYP6/+dzj1aBDW3PvG+tosPkOoO4qMmRp+BI4ptWKm7U3i+miPkEBZ20f+VEO85QKboqgzbtxoErzRpe7xfkg0eoJHZyA6TC/DbowmEzlPlm4cCpBO1bu2r1MWecWbR8IHxyZIOCSm6C1K16vOfcaM3wqm1VEHi/3/jz+4fiMuUsZcaz0K/duqGtPTC8xGaI+jwGNIXUGMTerPptVPTZPvf0FJwiL5hwaOpVuX5eKsJdmGqBJmqK63VRmT8GXbxmDT4f0RQA3Pc4zqdQqpyzlM+92ZFzSFa0V85KUJZH/HtUHY5uF2I5Uirg78vttNYClPQPCjAfi6lcW6CkJsiJIwTbk/XcHa1PfD6fc45mBvIz0F/P9PFdgfvi6pgHKZKacqeYkUeIPQcbfIZh0Ba/o1oAIZwo1J16Bl4BxnZXjuseBbHWwtVfpW/l+cdpdDjhFKh6YkeXirFP9XTMcaNpg5of048avwOhTmP7sQVQpgb0oFuodYTnmfbmi9Pldr/T68Ql1KYQX/WkKlzwRbvo0b5ejXZt9oU09+ykSSlaZ4ciyVd8C7fTmme8JwhknAhtnAHWT97rdz/tvlV3glgLaHRIeXlbDFbtSOXW7hi3yGO0OavGGqzOt5HOs9BIKpg1tF4R28cVxmSr/b0yS0dbUW9WY4i/1XtzTLxevdC0DtT5yXiaUjb0djhOazXRVYTJG3yzF1pnrrkatZsBlEE04fNxXbXGKQPWWjXErHqziovrup4hnEq/P9aoaaUCvZhD00wQO9Zr2Qm6XXtWo3ynQ2oJluVEVp8x7hDa9OqmKrSyYvkAqQjm3rLr/F3+3HHPgKVUe8IiKWr+B6X9dEuZNSC9Ij93xOcTxoRijLISQNwdO86RK19OoLFLPYQgvhzBmuHq73qIA1YsyFWaIcDOPbLGFqvtKOqsPHMkcM+J1lZNcSs/oq9TDG1fXoJ8mq6FMT7kF7VhjhuRwOtN/k+tITPwr38p7nsSaPbJcAbrxYbflX3HQS2u+nWR/5YhU4ngFbLw5LyFRw3gZAEUoujl45hrEZLbWTxS3shtX4agqRX0pRiVo2emiLRjN8zSom67gIoiRYHGOO4IFjy2ieX5tGxZY4m3p3FTaXsqQqQe/naD/Ok80qTuxX7o6LNcB9Bv415oKkCvyLI68MkVFo2iHBHh8bDpNOomrOXR7myYi6Ns94AT1HnnuO9FDPKkInpty1ZSEqvWw9GUNySxlQQP885UTalB1oaAlmnoMaRqa/QRKouE8BoloXlmZakJ0his0Mq2x8jNeBTrBXRMX7B8SIRzttWvsWhinXMd9Zv7rPDcKOlMPCM4Mv8KhdqTDFgzDPz5/S3ZSFXZKpCwZypSRZ3taNN2XZQZtHLa4hu9OZPN/TDR8dXHUX5hp6z+Hgd4wLnDq1WDryRcLamj3dEt/WpQMUzEuSGbb9s3ke/zzEr+wpQLA9029jLiGjPgVtkcoI92dQPyO0g4Kt57+/xNFGElXFZB3wpz2dmtWkmSBguD9b31eYmZW/n9BbKKMHh1Au4mf00zQKnrtRl0MfDhulrtZuXu3g/kDvOv52A/kO2kAP/VZJdGptKr+p7/JCgISg7meV0NKg2Cdw06i5OufL2uI6NwV1zdoLgYV7aol9lCq5VAStSAlMot2LKO2j42XLWpMTMrqQQxrnQiuGsxvCkrnF5LgpJlUY/Y2XLK74NnOoGpVT7AfmjzKLaoIty+lWcsbn5Fgi6voiqoFy6u/syJ3ogYY2D0L1FYVs8WTsya0hUvg5Q+0WvlXdkzmyv8sIaqXNeZnnZdHe/rB7edUFCgEcuMfUbVymzWYe2qQAMmGMJwQuAjAd1pwZuhEOawLg/I8r0oKtokT3bbXH+fi2dlSqBJq/hNgcfDoms+FzwQDAbOqHOrHUppF097cKj6lituItdO3uwJVawx8rlhNSRe7wv565D6s7ZB7zq3mwLhemLFuQuQgdnt2QZD+Hsaat/vn8pzQEGUDdf7VbE2lM+83m9O+jHB/zcA8l42gTxyvwYAAAAASUVORK5CYII=); + background:#ccc; -webkit-box-shadow:inset 0 -10px 10px rgba(0, 0, 0, 0.25), inset 0 10px 10px rgba(0, 0, 0, 0.25), inset -10px 0 10px rgba(0, 0, 0, 0.25), inset 10px 0 10px rgba(0, 0, 0, 0.25); + box-shadow:inset 0 -10px 10px rgba(0, 0, 0, 0.25), inset 0 10px 10px rgba(0, 0, 0, 0.25), inset -10px 0 10px rgba(0, 0, 0, 0.25), inset 10px 0 10px rgba(0, 0, 0, 0.25); -webkit-touch-callout:none; } +@media all and (-webkit-transform-3d) { + .c-grid.ready .cell { background:url(data:image/png;base64,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); } +} .c-grid .position-wrap { position:relative; } @@ -1395,14 +1712,11 @@ section .category-title { display:none; } display:inline-block; height:100%; width:100%; - -webkit-transform-style:preserve-3d; - -webkit-transition:ease-in-out 600ms; } .c-grid .flip-block .wrap { padding:15px 10px 10px;; } .c-grid .flip-block.active { - -webkit-transform:rotateY(180deg); } .c-grid .cloned-wrap { top:0; @@ -1431,11 +1745,9 @@ section .category-title { display:none; } 100% { -webkit-transform:rotate(3deg) skew(1deg) scale(1.2); } } -.c-grid .flip-block.active .i-flip { - -webkit-transform:rotate(360deg); -} .c-grid .flip-block .front { background:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#eee)); + background:-o-linear-gradient(top, #fff 0%, #eee 100%); } .c-grid .flip-block .front, .c-grid .flip-block .back { @@ -1447,8 +1759,23 @@ section .category-title { display:none; } } .c-grid .flip-block .back { background:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#eee)); + background:-o-linear-gradient(top, #fff 0%, #eee 100%); position:absolute; - -webkit-transform:rotateY(180deg) translate3d(0, 0, 1px); +} +.c-grid .flip-block.active .back { + z-index:1; +} +@media all and (-webkit-transform-3d) { + .c-grid .flip-block { + -webkit-transform-style:preserve-3d; + -webkit-transition:ease-in-out 600ms; + } + .c-grid .flip-block.active { + -webkit-transform:rotateY(180deg); + } + .c-grid .flip-block .back { + -webkit-transform:rotateY(180deg) translate3d(0, 0, 1px); + } } .c-grid .back-face { padding:10px; } .c-grid .back-face .rating-box { display:inline-block; } @@ -1458,6 +1785,7 @@ section .category-title { display:none; } .c-grid .back-face .price-box .special-price .price { font-weight:bold; } .c-grid .back-face .add-to-cart { background:-webkit-gradient(linear, 0 0, 0 100%, from(#F39823), to(#F37221)); + background:-o-linear-gradient(top, #F39823 0%, #F37221 100%); border:1px solid #fff; border-radius:5px; color:#fff; @@ -1467,7 +1795,9 @@ section .category-title { display:none; } padding:5px 10px; text-shadow:0 1px 0 #999; -webkit-box-shadow:0 3px 3px 0 #9F9F9F; + box-shadow:0 3px 3px 0 #9F9F9F; -webkit-background-clip:padding-box; + background-clip:padding-box; } .c-grid .back-face .rating-links .separator, .c-grid .back-face .rating-links .separator + a { @@ -1486,6 +1816,9 @@ section .category-title { display:none; } .catalog-product-view .messages { margin:0 -10px 0; } .product-view { padding:0 0 0; } .product-shop { padding:8px 0 0; } +.product-shop a { color:#fb6b36; text-decoration:underline; } +.product-shop .item-options { margin:0 0 10px; } +.product-shop .item-options dt { font-weight:bold; } .product-shop .product-image-and-actions { float:left; margin:0 10px 0 0; } .product-shop .product-image-wrap { position:relative; max-width:150px; } .product-shop .product-image { position:relative; } @@ -1494,7 +1827,9 @@ section .category-title { display:none; } display:block; background:url(../images/search_icon.png) no-repeat 0 0; -webkit-background-size:14px 14px; + background-size:14px 14px; -webkit-background-origin:padding-box; + background-origin:padding-box; position:absolute; top:7px; right:7px; @@ -1502,16 +1837,13 @@ section .category-title { display:none; } width:14px; z-index:1; } -.product-shop .product-image-wrap img { -webkit-border-radius:1px; -webkit-box-shadow:0 0 3px #D1D1D1; -webkit-transition:-webkit-transform ease-in 300ms; position:relative; } +.product-shop .product-image-wrap img { -webkit-border-radius:1px; -webkit-box-shadow:0 0 3px #D1D1D1; box-shadow:0 0 3px #D1D1D1; -webkit-transition:-webkit-transform ease-in 300ms; position:relative; } .product-shop .product-image-wrap img.animate { z-index:101; } .product-shop .product-image-wrap img.cloned { position:absolute; top:0; left:0; } .product-shop .product-image .carousel-items li { padding:2px 2px 0; } .product-shop .product-image .more-views { padding:3px 0 0 0; } .product-shop .wrap:before, .product-shop .wrap:after { content: ""; display: table; } .product-shop .wrap:after { clear:both; } -.product-shop >.product-actions { - margin-bottom:10px; -} .product-view .add-to-links, @@ -1537,20 +1869,43 @@ section .category-title { display:none; } .product-essential .product-options input[type=text]:disabled { background:#ddd; } .product-essential .product-options .qty-holder { display:block; margin:5px 0 0; } .product-essential .product-options .options-list li { padding:10px 0 0; } +.product-essential .product-options .options-list a { color:#fb6b36; text-decoration:underline; } -.product-essential .product-options-bottom .price-box { float:right; width:50%; } +.product-essential .product-options-bottom { background:#fefefe; position:relative; } +.product-essential .product-options-bottom .required { margin:10px 0; } +.product-essential .product-options-bottom .price-box { float:right; max-width:50%; } .product-essential .product-options-bottom .price-box .price-as-configured { text-align:right; } .product-essential .product-options-bottom .price-box .price-as-configured .price { display:block; font-size:18px; line-height:26px; text-shadow:0 1px 0 #fff; text-align:right; } -.product-essential .product-options-bottom .add-to-cart { float:left; width:50%; margin:0 0 15px; } -.product-essential .product-options-bottom .add-to-cart label { display:inline-block; font-size:14px; font-weight:bold; margin-bottom:50px; } -.product-essential .product-options-bottom .add-to-cart input[type=text] { display:inline-block; margin-bottom:10px; width:40px; font-size:12px; } -.product-essential .product-options-bottom .add-to-cart button { display:block; float:right; margin:50px -100% 0 0; padding:5px 20px; background:-webkit-gradient(linear, 0 0, 0 100%, from(#F39823), to(#F37221)); font-size:14px; color:#FFF; border:1px solid #FFF; border-radius:5px; -webkit-box-shadow:0 3px 3px 0 #9F9F9F; } +.product-essential .product-options-bottom .add-to-cart { float:left; width:50%; } +.product-essential .product-options-bottom .add-to-cart label { display:inline-block; font-size:14px; font-weight:bold; vertical-align:middle; } +.product-essential .product-options-bottom .add-to-cart input[type=text] { display:inline-block; width:40px; font-size:12px; vertical-align:middle; } +.product-essential .product-options-bottom button { + display:block; + padding:5px 20px; + background:-webkit-gradient(linear, 0 0, 0 100%, from(#F39823), to(#F37221)); + background:-o-linear-gradient(top, #F39823 0%, #F37221 100%); + font-size:14px; + color:#FFF; + clear:both; + float:right; + border:1px solid #FFF; + border-radius:5px; + margin:10px 0; + text-shadow:0 1px 2px rgba(0, 0, 0, 0.25); + -webkit-box-shadow:0 3px 3px 0 #9F9F9F; + box-shadow:0 3px 3px 0 #9F9F9F; +} .product-shop .price-box { overflow:hidden; } .product-shop .price-box .price-label { vertical-align:baseline; } .product-shop .price-box .old-price { color:#ccc; } .product-shop .price-box .special-price .price-label { color:#222; } .product-shop .price-box .special-price .price { font-weight:bold; } + .price-box .price-excluding-tax, + .price-box .price-including-tax { display:block; } + .price-box .price-excluding-tax .label, + .price-box .price-including-tax .label { font-weight:bold; } + .product-shop .product-desc { display:table;} .product-shop .product-desc .ratings { overflow:hidden; } .product-shop .product-desc .ratings, @@ -1558,28 +1913,32 @@ section .category-title { display:none; } .rating-box { background:url(../images/i_star_black.png) repeat-x center left; background-size:15px 15px; width:75px; height:15px; margin:3px 0; } .rating-box .rating { height:15px; background:url(../images/i_star.png) repeat-x center left; background-size:15px 15px; } -.product-shop .product-options-bottom .price-box { font-size:14px; font-weight:bold; text-align:right; } +.product-shop .product-options-bottom .price-box { font-size:14px; text-align:right; } +.product-shop .product-options-bottom .price-box .price { font-weight:bold; } .product-shop .product-options-bottom .add-to-cart label, .product-shop .product-options-bottom .add-to-cart input[type=text] { vertical-align:middle; margin:0; } -.product-shop .product-options-bottom .add-to-cart button { margin:30px -100% 0 0; } -.product-shop .product-options-bottom .product-actions { margin-bottom:10px; } +.product-shop .product-options-bottom .product-actions { margin:5px 0 10px; } .product-shop .product-options-bottom .product-actions li { display:table-cell; } .product-shop .add-to-cart-box { padding:5px 0; } .product-shop .add-to-cart-box label { vertical-align:middle; } -.product-shop .add-to-cart-box .qty { border:1px solid #ccc; border-radius:3px; font-size:12px; font-weight:bold; margin:0 0 0 5px; padding:4px 5px; -webkit-appearance:none; -webkit-background-clip:padding-box; width:35px; -webkit-box-sizing:content-box; vertical-align:middle; } +.product-shop .add-to-cart-box .qty { border:1px solid #ccc; border-radius:3px; font-size:12px; font-weight:bold; margin:0 0 0 5px; padding:4px 5px; -webkit-appearance:none; -webkit-background-clip:padding-box; background-clip:padding-box; width:35px; -webkit-box-sizing:content-box; box-sizing:content-box; vertical-align:middle; } .product-shop .add-to-cart-box button { clear:both; display:block; margin:0; padding:5px 20px; background:-webkit-gradient(linear, 0 0, 0 100%, from(#F39823), to(#F37221)); + background:-o-linear-gradient(top, #F39823 0%, #F37221 100%); font-size:14px; color:#FFF; border:1px solid #FFF; border-radius:5px; + text-shadow:0 1px 2px rgba(0, 0, 0, 0.25); -webkit-box-shadow:0 3px 3px 0 #9F9F9F; + box-shadow:0 3px 3px 0 #9F9F9F; -webkit-background-clip:padding-box; + background-clip:padding-box; } .product-shop .add-to-cart-box input + button { margin-top:10px; } @media (orientation: landscape) { @@ -1587,10 +1946,44 @@ section .category-title { display:none; } } .product-shop .product-image-and-actions .actions { margin:15px 0 0; } -.product-shop .product-image-and-actions .actions a { display:block; text-align:center; width:100px; margin:5px 0 0; padding:4px 10px; background:-webkit-gradient(linear, 0 0, 0 100%, from(#FFF), to(#C1C3C4)); font-size:12px; font-weight:bold; color:#636363; border:1px solid #FFF; border-radius:5px; -webkit-box-shadow:0 0 3px 0 #9F9F9F; } +.product-shop .product-image-and-actions .actions a { display:block; text-align:center; width:100px; margin:5px 0 0; padding:4px 10px; background:-webkit-gradient(linear, 0 0, 0 100%, from(#FFF), to(#C1C3C4)); background:-o-linear-gradient(top, #FFF 0%, #C1C3C4 100%); font-size:12px; font-weight:bold; color:#636363; border:1px solid #FFF; border-radius:5px; -webkit-box-shadow:0 0 3px 0 #9F9F9F; box-shadow:0 0 3px 0 #9F9F9F; } .product-shop .product-image-and-actions .actions a:last-child { margin:5px 0 10px; } -#product-options-wrapper { margin:10px 0; padding:5px 10px; border:1px solid #FFF; border-radius:5px; background:-webkit-gradient(linear, 0 0, 0 100%, from(#F6F6F6), to(#C5C5C5)); -webkit-box-shadow:0 0 3px 0 #9F9F9F; } +#product-options-wrapper dl, .giftcard-send-form { + background:-webkit-gradient(linear, 0 0, 0 100%, from(#F6F6F6), to(#C5C5C5)); + background:-o-linear-gradient(top, #F6F6F6 0%, #C5C5C5 100%); + margin:10px 0; + padding:5px 10px; + border:1px solid #FFF; + border-radius:5px; + -webkit-box-shadow:0 0 3px 0 #9F9F9F; + box-shadow:0 0 3px 0 #9F9F9F; +} + +/* +.product-essential .product-options dt label { font-size:14px; font-weight:bold; } +.product-essential .product-options dt label em { float:left; margin:0 4px 0 0; color:#F9721F; } +.product-essential .product-options dd { border-bottom:1px solid #bbb; padding:0 0 10px; } +.product-essential .product-options dd + dt { border-top:1px solid rgba(255, 255, 255, 0.75); padding:10px 0 0; } +.product-essential .product-options dd:last-child { border:none; } +.product-essential .product-options select { width:100%; } +.product-essential .product-options input[type=text] { font-size:12px; width:20%; } +.product-essential .product-options input[type=text]:disabled { background:#ddd; } +.product-essential .product-options .qty-holder { display:block; margin:5px 0 0; } +.product-essential .product-options .options-list li { padding:10px 0 0; } +*/ + +.giftcard-send-form em { color:#f9721f; margin:0 4px 0 0; } +.giftcard-send-form label { display:block; font-size:14px; font-weight:bold; margin:0 0 2px; } +.giftcard-send-form select { font-size:18px; width:100%; } +.giftcard-send-form li { border-bottom:1px solid #bbb; margin:0; padding:0 0 8px; } +.giftcard-send-form li + li { border-top:1px solid rgba(255, 255, 255, 0.75); padding:8px 0 0; } +.giftcard-send-form li:last-child { border-bottom:none; } +.giftcard-send-form .field { margin-bottom:8px; } +.giftcard-send-form .gift-card-amount-field { padding:0; } +.giftcard-send-form .notice { text-align:right; } +.giftcard-send-form .notice span { float:left; font-size:11px; display:block; } +.giftcard-send-form .notice span:last-child { float:none; } #product-attribute-specs-table { width:100%; border-collapse:collapse; } #product-attribute-specs-table th, @@ -1601,6 +1994,7 @@ section .category-title { display:none; } .grouped-items-table { border:1px solid #ccc; font-size:11px; line-height:13px; margin:0 0 5px; -webkit-border-radius:5px; } .grouped-items-table th { background:-webkit-gradient(linear, 0 0, 0 100%, from(#f6f6f6), to(#ccc)); + background:-o-linear-gradient(top, #f6f6f6 0%, #ccc 100%); border-bottom:1px solid #bbb; padding:2px 2px 3px; } @@ -1615,7 +2009,10 @@ section .category-title { display:none; } padding:5px 10px 10px; margin:10px 0 0; border-radius:5px; - background:-webkit-gradient(linear, 0 0, 0 100%, from(#F6F6F6), to(#C5C5C5)); -webkit-box-shadow:0 0 3px 0 #9F9F9F; + background:-webkit-gradient(linear, 0 0, 0 100%, from(#f6f6f6), to(#c5c5c5)); + background:-o-linear-gradient(top, #f6f6f6 0%, #c5c5c5 100%); + -webkit-box-shadow:0 0 3px 0 #9F9F9F; + box-shadow:0 0 3px 0 #9F9F9F; } .product-shop .grouped-grid .add-to-cart-box { @@ -1645,7 +2042,9 @@ section .category-title { display:none; } width:25px; -webkit-appearance:none; -webkit-background-clip:padding-box; + background-clip:padding-box; -webkit-box-sizing:content-box; + box-sizing:content-box; } .catalog-product-gallery .product-gallery { @@ -1670,6 +2069,7 @@ section .category-title { display:none; } .catalog-product-gallery .product-gallery li img { vertical-align:bottom; -webkit-box-shadow:0 0 6px rgba(0, 0, 0, 0.25); + box-shadow:0 0 6px rgba(0, 0, 0, 0.25); } .catalog-product-gallery .product-gallery .prev, .catalog-product-gallery .product-gallery .next { @@ -1689,6 +2089,7 @@ section .category-title { display:none; } .catalog-product-gallery .add-to-cart { background:-webkit-gradient(linear, 0 0, 0 100%, from(#F39823), to(#F37221)); + background:-o-linear-gradient(top, #F39823 0%, #F37221 100%); display:inline-block; padding:4px 7px 5px; font-size:12px; @@ -1698,12 +2099,22 @@ section .category-title { display:none; } position:absolute; top:50px; right:5px; + text-shadow:0 1px 2px rgba(0, 0, 0, 0.25); -webkit-background-clip:padding-box; } .product-collateral { clear:both; } +.product-collateral .box-description { padding:10px 0 0; } .product-collateral .box-additional h2 { margin:0 0 10px; } -.product-collateral .collateral-box { padding:5px 10px; border:1px solid #FFF; border-radius:5px; background:-webkit-gradient(linear, 0 0, 0 100%, from(#F6F6F6), to(#C5C5C5)); -webkit-box-shadow:0 0 3px 0 #9F9F9F; } +.product-collateral .collateral-box { + background:-webkit-gradient(linear, 0 0, 0 100%, from(#F6F6F6), to(#C5C5C5)); + background:-o-linear-gradient(top, #F6F6F6 0%, #C5C5C5 100%); + padding:5px 10px; + border:1px solid #FFF; + border-radius:5px; + -webkit-box-shadow:0 0 3px 0 #9F9F9F; + box-shadow:0 0 3px 0 #9F9F9F; +} .product-collateral h2, .product-collateral h4 { font-size:14px; font-weight:bold; } .product-specs { font-size:14px; text-align:justify; word-spacing:-0.2ex; } @@ -1713,6 +2124,7 @@ section .category-title { display:none; } .up-sell-box .prev, .up-sell-box .next { background:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#dcdcdc)); + background:-o-linear-gradient(top, #fff 0%, #dcdcdc 100%); border:1px solid #bebebe; display:block; position:absolute; @@ -1721,6 +2133,7 @@ section .category-title { display:none; } height:24px; width:24px; -webkit-border-radius:5px; + border-radius:5px; z-index:1; } .up-sell-box .prev.disabled, @@ -1731,9 +2144,12 @@ section .category-title { display:none; } .up-sell-box .prev:after { border-top:5px solid transparent; border-right:10px solid #000; border-bottom:5px solid transparent; right:8px; } .up-sell-box .next:after { border-top:5px solid transparent; border-left:10px solid #000; border-bottom:5px solid transparent; left:8px; } .carousel-wrap { overflow:hidden; position:relative; width:100%; } - .carousel-wrap .carousel-items { white-space:nowrap; -webkit-transition:all 150ms linear; -webkit-transform:translateX(0); -webkit-perspective:1000; -webkit-backface-visibility:hidden; } - .carousel-wrap li { display:inline-block; vertical-align:top; padding:0 5px; white-space:normal; -webkit-box-sizing:border-box; } - .up-sell-box .product-image { background:#fff; display:inline-block; padding:3px; border:1px solid #ccc; -webkit-box-shadow:0 1px 2px #999; text-align:center; } + .carousel-wrap .carousel-items { white-space:nowrap; -webkit-transition:all 150ms linear; -moz-transition:all 250ms linear; -o-transition:all 250ms linear; transition:all 250ms linear; } + @media all and (-webkit-transform-3d) { + .carousel-wrap .carousel-items { -webkit-transform:translateX(0); -webkit-perspective:1000; -webkit-backface-visibility:hidden; } + } + .carousel-wrap li { display:inline-block; vertical-align:top; padding:0 5px; white-space:normal; -webkit-box-sizing:border-box; box-sizing:border-box; } + .up-sell-box .product-image { background:#fff; display:inline-block; padding:3px; border:1px solid #ccc; -webkit-box-shadow:0 1px 2px #999; box-shadow:0 1px 2px #999; text-align:center; } .up-sell-box .up-sell h5 { font-size:12px; line-height:17px; margin:5px 0; } .up-sell-box .up-sell .price-box { font-size:12px; line-height:17px; } .up-sell li .price-box { font-weight:bold; } @@ -1757,6 +2173,7 @@ section .category-title { display:none; } .compare-clear-all, .compare-back-link a { background:-webkit-gradient(linear, 0 0, 0 100%, from(#ddd), to(#eee)); + background:-o-linear-gradient(top, #ddd 0%, #eee 100%); color:#000; text-shadow:1px 1px 0 #fff; display:inline-block; @@ -1770,7 +2187,8 @@ section .category-title { display:none; } } .compare-clear-all { color:#fff; - background:-webkit-gradient(linear, left top, left bottom, color-stop(0%,#f85032), color-stop(50%,#f16f5c), color-stop(51%,#f6290c), color-stop(71%,#f02f17), color-stop(100%,#e73827)); + background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #f85032), color-stop(50%, #f16f5c), color-stop(51%, #f6290c), color-stop(71%, #f02f17), color-stop(100%, #e73827)); + background:-o-linear-gradient(top, #f85032 0%, #f16f5c 50%, #f6290c 51%, #f02f17 71%, #e73827 100%); border:1px solid #777; border-radius:5px; display:inline-block; @@ -1783,6 +2201,7 @@ section .category-title { display:none; } .compare-back-link:before { content:''; background:-webkit-gradient(linear, 0 0, 0 100%, from(#ddd), to(#eee)); + background:-o-linear-gradient(top, #ddd 0%, #eee 100%); border:1px solid #999; height:18px; width:18px; @@ -1796,11 +2215,12 @@ section .category-title { display:none; } .compare-table th, .compare-table td { background:#fff; border:1px solid #ccc; padding:5px 15px; vertical-align:top; } .compare-table td a { color:#fb6b36; } -.compare-table th { background:url(../images/fabric.jpg) repeat; border:none; -webkit-box-shadow:inset -3px 0 3px -3px #ccc; text-shadow:1px 1px 1px #fff; text-align:left; position:relative; padding:5px 10px 5px 10px; } +.compare-table th { background:url(../images/fabric.jpg) repeat; border:none; -webkit-box-shadow:inset -3px 0 3px -3px #ccc; box-shadow:inset -3px 0 3px -3px #ccc; text-shadow:1px 1px 1px #fff; text-align:left; position:relative; padding:5px 10px 5px 10px; } .compare-table tr:nth-child(even) td { background:#f6f6f6; } .compare-table .move-left, .compare-table .move-right { background:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#eee)); + background:-o-linear-gradient(top, #fff 0%, #eee 100%); border:1px solid #c4c4c4; border-radius:5px; display:inline-block; @@ -1809,6 +2229,7 @@ section .category-title { display:none; } height:16px; width:10px; -webkit-box-shadow:0 0 2px rgba(0, 0, 0, 0.15); + box-shadow:0 0 2px rgba(0, 0, 0, 0.15); } .compare-table .move-left:after, .compare-table .move-right:after { content:''; position:absolute; top:8px; z-index:2; font-size:0; line-height:0; width:0; } @@ -1838,9 +2259,19 @@ section .category-title { display:none; } -webkit-transition:-webkit-transform 300ms linear; -webkit-transform:translate3d(0, 0, 5px) !important; } -.compare-table .collapsible .nobr { background:-webkit-gradient(linear, 0 0, 0 100%, from(#f6f6f6), to(#bbb)); border:1px solid #aaa; border-radius:7px; -webkit-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.15), 0 1px 0 #fff; } +.compare-table .collapsible .nobr { + background:-webkit-gradient(linear, 0 0, 0 100%, from(#f6f6f6), to(#bbb)); + background:-o-linear-gradient(top, #f6f6f6 0%, #bbb 100%); + border:1px solid #aaa; + border-radius:7px; + -webkit-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.15), 0 1px 0 #fff; + box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.15), 0 1px 0 #fff; +} .compare-table .collapsible .nobr:active, -.compare-table .collapsible.collapsed .nobr { background:-webkit-gradient(linear, 0 0, 0 100%, from(#ddd), to(#ccc)); } +.compare-table .collapsible.collapsed .nobr { + background:-webkit-gradient(linear, 0 0, 0 100%, from(#ddd), to(#ccc)); + background:-o-linear-gradient(top, #ddd 0%, #ccc 100%); +} .compare-table .collapsible .nobr:before { content:''; background:url(../images/i_arrow_white.png) no-repeat 0 0; @@ -1861,6 +2292,7 @@ section .category-title { display:none; } .compare-table .product-name a { color:#000; } .compare-table .btn-cart { background:-webkit-gradient(linear, 0 0, 0 100%, from(#F39823), to(#F37221)); + background:-o-linear-gradient(top, #F39823 0%, #F37221 100%); border:1px solid #fff; border-radius:5px; color:#fff; @@ -1869,12 +2301,14 @@ section .category-title { display:none; } margin-left:-2px; margin-bottom:7px; padding:5px 10px; - text-shadow:0 1px 0 #999; + text-shadow:0 1px 2px rgba(0, 0, 0, 0.25); -webkit-box-shadow:0 3px 3px 0 #9F9F9F; + box-shadow:0 3px 3px 0 #9F9F9F; -webkit-background-clip:padding-box; + background-clip:padding-box; } .compare-table .btn-remove { -webkit-transform:translate3d(0, 0, 0); } -.compare-table .product-image { background:#fff; display:inline-block; margin:4px 0 10px; padding:3px; position:relative; border:1px solid #ccc; -webkit-box-shadow:0 1px 2px #ccc; text-align:center; } +.compare-table .product-image { background:#fff; display:inline-block; margin:4px 0 10px; padding:3px; position:relative; border:1px solid #ccc; -webkit-box-shadow:0 1px 2px #ccc; box-shadow:0 1px 2px #ccc; text-align:center; } .compare-table .product-image img {} .compare-table .loader { background:url(../images/loader.gif) no-repeat center rgba(0, 0, 0, 0.05); background-size:20px 20px; float:right; height:20px; width:20px; } @@ -1914,26 +2348,58 @@ section .category-title { display:none; } width:100%; -webkit-appearance:none; -webkit-box-sizing:border-box; + box-sizing:border-box; -webkit-box-shadow:inset 1px 1px 1px rgba(0, 0, 0, 0.1); + box-shadow:inset 1px 1px 1px rgba(0, 0, 0, 0.1); } .review-product-list .pager, .review-product-list .form-add h2, .review-product-list .form-add h3, .review-product-list .form-add h4 em { display:none; } -.review-product-list .form-add h4 { background:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#e5e5e5)); border-bottom:1px solid #ccc; border-radius:5px 5px 0 0; margin:0 -10px 0; padding:10px; text-shadow:1px 1px #fff; } -.review-product-list .form-add { background:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#eee)); border:1px solid #ccc; margin:10px 0 5px; padding:0 10px 15px; border-radius:5px; -webkit-box-shadow:0 1px 3px #ccc; } +.review-product-list .form-add h4 { + background:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#e5e5e5)); + background:-o-linear-gradient(top, #fff 0%, #e5e5e5 100%); + border-bottom:1px solid #ccc; + border-radius:5px 5px 0 0; + margin:0 -10px 0; + padding:10px; + text-shadow:1px 1px #fff; +} +.review-product-list .form-add { + background:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#eee)); + background:-o-linear-gradient(top, #fff 0%, #eee 100%); + border:1px solid #ccc; + margin:10px 0 5px; + padding:0 10px 15px; + border-radius:5px; + -webkit-box-shadow:0 1px 3px #ccc; + box-shadow:0 1px 3px #ccc; +} .review-product-list .form-add .form-list li:nth-child(2) { margin:15px 0 0; } .review-product-list .box-reviews h2 { margin-bottom:6px; } +.review-product-list .box-reviews dl { border-radius:5px; padding:10px 10px 0; -webkit-box-shadow:0 1px 3px #ccc; box-shadow:0 1px 3px #ccc; } .review-product-list .box-reviews dt { padding:0 0 5px; } +.review-product-list .box-reviews dt a, +.review-product-list .box-reviews dt h3 { display:block; font-size:13px; font-weight:bold; } +.review-product-list .box-reviews dd { overflow:hidden; } +.review-product-list .box-reviews dd:last-child { border:none; padding:0 0 10px; } +.review-product-list .box-reviews dd .date { color:#666; font-size:11px; display:block; } +@media(orientation:landscape) { + .review-product-list .box-reviews dd table { + float:right; + margin:-3px 0 0 10px; + } +} .review-product-list .ratings-table { border-spacing:0; } .review-product-list .ratings-table th { padding-right:5px; } -#customer-reviews small { font-size:12px; } .review-table-wrap { padding:10px 0 0; } +.review-product-list .box-reviews dd + dt:before, .review-table-wrap:after { content:''; background:-webkit-gradient(linear, left top, right top, color-stop(0%,#fff), color-stop(25%,#ccc), color-stop(75%,#ccc), color-stop(100%,#fff)); + background:-o-linear-gradient(top, #fff 0%, #ccc 22%, #fff 100%); display:block; height:1px; width:100%; @@ -1953,21 +2419,25 @@ section .category-title { display:none; } #product-review-table tbody td input { background:url(../images/i_star.png) no-repeat center; border:none; background-size:15px 15px; display:inline-block; height:25px; width:25px; opacity:0.25; margin:0; -webkit-appearance:none; -webkit-transition:all 100ms ease-in-out; } #product-review-table tbody td input:checked, #product-review-table tbody td.checked input { opacity:1; } -.review-product-list .buttons-set button { padding:5px 10px; background:-webkit-gradient(linear, 0 0, 0 100%, from(#F39823), to(#F37221)); font-size:14px; font-weight:bold; color:#FFF; border:1px solid #FFF; border-radius:5px; -webkit-box-shadow:0 3px 3px 0 #9F9F9F; } +.review-product-list .buttons-set button { + padding:5px 10px; + background:-webkit-gradient(linear, 0 0, 0 100%, from(#F39823), to(#F37221)); + background:-o-linear-gradient(top, #F39823 0%, #F37221 100%); + font-size:14px; + color:#FFF; + border:1px solid #FFF; + border-radius:5px; + -webkit-box-shadow:0 3px 3px 0 #9F9F9F; + box-shadow:0 3px 3px 0 #9F9F9F; + text-shadow:0 1px 2px rgba(0, 0, 0, 0.25); +} /* Product Review Page -----------------------------*/ -.my-account .product-review { padding:10px 10px 0; } -.my-account .product-review .page-title, -.review-customer-index .my-account .page-title, -.customer-address-form .page-title { background:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#e5e5e5)); border:none; border-bottom:1px solid #ccc; height:20px; margin:-10px -10px 10px; -webkit-box-shadow:none; } -.my-account .product-review .page-title h1, -.review-customer-index .page-title h1, -.customer-address-form .page-title h1 { font-size:16px; line-height:20px; } .my-account .product-review .product-img-box {} .my-account .product-review .product-img-box > * { display:none; } .my-account .product-review .product-img-box > a { display:block; float:left; margin-right:10px; } -.my-account .product-review .product-img-box img { -webkit-border-radius:1px; -webkit-box-shadow:0 0 3px #D1D1D1; } +.my-account .product-review .product-img-box img { -webkit-border-radius:1px; -webkit-box-shadow:0 0 3px #D1D1D1; box-shadow:0 0 3px #D1D1D1; } .my-account .product-review .product-details h2 { font-size:16px; font-weight:bold; line-height:1.3; margin:0; } .my-account .product-review .product-details h3 { font-size:13px; margin:2px 0; } .my-account .product-review .product-details dl { clear:left; padding:10px 0 0; } @@ -1987,11 +2457,19 @@ section .category-title { display:none; } .customer-address-form .buttons-set .back-link small { display:none; } .review-customer-index section[role="main"] { padding-bottom:0; } -.review-customer-index .my-account .page-title { margin:0; } -.review-customer-index .pager { background:-webkit-gradient(linear, 0 0, 0 100%, from(#fefefe), to(#eee)); overflow:hidden; padding:5px 10px; text-align:right; } +.review-customer-index .my-account .page-title { margin-bottom:0; } +.review-customer-index .pager { + background:-webkit-gradient(linear, 0 0, 0 100%, from(#fefefe), to(#eee)); + background:-o-linear-gradient(top, #fefefe 0%, #eee 100%); + overflow:hidden; + margin:0 -10px; + padding:5px 10px; + text-align:right; +} .review-customer-index .pager .amount { float:left; line-height:30px; } .review-customer-index .pager select { width:60px; } -.review-customer-index table { font-size:14px; line-height:16px; border-collapse:collapse; width:100%; } +.review-customer-index .my-reviews-table-wrap { margin:0 -10px; } +.review-customer-index table { font-size:12px; line-height:16px; border-collapse:collapse; width:100%; } .review-customer-index table th, .review-customer-index table td { border:solid #eee; border-width:1px 0; padding:7px 5px; } .review-customer-index table td { vertical-align:top; } @@ -2004,12 +2482,31 @@ section .category-title { display:none; } /* Cart -----------------------------*/ .cart-shared { margin:0 0 -10px; } -.cart-shared .page-title { background:#eee; border:solid #ccc; border-width:1px 0; margin:0 -10px 10px; padding:10px; text-align:left; -webkit-box-shadow:none; } +.cart-shared .page-title { background:#eee; border:solid #ccc; border-width:1px 0; margin:0 -10px 10px; padding:10px; text-align:left; -webkit-box-shadow:none; box-shadow:none; } .cart-shared .page-title h1 { display:block; margin:0 auto; line-height:30px; text-shadow:0 1px 1px #fff; text-align:center; } .cart-shared .page-title a { background:#ccc; border:1px solid #bbb; display:block; float:left; margin:-30px 0 0; font-size:11px; font-weight:bold; padding:5px 10px; -webkit-border-radius:4px; text-shadow:none; } - +.cart-shared .label, +.cart-shared .weee .price { display:block; font-weight:bold; white-space:nowrap; } .cart-shared .messages { margin:10px 0 7px; } .cart-shared fieldset { border-bottom:1px solid #ccc; margin:0 -10px; padding:0 10px 5px; } +.remove-all-button { display:none; padding:10px 5px 15px; text-align:center; } +.remove-all-button a { + background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b), color-stop(100%, #c43c35)); + background:-o-linear-gradient(top, #ee5f5b 0%, #c43c35 100%); + border:1px solid #fff; + color:#fff; + font-weight:bold; + border-radius:5px; + display:inline-block; + padding:5px 0; + width:100%; + -webkit-box-shadow:0 3px 3px 0 #9f9f9f; + box-shadow:0 3px 3px 0 #9f9f9f; + text-shadow:0 -1px 2px rgba(0, 0, 0, 0.25); +} +.cart-shared .remove-all-button { border-bottom:1px solid #eae8ea; } +.my-wishlist .remove-all-button { float:left; padding:0 8px 0 0; } +.my-wishlist .remove-all-button a { font-size:11px; padding:5px; box-sizing:border-box; -webkit-box-sizing:border-box; } .cart-table-wrap { position:relative; @@ -2042,7 +2539,8 @@ section .category-title { display:none; } .cart-table-wrap.grouped-items .cart-table .item-options, .cart-table-wrap.grouped-items .cart-table .item-qty, .cart-table-wrap.grouped-items .cart-table .cart-price, -.cart-table-wrap.grouped-items .cart-table .price-box { +.cart-table-wrap.grouped-items .cart-table .price-box, +.cart-table-wrap.grouped-items .cart-table .label { display:none; } @@ -2055,18 +2553,28 @@ section .category-title { display:none; } .cart-shared .cart-table th { background:#ddd; font-weight:normal; } .cart-shared .cart-table th:first-child { -webkit-border-top-left-radius:5px; } .cart-shared .cart-table th:last-child { -webkit-border-top-right-radius:5px; } +.cart-shared .cart-table tr:last-child td { border:0; } .cart-shared .cart-table .odd { background:#eee; } .cart-shared .cart-table td b + input { vertical-align:middle; } .cart-shared .cart-table td input { border:1px solid #ccc; border-radius:3px; font-size:12px; font-weight:bold; margin:0 0 0 10px; padding:4px 5px; -webkit-appearance:none; -webkit-background-clip:padding-box; width:25px; -webkit-box-sizing:content-box; } .cart-shared .cart-table tfoot td { padding:0; vertical-align:top; } -.cart-shared .cart-table tfoot button { background:-webkit-gradient(linear, 0 0, 0 100%, from(#333), to(#111)); border:none; color:#fff; font-size:13px; font-weight:bold; padding:8px 10px; text-shadow:0 1px 0 #000; -webkit-border-radius:0 0 5px 5px; width:100%; } +.cart-shared .cart-table tfoot button { + background:-webkit-gradient(linear, 0 0, 0 100%, from(#333), to(#111)); + background:-o-linear-gradient(top, #333 0%, #111 100%); + border:none; + color:#fff; + font-size:13px; + font-weight:bold; + padding:8px 10px; + text-shadow:0 1px 0 #000; + -webkit-border-radius:0 0 5px 5px; + width:100%; +} .cart-shared .cart-table tbody td { vertical-align:top; } .cart-shared .cart-table .product-name { margin:0 0 10px; } .cart-shared .cart-table .product-name a { font-size:12px; color:#2f2f2f; } -.cart-shared .cart-table .product-image img { border:1px solid #FFF; -webkit-box-shadow:1px 1px 3px 0 #ccc; } -.cart-shared .cart-table .item-remove, -.btn-remove, -.item-remove { +.cart-shared .cart-table .product-image img { border:1px solid #FFF; -webkit-box-shadow:1px 1px 3px 0 #ccc; box-shadow:1px 1px 3px 0 #ccc; } +.cart-shared .cart-table .item-remove, .btn-remove, .item-remove { background:#e10000; border-radius:8px; display:block; @@ -2078,6 +2586,14 @@ section .category-title { display:none; } width:10px; -webkit-background-clip:padding-box; } +.cart-shared .totals .btn-remove { + background:none; + padding:0; + height:auto; + width:auto; + top:3px; + margin-left:2px; +} .cart-shared .cart-table .item-remove:before, .btn-remove:before, .item-remove:before { @@ -2085,6 +2601,7 @@ section .category-title { display:none; } background:#fff; border-radius:9px; -webkit-box-shadow:0 0 6px #888; + box-shadow:0 0 6px #888; display:block; position:absolute; top:-2px; @@ -2101,22 +2618,103 @@ section .category-title { display:none; } .cart-shared .totals table { border-spacing:0; font-size:12px; width:100%; } .cart-shared .totals td { padding:2px; } -.cart-shared .discount { font-size:12px; margin:0 -10px 20px; padding:10px 15px 15px; } -.cart-shared .discount h2 { font-size:12px; font-weight:bold; margin:0; text-shadow:0 1px 0 #fff; } +.cart-shared .totals .summary-total td[colspan="1"] { text-align:right; } +.cart-shared .totals .summary-collapse { cursor:pointer; text-decoration:underline; } +.cart-shared .discount, +.cart-shared .giftcard { font-size:12px; margin:0 -10px 20px; padding:10px 15px 15px; } +.cart-shared .giftcard a, +.cart-shared .discount a { display:inline-block; padding:5px 0; text-decoration:underline; } +.cart-shared .discount h2, +.cart-shared .giftcard h2 { font-size:12px; font-weight:bold; margin:0; text-shadow:0 1px 0 #fff; } +.cart-shared .giftcard label, .cart-shared .discount label { display:none; } +.cart-shared .giftcard .input-box, .cart-shared .discount .input-box { display:inline-block; } -.cart-shared .discount .input-box input { border:1px solid #CAC8C8; border-radius:2px; font-size:11px; line-height:1; padding:5px 10px; -webkit-appearance:none; width:140px; -webkit-box-shadow:inset 0 1px 2px #ccc; -webkit-background-clip:padding-box; } -.cart-shared .discount .buttons-set { display:inline-block; } -.cart-shared .discount .buttons-set button { padding:5px 10px; background:-webkit-gradient(linear, 0 0, 0 100%, from(#E2E2E2), to(#9D9D9D)); font-size:11px; font-weight:bold; color:#FFF; border:1px solid #FFF; border-radius:5px; -webkit-box-shadow:0 3px 3px 0 #9F9F9F; -webkit-background-clip:padding-box; } -.cart-shared .checkout-types { float:right; } +.cart-shared .giftcard .input-box input, +.cart-shared .discount .input-box input { + border:1px solid #CAC8C8; + border-radius:2px; + font-size:11px; + line-height:1; + padding:5px 10px; + width:140px; + -webkit-appearance:none; + appearance:none; + -webkit-box-shadow:inset 0 1px 2px #ccc; + box-shadow:inset 0 1px 2px #ccc; + -webkit-background-clip:padding-box; + background-clip:padding-box; +} +.cart-shared .discount + .giftcard { margin-top:-30px; } +.cart-shared .discount .buttons-set { display:inline-block; vertical-align:top; margin:-1px 0 0; } +.cart-shared .giftcard button, +.cart-shared .discount .buttons-set button { + background:-webkit-gradient(linear, 0 0, 0 100%, from(#E2E2E2), to(#9D9D9D)); + background:-o-linear-gradient(top, #E2E2E2 0%, #9D9D9D 100%); + border:1px solid #FFF; + border-radius:5px; + color:#fff; + font-size:11px; + font-weight:bold; + margin:0; + padding:6px 10px; + vertical-align:top; + -webkit-box-shadow:0 3px 3px 0 #9F9F9F; + box-shadow:0 3px 3px 0 #9F9F9F; + -webkit-background-clip:padding-box; + background-clip:padding-box; + text-shadow:0 -1px 2px rgba(0, 0, 0, 0.25); +} +.cart-shared .checkout-types { float:right; width:65%; } .cart-shared .checkout-types li { text-align:right; } .cart-shared .checkout-types li:first-child {margin:0 0 10px; } -.cart-shared .checkout-types li button { clear:both; display:block; float:right; margin:10px 0; padding:5px 20px; background:-webkit-gradient(linear, 0 0, 0 100%, from(#F39823), to(#F37221)); font-size:14px; font-weight:bold; color:#FFF; border:1px solid #FFF; border-radius:5px; -webkit-box-shadow:0 3px 3px 0 #9F9F9F; -webkit-background-clip:padding-box; } -#update-cart { float:left; margin:10px 0; padding:5px 10px; background:-webkit-gradient(linear, 0 0, 0 100%, from(#E2E2E2), to(#9D9D9D)); font-size:14px; font-weight:bold; color:#FFF; border:1px solid #FFF; border-radius:5px; -webkit-box-shadow:0 3px 3px 0 #9F9F9F; -webkit-background-clip:padding-box; } +.cart-shared .checkout-types li button { + background:-webkit-gradient(linear, 0 0, 0 100%, from(#F39823), to(#F37221)); + background:-o-linear-gradient(top, #F39823 0%, #F37221 100%); + clear:both; + display:block; + float:right; + margin:10px 0; + padding:5px 20px; + font-size:14px; + font-weight:bold; + color:#FFF; + border:1px solid #FFF; + border-radius:5px; + -webkit-box-shadow:0 3px 3px 0 #9F9F9F; + box-shadow:0 3px 3px 0 #9F9F9F; + -webkit-background-clip:padding-box; + background-clip:padding-box; + text-shadow:0 1px 2px rgba(0, 0, 0, 0.25); +} +#update-cart { + background:-webkit-gradient(linear, 0 0, 0 100%, from(#E2E2E2), to(#9D9D9D)); + background:-o-linear-gradient(top, #E2E2E2 0%, #9D9D9D 100%); + float:left; + margin:10px 0; + padding:5px 10px; + font-size:14px; + font-weight:bold; + color:#FFF; + border:1px solid #FFF; + border-radius:5px; + -webkit-box-shadow:0 3px 3px 0 #9F9F9F; + box-shadow:0 3px 3px 0 #9F9F9F; + -webkit-background-clip:padding-box; + background-clip:padding-box; + text-shadow:0 -1px 2px rgba(0, 0, 0, 0.25); +} .cart-shared .checkout-types .paypal-or { color:#333; display:block; padding:0 0 5px; text-shadow:0 0 1px #fff; } .cart-shared .checkout-types li:nth-child(3) > a { display:inline-block; margin:10px 0 0; padding:10px; } -.cart-shared .cart-footer { margin:0 -10px; padding:10px; background:-webkit-gradient(linear, 0 0, 0 100%, from(#FDFDFD), to(#CFD0D1)); -webkit-box-shadow: 0px 0px 10px 0px #222; } +.cart-shared .cart-footer { + background:-webkit-gradient(linear, 0 0, 0 100%, from(#FDFDFD), to(#CFD0D1)); + background:-o-linear-gradient(top, #FDFDFD 0%, #CFD0D1 100%); + margin:0 -10px; + padding:10px; + -webkit-box-shadow: 0px 0px 10px 0px #222; + box-shadow: 0px 0px 10px 0px #222; +} .cart-shared .cart-footer:after { content:"."; display:block; clear:both; visibility:hidden; line-height:0; height:0; } .checkout-agreements { @@ -2134,6 +2732,7 @@ section .category-title { display:none; } max-height:250px; overflow:auto; -webkit-box-shadow:inset 0 0 3px #000; + box-shadow:inset 0 0 3px #000; } .checkout-agreements .agree { @@ -2145,56 +2744,135 @@ section .category-title { display:none; } .a-center { text-align:center; } .a-right { text-align:right; } -.page-title { background:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#ccc)); border:solid #999; border-width:1px 0; border-top-color:#ccc; text-shadow:0 1px 0 #fff; margin:0 -10px 10px; padding:10px; -webkit-box-shadow:0 3px 3px #eee; } +.page-title { + background:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#ccc)); + background:-o-linear-gradient(top, #fff 0%, #ccc 100%); + border:solid #999; + border-width:1px 0; + border-top-color:#ccc; + text-shadow:0 1px 0 #fff; + margin:0 -10px 10px; + padding:10px; + -webkit-box-shadow:0 3px 3px #eee; + box-shadow:0 3px 3px #eee; +} /* User account -----------------------------*/ -.my-account { margin:0 -10px; } -.my-account .messages { padding:10px 10px 0; } -.my-account .messages .success-msg { margin:-10px 0 0; } +.show-links { position:absolute; top:53px; left:10px; z-index:99; } +.my-account { margin:0; } +.my-account .messages { margin:-10px -10px 10px; padding:0; } +.my-account .messages .success-msg { margin-bottom:0; } .my-account .messages .success-msg a { color:#fff; } -.my-account .breadcrumbs { margin:0; } - +.my-account .breadcrumbs {} +.my-account .welcome-msg { margin:0 0 10px; } -.customer-account-index .my-account .messages { margin:10px 0 -10px; padding:0; } +.customer-account-index .my-account .messages { margin:0; padding:0; } +.my-account .box { margin-bottom:10px; } .my-account .box-account { background:#fff; padding:0 0 10px; } -.my-account .box-info { padding-bottom:0; } -.my-account .box-info .box-head { margin-bottom:0; } -.my-account .box-info .box-title { padding:5px 10px; } -.my-account .box-info .box-content { padding:10px; } -.my-account .box-info .col2-set:nth-child(3) .col-1, -.my-account .box-info .col2-set:nth-child(3) .col-2 { display:inline-block; vertical-align:top; width:49%; } -.my-account .box-content p a {} -.my-account .box-reviews .number { display:none; } - -.my-account .page-title { background:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#ccc)); border:solid #999; border-width:1px 0; border-top-color:#ccc; text-shadow:0 1px 0 #fff; padding:10px; -webkit-box-shadow:0 3px 3px #eee; margin:0; } - -.my-account .box-head { background:-webkit-gradient(linear, left top, left bottom, color-stop(0%,#ffb76b), color-stop(50%,#ffa73d), color-stop(51%,#ff7c00), color-stop(100%,#ff7f04)); border-bottom:1px solid #999; margin:0 0 10px; padding:7px 10px 8px; position:relative; } -.my-account .box-head a { background:-webkit-gradient(linear, 0 0, 0 100%, from(#eee), to(#fff)); font-weight:bold; position:absolute; padding:2px 10px; top:3px; right:5px; -webkit-border-radius:4px; -webkit-box-shadow:inset 0 0 2px #000; text-shadow:0 1px 0 #fff; } +.my-account .info-box { padding-bottom:0; } +.my-account .info-box .box-head { margin-bottom:0; } +.my-account .info-box .box-title { padding:5px 10px; } +.my-account .info-box .box-content { border:1px solid #aaa; padding:10px; margin:0 0 10px; } +.my-account .info-box .col2-set:nth-child(3) .col-1, +.my-account .info-box .col2-set:nth-child(3) .col-2 { display:inline-block; vertical-align:top; width:49%; } +.my-account .box-content a { text-decoration:underline; } +.my-account .dashboard .reviews ol { list-style:none; margin:0; padding:0; } +.my-account .dashboard .reviews .number { float:left; margin:0 10px 0 0; } +.my-account .dashboard .reviews .details { display:table; } +.my-account .dashboard .reviews .details p { display:inline; vertical-align:middle; } +.my-account .dashboard .reviews .details .rating-box { display:inline-block; vertical-align:middle; margin:0; } + +.my-account .my-wishlist { margin:0 -10px; } + +.my-account .page-title { + background:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#ccc)); + background:-o-linear-gradient(top, #fff 0%, #ccc 100%); + border:solid #999; + border-width:1px 0; + border-top-color:#ccc; + margin:0 -10px 10px; + padding:10px; + -webkit-box-shadow:0 3px 3px #eee; + box-shadow:0 3px 3px #eee; + text-shadow:0 1px 0 #fff; +} + +.my-account .box-head { + background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #ffb76b), color-stop(50%, #ffa73d), color-stop(51%, #ff7c00), color-stop(100%, #ff7f04)); + background:-o-linear-gradient(top, #ffb76b 0%, #ffa73d 50%, #ff7c00 51%, #ff7f04 100%); + border-bottom:1px solid #999; + padding:7px 10px 8px; + position:relative; +} +.my-account .box-head a { + background:-webkit-gradient(linear, 0 0, 0 100%, from(#eee), to(#fff)); + background:-o-linear-gradient(top, #eee 0%, #fff 100%); + font-weight:bold; + position:absolute; + padding:2px 10px; + top:3px; + right:5px; + -webkit-border-radius:4px; + border-radius:4px; + -webkit-box-shadow:inset 0 0 2px #000; + box-shadow:inset 0 0 2px #000; + text-shadow:0 1px 0 #fff; +} .my-account .box-head h2 { color:#f6f6f6; font-weight:bold; text-shadow:0 0 3px #333; line-height:1; } -.my-account .box-recent .data-table { border-spacing:0; margin:-10px 0; width:100%; } +.my-account .box-recent .data-table { border-spacing:0; width:100%; } .my-account .box-recent .data-table th, .my-account .box-recent .data-table td { padding:2px 5px; } -.my-account .box-recent .data-table th { background:-webkit-gradient(linear, 0 0, 0 100%, from(#eee), to(#ccc)); border-bottom:1px solid #aaa; text-shadow:0 1px 0 #fff; } -.my-account .box-recent .data-table tr:nth-child(odd) { background:#f6f6f6; } -.my-account .box-recent .data-table tr td:first-child a { display:block; background:-webkit-gradient(linear, 0 0, 0 100%, from(#eee), to(#fff)); font-weight:bold; margin:4px 0; padding:2px 10px; -webkit-border-radius:4px; -webkit-box-shadow:inset 0 0 2px #000; text-shadow:0 1px 0 #fff; } +.my-account .box-recent .data-table th { + background:-webkit-gradient(linear, 0 0, 0 100%, from(#eee), to(#ccc)); + background:-o-linear-gradient(top, #eee 0%, #ccc 100%); + border-bottom:1px solid #aaa; + text-shadow:0 1px 0 #fff; +} +.my-account .box-recent .data-table tr:nth-child(odd) { background:#f6f6f6; } +.my-account .box-recent .data-table tr td:nth-child(3) { text-align:center; } +.my-account .box-recent .data-table tr td:first-child a { + display:block; + background:-webkit-gradient(linear, 0 0, 0 100%, from(#eee), to(#fff)); + background:-o-linear-gradient(top, #eee 0%, #fff 100%); + font-weight:bold; + margin:4px 0; + padding:2px 10px; + -webkit-border-radius:4px; + border-radius:4px; + -webkit-box-shadow:inset 0 0 2px #000; + box-shadow:inset 0 0 2px #000; + text-shadow:0 1px 0 #fff; +} -.my-account .box-title { background:-webkit-gradient(linear, 0 0, 0 100%, from(#eee), to(#ccc)); border-top:1px solid #bbb; border-bottom:1px solid #aaa; font-size:14px; text-shadow:0 1px 0 #fff; position:relative; } -.my-account .box-title a { position:absolute; top:5px; right:5px; } +.my-account .box-title { + background:-webkit-gradient(linear, 0 0, 0 100%, from(#eee), to(#ccc)); + background:-o-linear-gradient(top, #eee 0%, #ccc 100%); + border:1px solid; + border-color:#bbb #aaa; + color:#333; + font-size:13px; + text-shadow:0 1px 0 #fff; + position:relative; +} +.my-account .box-title .separator { display:none; } +.my-account .box-title a { position:absolute; top:5px; right:5px; text-decoration:underline; } .my-account .box-title h3 { display:inline-block; } .my-account .box-content h4 {} .my-account .col2-set .col-1, .my-account .col2-set .col-2 {} .my-account .col2-set .col-1:last-child, -.my-account .col2-set .col-2:last-child { -} -.my-account .back-link { float:left; margin:0 15px 0 0; } +.my-account .col2-set .col-2:last-child {} +.my-account .sub-title, +.my-account .legend { color:#333; font-size:15px; font-weight:bold; margin:0 0 5px; text-shadow:0 1px 0 #ddd; } +.my-account .back-link { display:none; float:left; margin:0 15px 0 0; } .my-account .input-box select { font-size:16px; } -.my-account .buttons-set { clear:both; } +.my-account .buttons-set, +.my-account .buttons-set2 { clear:both; text-align:center; } .my-account .buttons-set:after { content:"."; display:block; @@ -2203,11 +2881,11 @@ section .category-title { display:none; } line-height:0; height:0; } -.my-account .welcome-msg, .my-account label em, .my-account .buttons-set .required, -.my-account label[for="street_1"], -.my-account h2.legend { display:none; } +.my-account label[for="street_1"] { display:none; } + +.my-account .storecredit .account-balance { margin:0 0 10px; } .sales-order-view .my-account { margin:0; @@ -2216,6 +2894,7 @@ section .category-title { display:none; } border-collapse:collapse; margin:10px 0 15px; -webkit-box-shadow:0 3px 6px #ccc; + box-shadow:0 3px 6px #ccc; } .sales-order-view #my-orders-table th, .sales-order-view #my-orders-table td { @@ -2247,6 +2926,7 @@ section .category-title { display:none; } .sales-order-view #my-orders-table tbody tr:first-child th, .sales-order-view #my-orders-table tbody tr:first-child th + td { background:-webkit-gradient(linear, 0 0, 0 100%, from(#f6f6f6), to(#ddd)); + background:-o-linear-gradient(top, #f6f6f6 0%, #ddd 100%); display:table-cell; padding:5px; font-weight:normal; @@ -2302,12 +2982,18 @@ section .category-title { display:none; } .sales-order-history .data-table { border-spacing:0; margin:-10px 0 0 0; width:100%; } .sales-order-history .data-table th, .sales-order-history .data-table td { padding:2px 5px; } -.sales-order-history .data-table th { background:-webkit-gradient(linear, 0 0, 0 100%, from(#eee), to(#ccc)); border-bottom:1px solid #aaa; text-shadow:0 1px 0 #fff; } +.sales-order-history .data-table th { + background:-webkit-gradient(linear, 0 0, 0 100%, from(#eee), to(#ccc)); + background:-o-linear-gradient(top, #eee 0%, #ccc 100%); + border-bottom:1px solid #aaa; + text-shadow:0 1px 0 #fff; +} .sales-order-history .data-table th:nth-child(4) { width:20%; text-align:left; } .sales-order-history .data-table tr:nth-child(odd) { background:#f6f6f6; } .sales-order-history .amount { display:none; } .sales-order-history .pager { - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#FFB76B), color-stop(50%,#FFA73D), color-stop(51%,#FF7C00), color-stop(100%,#FF7F04)); + background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #FFB76B), color-stop(50%, #FFA73D), color-stop(51%, #FF7C00), color-stop(100%, #FF7F04)); + background:-o-linear-gradient(top, #FFB76B 0%, #FFA73D 50%, #FF7C00 51%, #FF7F04 100%); border-bottom: 1px solid #999; margin: 0 0 10px; padding: 7px 10px 8px; @@ -2316,6 +3002,7 @@ section .category-title { display:none; } text-shadow: 0 0 3px #333; line-height: 1; text-align:right; + overflow:hidden; } .sales-order-history .limiter { float:left; @@ -2329,7 +3016,8 @@ section .category-title { display:none; } padding: 5px 10px; border: 1px solid #CECECE; border-radius: 5px; - background: -webkit-gradient(linear, 0 0, 0 100%, from(#FEFEFE), to(#EEE)); + background: -webkit-gradient(linear, 0 0, 0 100%, from(#fefefe), to(#eee)); + background:-o-linear-gradient(top, #fefefe 0%, #eee 100%); font-size: 16px; text-transform: small-caps; } @@ -2338,59 +3026,57 @@ section .category-title { display:none; } padding: 5px 10px; border: 1px solid #CECECE; border-radius: 5px; - background: -webkit-gradient(linear, 0 0, 0 100%, from(#FEFEFE), to(#EEE)); + background: -webkit-gradient(linear, 0 0, 0 100%, from(#fefefe), to(#eee)); + background:-o-linear-gradient(top, #fefefe 0%, #eee 100%); font-size: 16px; text-transform: small-caps; } -.customer-account-edit form, -.customer-address-form form { padding:0 10px; } -.customer-address-form form .buttons-set { padding:10px 0; } -.customer-account-edit select, -.customer-address-form form select { border:1px solid; border-color:#8e8e8e #e1e1e1 #e1e1e1 #8e8e8e; border-radius:5px; font-size:18px; padding:5px 30px 5px 5px; width:100%; margin:0; } -.customer-account-edit .field label, -.customer-address-form form .field label, -.customer-address-form form .wide label { color:#5e5e5e; display:block; font-size:12px; line-height:16px; margin:8px 0 0; padding:0 0 3px; font-weight:bold; } -.customer-account-edit .buttons-set button[type=submit], -.customer-address-form form .buttons-set button[type=submit] { - display:block; - margin:0; - padding:5px 20px; + +.my-account form label, +.my-account form .field label, +.my-account form .wide label { color:#5e5e5e; display:block; font-size:12px; line-height:16px; margin:8px 0 0; padding:0 0 3px; font-weight:bold; } +.my-account form .buttons-set { padding:10px 0; } +.my-account .form-list label { display:block; margin:5px 0 2px 0; } +.my-account .form-list select { border:1px solid; border-color:#8e8e8e #e1e1e1 #e1e1e1 #8e8e8e; border-radius:5px; font-size:18px; padding:5px 30px 5px 5px; width:100%; margin:0; } +.my-account .form-list .control .checkbox + label, +.my-account .form-list .control .input-box + label { display:inline; margin-left:2px; vertical-align:middle; } +.my-account button { + display:inline-block; background:-webkit-gradient(linear, 0 0, 0 100%, from(#F39823), to(#F37221)); - font-size:14px; + background:-o-linear-gradient(top, #F39823 0%, #F37221 100%); color:#FFF; + font-size:14px; border:1px solid #FFF; border-radius:5px; + margin:0; + padding:5px 10px; + vertical-align:top; -webkit-box-shadow:0 3px 3px 0 #9F9F9F; -} -.customer-account-edit .customer-name label { display:block; margin:5px 0 2px 0; } -.newsletter-manage-index .my-account form { padding:10px 10px 0; } -.newsletter-manage-index .my-account .buttons-set button[type=submit] { - float:right; - padding: 5px 10px; - border: 1px solid #CECECE; - border-radius: 5px; - background: -webkit-gradient(linear, 0 0, 0 100%, from(#FEFEFE), to(#EEE)); - font-size: 16px; - text-transform: small-caps; + box-shadow:0 3px 3px 0 #9F9F9F; + text-shadow:0 1px 2px rgba(0, 0, 0, 0.25); } .newsletter-manage-index .my-account .back-link a { display:block; padding: 5px 10px; border: 1px solid #CECECE; border-radius: 5px; - background: -webkit-gradient(linear, 0 0, 0 100%, from(#FEFEFE), to(#EEE)); + background: -webkit-gradient(linear, 0 0, 0 100%, from(#fefefe), to(#eee)); + background:-o-linear-gradient(top, #fefefe 0%, #eee 100%); font-size: 16px; text-transform: small-caps; } .customer-address-index .page-title h1 { float:left; padding:5px 0 0 0; } .customer-address-index .page-title button { float:right; - background: -webkit-gradient(linear, 0 0, 0 100%, from(#EEE), to(white)); + background: -webkit-gradient(linear, 0 0, 0 100%, from(#eee), to(#fff)); + background:-o-linear-gradient(top, #eee 0%, #fff 100%); font-weight: bold; padding: 6px 10px; border:0; -webkit-border-radius: 4px; + border-radius: 4px; -webkit-box-shadow: inset 0 0 2px #000; + box-shadow: inset 0 0 2px #000; text-shadow: 0 1px 0 white; } .customer-address-index .page-title:after { @@ -2401,28 +3087,26 @@ section .category-title { display:none; } line-height: 0; height: 0; } -.customer-address-index .addresses-list { padding:0 10px; } -.customer-address-index .addresses-list ol { - list-style:none; - margin:0; - padding:10px 0; -} - .customer-address-index .addresses-list ol li { - display:inline-block; - vertical-align:top; - width:45%; - } +.customer-address-index .addresses-list {} +.customer-address-index .addresses-list ol { list-style:none; margin:0; padding:0; } +.customer-address-index .addresses-list ol li {} .customer-address-index .back-link a { display:block; margin:0 0 0 10px; padding: 5px 10px; border: 1px solid #CECECE; border-radius: 5px; - background: -webkit-gradient(linear, 0 0, 0 100%, from(#FEFEFE), to(#EEE)); + background: -webkit-gradient(linear, 0 0, 0 100%, from(#fefefe), to(#eee)); + background:-o-linear-gradient(top, #fefefe 0%, #eee 100%); font-size: 16px; text-transform: small-caps; } +.block-account { margin:0; padding:0; position:absolute; width:100%; left:-100%; z-index:100; -webkit-box-shadow:0 3px 3px rgba(0, 0, 0, .25) } +.block-account .block-title { display:none; } +.block-account li { background:#eee; border-bottom:1px solid #ccc; padding:10px 10px 10px 100px; } +.block-account .back { background:#333; color:#fff; padding:10px; position:absolute; top:0; bottom:0; left:0; width:60px; } + /* Order -----------------------------*/ @@ -2443,13 +3127,15 @@ section .category-title { display:none; } .sales-order-shipment .page-title a, .sales-order-view .page-title a { float:right; - background: -webkit-gradient(linear, 0 0, 0 100%, from(#EEE), to(white)); + background:-webkit-gradient(linear, 0 0, 0 100%, from(#eee), to(#fff)); + background:-o-linear-gradient(top, #eee 0%, #fff 100%); font-weight: bold; margin:0 0 0 10px; padding: 6px 10px; border:0; -webkit-border-radius: 4px; -webkit-box-shadow: inset 0 0 2px #000; + box-shadow: inset 0 0 2px #000; text-shadow: 0 1px 0 white; } .sales-order-view .page-title .link-print { @@ -2479,7 +3165,8 @@ section .category-title { display:none; } padding: 5px 10px; border: 1px solid #CECECE; border-radius: 5px; - background: -webkit-gradient(linear, 0 0, 0 100%, from(#FEFEFE), to(#EEE)); + background: -webkit-gradient(linear, 0 0, 0 100%, from(#fefefe), to(#eee)); + background:-o-linear-gradient(top, #fefefe 0%, #eee 100%); font-size: 16px; text-transform: small-caps; } @@ -2525,9 +3212,10 @@ section .category-title { display:none; } .sales-order-view .order-info dd ul li a { display:block; padding: 5px 10px; - border: 1px solid #CECECE; + border: 1px solid #cecece; border-radius: 5px; - background: -webkit-gradient(linear, 0 0, 0 100%, from(#FEFEFE), to(#EEE)); + background: -webkit-gradient(linear, 0 0, 0 100%, from(#fefefe), to(#eee)); + background:-o-linear-gradient(top, #fefefe 0%, #eee 100%); font-size: 16px; text-transform: small-caps; } @@ -2538,15 +3226,52 @@ section .category-title { display:none; } /* Wishlist -----------------------------*/ -.my-wishlist h1 { background:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#ccc)); border:solid #999; border-width:1px 0; border-top-color:#ccc; margin:0 0 10px; padding:10px; } +.my-wishlist h1 { + background:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#ccc)); + background:-o-linear-gradient(top, #fff 0%, #ccc 100%); + border:solid #999; + border-width:1px 0; + border-top-color:#ccc; + margin:0 0 10px; + padding:10px; +} .my-wishlist h2 { font-weight:bold; line-height:16px; } -.my-wishlist .buttons-set { margin:10px 0 -10px; padding: 10px; background:-webkit-gradient(linear, 0 0, 0 100%, from(#FDFDFD), to(#CFD0D1)); -webkit-box-shadow:0px 0px 10px 0px #222; } +.my-wishlist .buttons-set { + background:-webkit-gradient(linear, 0 0, 0 100%, from(#fdfdfd), to(#cfd0d1)); + background:-o-linear-gradient(top, #fdfdfd 0%, #cfd0d1 100%); + margin:10px 0 -10px; + padding: 10px; + -webkit-box-shadow:0px 0px 10px 0px rgba(0, 0, 0, .25); + box-shadow:0px 0px 10px 0px rgba(0, 0, 0, .25); +} .my-wishlist .buttons-set:after { content:"."; display:block; clear:both; visibility:hidden; line-height:0; height:0; } -.my-wishlist .buttons-set li:first-child { float:left; } +.my-wishlist .buttons-set li { float:left; } +.my-wishlist .buttons-set li:first-child { padding:0 8px 0 0; } .my-wishlist .buttons-set li:last-child { float:right; } -.my-wishlist .buttons-set li .add-all-to-cart { text-align:right; padding:5px 10px; background:-webkit-gradient(linear, 0 0, 0 100%, from(#F39823), to(#F37221)); font-size:11px; font-weight:bold; color:#FFF; border:1px solid #FFF; border-radius:5px; -webkit-box-shadow:0 3px 3px 0 #9F9F9F; } +.my-wishlist .buttons-set li .add-all-to-cart, +.my-wishlist .buttons-set li .btn-share { + text-align:right; + padding:5px 10px; + background:-webkit-gradient(linear, 0 0, 0 100%, from(#F39823), to(#F37221)); + background:-o-linear-gradient(top, #f39823 0%, #f37221 100%); + font-size:11px; + font-weight:bold; + color:#fff; + border:1px solid #fff; + border-radius:5px; + -webkit-box-shadow:0 3px 3px 0 #9f9f9f; + box-shadow:0 3px 3px 0 #9f9f9f; + text-shadow:0 1px 2px rgba(0, 0, 0, 0.25); +} +.my-wishlist .buttons-set li .btn-share { + margin:0; + line-height:18px; + -webkit-appearance:none; + appearance:none; +} .my-wishlist .buttons-set li .update-wishlist { - background:-webkit-gradient(linear, 0 0, 0 100%, from(#E2E2E2), to(#9D9D9D)); + background:-webkit-gradient(linear, 0 0, 0 100%, from(#e2e2e2), to(#9d9d9d)); + background:-o-linear-gradient(top, #e2e2e2 0%, #9d9d9d 100%); color:#fff; border:1px solid #fff; border-radius:5px; @@ -2557,35 +3282,61 @@ section .category-title { display:none; } font-weight:bold; line-height:18px; -webkit-box-shadow:0 3px 3px 0 #9F9F9F; + box-shadow:0 3px 3px 0 #9F9F9F; + text-shadow:0 -1px 2px rgba(0, 0, 0, 0.25); -webkit-appearance:none; + appearance:none; } .my-wishlist .buttons-set li span { display:inline-block; } -#wishlist-list { margin:-10px 0 0; } +#wishlist-list { margin:-10px 0 0; min-height:200px; } #wishlist-list li > a { float:left; padding:10px; } -#wishlist-list li > a img { border:1px solid #FFF; -webkit-box-shadow:1px 1px 3px 0 #ccc; } +#wishlist-list li > a img { border:1px solid #FFF; -webkit-box-shadow:1px 1px 3px 0 #ccc; box-shadow:1px 1px 3px 0 #ccc; } #wishlist-list li { border-bottom:1px solid #CCC; position:relative; min-height:80px; } #wishlist-list li:last-child { border-bottom:0; } -#wishlist-list li, #wishlist-list .wishlist-item { overflow:hidden; } -#wishlist-list .wishlist-item { padding:10px; } +#wishlist-list .wishlist-item { padding:10px; position:relative; } #wishlist-list .wishlist-item .price-box { margin:0 0 5px; } +#wishlist-list li.no-qty .qty-holder { display:none; } #wishlist-list .qty { width:25px; margin:-2px 0 0; padding:2px 3px; border-radius:5px; font-size:12px; } #wishlist-list button { margin:0; padding:0; border:0; } -#wishlist-list button.btn-cart { position:absolute; right:10px; bottom:10px; text-align:right; margin:0; padding:5px 10px; background:-webkit-gradient(linear, 0 0, 0 100%, from(#F39823), to(#F37221)); font-size:11px; font-weight:bold; color:#FFF; border:1px solid #FFF; border-radius:5px; -webkit-box-shadow:0 3px 3px 0 #9F9F9F; } -#wishlist-list .edit-wishlist-item { background:-webkit-gradient(linear, 0 0, 0 100%, from(#ff9822), to(#f30)); border-radius:5px; color:#fff; display:inline-block; font-size:16px; padding:7px 15px; text-shadow:0 1px 1px #111; -webkit-box-shadow:0 0 1px #000; } -#wishlist-list tr { border-bottom:1px solid #CCC; } -#wishlist-list tr:last-child { border-bottom:0; } +#wishlist-list button.btn-cart { + background:-webkit-gradient(linear, 0 0, 0 100%, from(#f39823), to(#f37221)); + background:-o-linear-gradient(top, #f39823 0%, #f37221 100%); + text-align:right; + margin:0; + padding:5px 10px; + font-size:11px; + font-weight:bold; + color:#FFF; + border:1px solid #FFF; + border-radius:5px; + -webkit-box-shadow:0 3px 3px 0 #9F9F9F; + box-shadow:0 3px 3px 0 #9F9F9F; + text-shadow:0 1px 2px rgba(0, 0, 0, 0.25); +} +#wishlist-list .wishlist-item button.btn-cart { + position:absolute; + bottom:10px; + right:10px; +} +#wishlist-list .edit-wishlist-item { + background:-webkit-gradient(linear, 0 0, 0 100%, from(#ff9822), to(#f30)); + background:-o-linear-gradient(top, #ff9822 0%, #f30 100%); + border-radius:5px; + color:#fff; + display:inline-block; + font-size:16px; + padding:7px 15px; + -webkit-box-shadow:0 0 1px #000; + box-shadow:0 0 1px #000; + text-shadow:0 1px 1px #111; +} #wishlist-list a.btn-remove {} #wishlist-list a.btn-remove img { vertical-align:top; } #wishlist-list a.btn-edit { - background:-webkit-gradient( - linear, - 0 0, - 0 100%, - color-stop(1, #F5D605), - color-stop(0, #FF9D05) - ); + background:-webkit-gradient(linear, 0 0, 0 100%, color-stop(1, #f5d605), color-stop(0, #ff9d05)); + background:-o-linear-gradient(top, #ff9d05 0%, #f5d605 100%); font-family:Arial Rounded MT Bold; border:2px solid #fff; border-radius:15px; @@ -2600,7 +3351,33 @@ section .category-title { display:none; } text-shadow:0 1px 1px #999; vertical-align:middle; -webkit-box-shadow:0 0 5px rgba(0, 0, 0, 0.25); + box-shadow:0 0 5px rgba(0, 0, 0, 0.25); -webkit-background-clip:padding-box; + background-clip:padding-box; +} +#wishlist-list .add-to { + color:#666; + display:inline-block; + line-height:1.1; + margin:10px 0 10px 10px; + border-bottom:1px dashed; +} +#wishlist-list .gift-registry-select { + display:inline-block; + vertical-align:top; + text-align:center; +} +#wishlist-list .btn-gift-registry { + padding:10px; +} +#wishlist-list .btn-gift-registry select { + font-size:13px; +} +#wishlist-list .btn-gift-registry strong { + display:block; +} + +#wishlist-list .btn-gift-registry button.btn-cart { } .wishlist-wrap { position:relative; } .wishlist-wrap.grouped-items #wishlist-list li { min-height:initial; position:static; } @@ -2615,8 +3392,10 @@ section .category-title { display:none; } .wishlist-wrap.grouped-items b, .wishlist-wrap.grouped-items button, .wishlist-wrap.grouped-items input, +.wishlist-wrap.grouped-items .add-to, .wishlist-wrap.grouped-items .btn-remove, -.wishlist-wrap.grouped-items .btn-edit { +.wishlist-wrap.grouped-items .btn-edit, +.wishlist-wrap.grouped-items .btn-gift-registry { display:none !important; } .wishlist-wrap.grouped-items #wishlist-list .wishlist-item { @@ -2635,6 +3414,21 @@ section .category-title { display:none; } left:0; } +.giftregistry-table-wrap { margin:-10px -10px 10px; } +.giftregistry-table-wrap table { border-spacing:0; border-collapse:collapse; width:100%; } +.giftregistry-table-wrap th { background:#f6f6f6; text-align:left; white-space:nowrap; } +.giftregistry-table-wrap th, +.giftregistry-table-wrap td { border:1px solid #ddd; padding:5px 7px; } +.giftregistry-table-wrap td { vertical-align:top; } +.giftregistry-table-wrap td[colspan="2"] { background:-webkit-gradient(linear, 0 0, 0 100%, from(#eee), to(#ddd)); border-bottom-color:#ccc; text-align:center; text-shadow:0 1px 0 #fff; } +.giftregistry-table-wrap td .separator { color:#666; margin:0 2px; vertical-align:baseline; } + +.data-table-gift-registry-wrap { margin:-10px -10px 0; } +.data-table-gift-registry { border-spacing:0; border-collapse:collapse; margin:0 0 10px; width:100%; } +.data-table-gift-registry th { background:#f6f6f6; vertical-align:top; } +.data-table-gift-registry th, +.data-table-gift-registry td { border:1px solid #eee; padding:5px 10px; text-align:left; } +.data-table-gift-registry tr:nth-child(even) td { background:#f9f9f9; } /* Checkout -----------------------------*/ @@ -2642,40 +3436,66 @@ section .category-title { display:none; } margin:0 -10px 0 !important; } -.checkout-onepage-index .page-title { background:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#ccc)); border:solid #999; border-width:1px 0; border-top-color:#ccc; margin:0 -10px 10px; padding:10px; } -#checkoutSteps { margin:-7px; padding:0; list-style:none; -webkit-box-shadow:3px -3px 2px 0 #F0F0F0; color:#636363; } +.checkout-onepage-index .page-title { + background:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#ccc)); + background:-o-linear-gradient(top, #fff 0%, #ccc 100%); + border:solid #999; + border-width:1px 0; + border-top-color:#ccc; + margin:0 -10px 10px; + padding:10px; +} +#checkoutSteps { margin:-7px; padding:0; list-style:none; -webkit-box-shadow:3px -3px 2px 0 #F0F0F0; box-shadow:3px -3px 2px 0 #F0F0F0; color:#636363; } #checkoutSteps .step-title h2 { font-size:16px; font-weight:bold; } #checkoutSteps select { border:1px solid; border-color:#8e8e8e #e1e1e1 #e1e1e1 #8e8e8e; border-radius:5px; font-size:18px; padding:5px 30px 5px 5px; } #checkoutSteps li { margin:0 0 3px; } -#checkoutSteps li .step-title { margin:-1px 0 0 0; padding:5px 10px; border:1px solid #CECECE; border-radius:2px; background: -webkit-gradient(linear, 0 0, 0 100%, from(#FEFEFE), to(#EEE)); } +#checkoutSteps li .step-title { + background: -webkit-gradient(linear, 0 0, 0 100%, from(#fefefe), to(#eee)); + background:-o-linear-gradient(top, #fefefe 0%, #eee 100%); + margin:-1px 0 0 0; + padding:5px 10px; + border:1px solid #CECECE; + border-radius:2px; +} #checkoutSteps li.allow .step-title { + background:-webkit-gradient(linear, 0 0, 0 100%, from(#fbfbfb), to(#d1d3d4)); + background:-o-linear-gradient(top, #fbfbfb 0%, #d1d3d4 100%); margin:-1px 0 0 0; padding:5px 10px; - border:1px solid #C6C6C6; + border:1px solid #c6c6c6; border-radius:2px; - background:-webkit-gradient(linear, 0 0, 0 100%, from(#FBFBFB), to(#D1D3D4)); } -#checkoutSteps li.active .step-title { background:-webkit-gradient(linear, left top, left bottom, color-stop(0%,#ffa84c), color-stop(100%,#ff7b0d)); color:#fff; text-shadow:0 -1px 0 #ff7b0d; } +#checkoutSteps li.active .step-title { + background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #ffa84c), color-stop(100%, #ff7b0d)); + background:-o-linear-gradient(top, #ffa84c 0%, #ff7b0d 100%); + color:#fff; + text-shadow:0 -1px 0 #ff7b0d; +} #checkoutSteps li .step-title a { display:none; } #checkoutSteps li .step { + background: -webkit-gradient(linear, 0 0, 0 100%, from(#eee), to(#fefefe)); + background:-o-linear-gradient(top, #eee 0%, #fefefe 100%); margin:-1px 0 10px; padding:10px; border-radius:2px; - background: -webkit-gradient(linear, 0 0, 0 100%, from(#EEE), to(#FEFEFE)); border:1px solid #CECECE; } #checkoutSteps li .step select { width:100%; } #checkoutSteps li .step .input-box .v-fix { display:inline-block; width:49%; } -#checkoutSteps li .step .input-box .v-fix .year { width:70px; } +#checkoutSteps li .step .input-box .v-fix .year { width:auto; } #checkoutSteps li .step .input-box .cvv-what-is-this { display:inline-block; vertical-align:top; margin:0 0 0 5px; font-size:13px; color:#F4641E; } #checkoutSteps li .step .tool-tip .btn-close { padding:0 0 5px 0; text-align:right; font-size:13px; } #checkoutSteps li .step .tool-tip .btn-close a { color:#DF2327; } #checkoutSteps li .step .tool-tip .tool-tip-content img { width:100%; } +#checkoutSteps li .step .control .input-box { display:inline; } #checkoutSteps li .step input[type="radio"] { vertical-align:top; } #checkoutSteps li .step .sp-methods dt { font-weight:bold; } +#checkoutSteps li .step .sp-methods + div { padding:10px 0 0; } #checkoutSteps li .step .please-wait { margin:0 0 0 10px; } #checkoutSteps li .step .please-wait img { vertical-align:middle; } #checkoutSteps li .step fieldset .required em { margin:0 5px 0 0; color:#F4641E; } +#checkoutSteps li .step .buttons-set { padding:10px 0 0; } +#checkoutSteps #customerbalance_placer { padding:0 0 10px; } #checkoutSteps li .step .buttons-set:after { content:"."; display:block; @@ -2694,25 +3514,57 @@ section .category-title { display:none; } #checkoutSteps li .step .gift-messages-form .product-name { font-size:14px; } #checkoutSteps li .step .buttons-set .required { padding:0 0 10px; color:#F4641E; font-weight:bold; } #checkoutSteps li .step .back-link { float:left; } -#checkoutSteps li .step .back-link a {display:block; padding:5px 10px; background:-webkit-gradient(linear, 0 0, 0 100%, from(#F39823), to(#F37221)); font-size:11px; font-weight:bold; color:#FFF; border:1px solid #FFF; border-radius:5px; -webkit-box-shadow:0 3px 3px 0 #9F9F9F; } +#checkoutSteps li .step .back-link a { + background:-webkit-gradient(linear, 0 0, 0 100%, from(#f39823), to(#f37221)); + background:-o-linear-gradient(top, #f39823 0%, #f37221 100%); + display:block; + padding:5px 10px; + font-size:11px; + font-weight:bold; + color:#FFF; + border:1px solid #FFF; + border-radius:5px; + -webkit-box-shadow:0 3px 3px 0 #9F9F9F; + box-shadow:0 3px 3px 0 #9F9F9F; + text-shadow:0 1px 2px rgba(0, 0, 0, 0.25); +} #checkoutSteps li .step-title h2 { display:block; } #checkoutSteps li.allow .step-title h2 { background:url(../images/bg_checkout_step_passed.png) no-repeat center right; } #checkoutSteps li.active .step-title h2 { background:none !important; } #checkoutSteps li .step-title .number { display:none; } #checkoutSteps .form-list .field label, #checkoutSteps .form-list .wide label { padding:0; font-weight:bold; } -#checkoutSteps li .step .buttons-set button { float:right; padding:5px 10px; background:-webkit-gradient(linear, 0 0, 0 100%, from(#F39823), to(#F37221)); font-size:14px; font-weight:bold; color:#FFF; border:1px solid #FFF; border-radius:5px; -webkit-box-shadow:0 3px 3px 0 #9F9F9F; -webkit-background-clip:padding-box; } +#checkoutSteps li .step .buttons-set button { + background:-webkit-gradient(linear, 0 0, 0 100%, from(#f39823), to(#f37221)); + background:-o-linear-gradient(top, #f39823 0%, #f37221 100%); + float:right; + padding:5px 10px; + font-size:14px; + font-weight:bold; + color:#fff; + border:1px solid #FFF; + border-radius:5px; + -webkit-box-shadow:0 3px 3px 0 #9F9F9F; + box-shadow:0 3px 3px 0 #9F9F9F; + -webkit-background-clip:padding-box; + background-clip:padding-box; + text-shadow:0 1px 2px rgba(0, 0, 0, 0.25); +} #checkoutSteps .validation-advice { color:#F4641E; } #checkoutSteps .back-link small { display:none; } #co-payment-form dd, #checkoutSteps li .step dd { margin:0; padding:0; } #opc-billing .step .required em { display:none; } -#checkout-review-table { width:100%; font-size:12px; } +#checkout-review-table { border-spacing:0; width:100%; font-size:12px; } #checkout-review-table th { background:#909090; color:#FFF; } #checkout-review-table th:first-child { text-align:left; } #checkout-review-table tr.odd td { background:#EEE; } #checkout-review-table th, #checkout-review-table td { padding:2px 5px; } +#checkout-review-table td { vertical-align:top; } +#checkout-review-table td.last { text-align:right; } +#checkout-review-table .label { white-space:nowrap; } +#checkout-review-table .cart-price { font-weight:bold; } #checkout-review-submit .buttons-set:after { content:"."; display:block; @@ -2732,6 +3584,7 @@ section .category-title { display:none; } #checkout-review-submit .buttons-set .f-left a { display:block; background:-webkit-gradient(linear, 0 0, 0 100%, from(#333), to(#111)); + background:-o-linear-gradient(top, #333 0%, #111 100%); border:none; color:white; font-size:13px; @@ -2741,6 +3594,7 @@ section .category-title { display:none; } padding:8px 10px; text-shadow:0 1px 0 black; -webkit-border-radius:5px; + border-radius:5px; } #checkout-step-login .checkout-login { border-bottom:1px solid #ccc; @@ -2773,10 +3627,69 @@ section .category-title { display:none; } vertical-align:middle; } +.add-gift-message, +.gift-message-form { + margin:10px 0 20px; +} + +.add-gift-message h3, +.gift-message-form h4 { + margin:0 0 5px; +} + +.gift-message-form p { + margin:0 0 8px; +} + +.gift-message-form .product-image { + margin:0 0 2px; +} + +.gift-message-form .product-image img { + -webkit-box-shadow:0 1px 2px #999; + box-shadow:0 1px 2px #999; +} + +.gift-message-form ol { + list-style:none; + margin:0 0 10px; + padding:0; +} + +.gift-message-form .gift-item:after { + content:""; + display:table; +} +.gift-message-form .gift-item:after { + clear:both; +} + +.gift-message-form .number { + display:none; +} + +.gift-message-form .form-list { + clear:both; + border-bottom:1px solid #ccc; + margin:0 0 10px; + padding:10px 0; +} + +.gift-message-form .product-img-box { + float:left; + margin-right:10px; +} + +.extra-options-container { clear:both; } + /* Sitemap -----------------------------*/ .sitemap { margin:-1px -10px -10px; } -.sitemap li { background:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#eee)); border-top:1px solid #ccc; } +.sitemap li { + background:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#eee)); + background:-o-linear-gradient(top, #fff 0%, #eee 100%); + border-top:1px solid #ccc; +} .sitemap li a { background:url(../images/i_arrow_small.png) no-repeat 96% 50%; background-size:8px 12px; display:block; padding:10px; font-size:16px; } .catalog-seo-sitemap-category .page-title { display:none; } .catalog-seo-sitemap-category .page-sitemap .pager, @@ -2795,18 +3708,43 @@ section .category-title { display:none; } .catalogsearch-term-popular .tags-list li { display:inline-block; padding:0 10px; line-height:2; font-size:16px; } -/* Contact us +/* Contact us, Share Wishlist -----------------------------*/ .contacts-index-index .page-title { display:none; } .contacts-index-index .fieldset h2 { display:none; } .contacts-index-index .messages { margin:0 -10px 0; } -.contacts-index-index form { display:block; border:1px solid #D1D1D1; margin:2px -7px -7px; padding:9px; border-radius:2px 2px 5px 5px; background:-webkit-gradient(linear, 0 0, 0 100%,color-stop(0, #FCFDFD), color-stop(0.8, #EEEEEE), color-stop(1, #E8E9E9)); } -.contacts-index-index label { color:#5E5E5E; font-weight:bold; } -.contacts-index-index label em { margin:0 3px 0 0; color:#F4641E; } +.contacts-index-index section form, +.wishlist-index-share section form { + background:-webkit-gradient(linear, 0 0, 0 100%,color-stop(0, #FCFDFD), color-stop(0.8, #eee), color-stop(1, #e8e9e9)); + background:-o-linear-gradient(top, #fcfdfd 0%, #eee 80%, #e8e9e9 100%); + display:block; + border:1px solid #d1d1d1; + margin:2px -7px -7px; + padding:9px; + border-radius:2px 2px 5px 5px; +} +.wishlist-index-share section form { margin:2px 3px -7px; } +.contacts-index-index label, +.wishlist-index-share label { color:#5E5E5E; font-weight:bold; } +.contacts-index-index label em, +.wishlist-index-share label em { display:inline; margin:0 3px 0 0; color:#F4641E; } .contacts-index-index textarea { width:99%; height:200px; } .contacts-index-index .fields .field { margin:0 0 8px 0; } -.contacts-index-index .buttons-set .required { float:right; font-size:12px; font-weight:bold; color:#F4641E; } -.contacts-index-index .buttons-set button { padding:5px 10px; background:-webkit-gradient(linear, 0 0, 0 100%, from(#F39823), to(#F37221)); font-size:12px; color:#FFF; border:1px solid #FFF; border-radius:5px; -webkit-box-shadow:0 3px 3px 0 #9F9F9F; } +.contacts-index-index .buttons-set .required, +.wishlist-index-share .buttons-set .required { float:right; font-size:12px; font-weight:bold; color:#F4641E; display:block; } +.contacts-index-index .buttons-set button, +.wishlist-index-share .buttons-set button { + background:-webkit-gradient(linear, 0 0, 0 100%, from(#f39823), to(#f37221)); + background:-o-linear-gradient(top, #f39823 0%, #f37221 100%); + padding:5px 10px; + font-size:12px; + color:#fff; + border:1px solid #fff; + border-radius:5px; + -webkit-box-shadow:0 3px 3px 0 #9f9f9f; + box-shadow:0 3px 3px 0 #9f9f9f; + text-shadow:0 1px 2px rgba(0, 0, 0, 0.25); +} /* Advanced search @@ -2820,14 +3758,15 @@ section .category-title { display:none; } padding:5px 10px; border:1px solid #CECECE; border-radius:2px; - background:-webkit-gradient(linear, 0 0, 0 100%, from(#FEFEFE), to(#EEE)); + background:-webkit-gradient(linear, 0 0, 0 100%, from(#fefefe), to(#eee)); + background:-o-linear-gradient(top, #fefefe 0%, #eee 100%); font-size:16px; } -#advanced-search-list li:nth-last-child(-n+3) { +#advanced-search-list > li:nth-last-child(-n+3) { display:inline-block; width:32%; } -#advanced-search-list li:nth-last-child(-n+3) select { +#advanced-search-list > li:nth-last-child(-n+3) select { width:100%; } diff --git a/skin/frontend/default/iphone/images/bg_button.png b/skin/frontend/default/iphone/images/bg_button.png new file mode 100644 index 0000000000..fa1b0ceeb3 Binary files /dev/null and b/skin/frontend/default/iphone/images/bg_button.png differ diff --git a/skin/frontend/default/iphone/js/iphone.js b/skin/frontend/default/iphone/js/iphone.js index 48418ecda3..fd15c7cc48 100644 --- a/skin/frontend/default/iphone/js/iphone.js +++ b/skin/frontend/default/iphone/js/iphone.js @@ -26,6 +26,52 @@ // Homepage categories and subcategories slider document.observe("dom:loaded", function() { + function handler(position) { + var lat = position.coords.latitude, + lng = position.coords.longitude; + + //alert(latitude + ' ' + longitude); + + var geocoder = new google.maps.Geocoder(); + + function codeLatLng() { + var latlng = new google.maps.LatLng(lat, lng); + geocoder.geocode({'latLng': latlng}, function(results, status) { + if (status == google.maps.GeocoderStatus.OK) { + if (results[0]) { + alert(results[0].formatted_address); + } + } else { + alert("Geocoder failed due to: " + status); + } + }); + } + + //codeLatLng(); + + } + + if ( navigator.geolocation ) { + //navigator.geolocation.getCurrentPosition(handler); + } + + if ( $('giftregistry-table') ) { + $('giftregistry-table').wrap('div', { 'class' : 'giftregistry-table-wrap' }); + } + + if ( $('my-reviews-table') ) { + $('my-reviews-table').wrap('div', { 'class' : 'my-reviews-table-wrap' }); + } + + var transformPref = Modernizr.prefixed('transform'); + + function supportsTouchCallout () { + var div = document.createElement('div'), + supports = div.style['webkitTouchCallout'] !== undefined || div.style['touchCallout'] !== undefined; + + return supports + } + $$('input[name=qty], input[name*=super_group], input[name*=qty]').each(function (el) { var defaultValue = el.value; el.observe('focus', function () { @@ -52,12 +98,78 @@ document.observe("dom:loaded", function() { }); } + function is_touch_device() { + try { + document.createEvent("TouchEvent"); + return true; + } catch (e) { + return false; + } + } + + var touch = is_touch_device(); + + $$('select[multiple]').each(function (select) { + var select_options = new Element('ol', {'class': 'select-multiple-options'}).wrap('div', { 'class' : 'select-multiple-options-wrap' }), + selected; + + select.wrap('div', { 'class': 'select-multiple-wrap' }); + select.select('option').each(function(option) { + select_options.down().insert({ bottom : new Element('li', { 'class' : 'select-option', 'data-option-value' : option.value }).update(option.text) }); + }); + + select_options.insert({ top : new Element('div', { 'class' : 'select-heading' }).update('Choose options...').insert({ top : new Element('span', { 'class' : 'select-close' }).update('x') }) }); + + var closeSelect = function() { + select_options.setStyle({ 'visibility' : 'hidden' }); + selected = []; + select.select('option').each(function (option) { + if (option.selected) { + selected.push(option.text) + } + }); + + if (selected.size() > 0) { + select.previous().update('' + selected.join(', ')).addClassName('filled'); + select.previous().select('span')[0].update(selected.size()); + } else { + select.previous().update('Choose options...').removeClassName('filled'); + } + document.stopObserving('click', closeSelect); + } + + select_options.select('.select-close')[0].observe('click', closeSelect ); + + select_options.on('click', '.select-option', function(e, elem) { + var option = select.select('option[value=' + elem.readAttribute('data-option-value') + ']')[0]; + elem.toggleClassName('active'); + if (option.selected) { + option.selected = false + } else { + option.selected = true; + } + if (typeof bundle !== 'undefined') bundle.changeSelection(select); + }); + + select.insert({ before : select_options }); + select.insert({ + before: new Element('div', {'class': 'select-multiple'}).update("Choose options...").observe('click', function(e) { + select.previous('.select-multiple-options-wrap').setStyle({ 'visibility' : 'visible' }).observe('click', function(e) { + e.stopPropagation(); + }); + setTimeout(function() { + document.observe('click', closeSelect) + }, 1); + }) + }); + select.setStyle({ 'visibility' : 'hidden', 'position' : 'absolute' }); + }); + var supportsOrientationChange = "onorientationchange" in window, orientationEvent = supportsOrientationChange ? "orientationchange" : "resize"; Event.observe(window, orientationEvent, function() { - var orientation, - page; + var orientation, page, transformValue = {}; switch(window.orientation){ case 0: @@ -84,7 +196,12 @@ document.observe("dom:loaded", function() { sliderPosition = (sliderPosition + viewportWidth*page) - document.body.offsetWidth*page; viewportWidth = document.body.offsetWidth; - $("nav-container").setStyle({"-webkit-transform" : "translate3d(" + sliderPosition + "px, 0, 0)"}); + if ( Modernizr.csstransforms3d ) { + transformValue[transformPref] = "translate3d(" + sliderPosition + "px, 0, 0)"; + } else if ( Modernizr.csstransforms ) { + transformValue[transformPref] = "translate(" + sliderPosition + "px, 0)"; + } + $("nav-container").setStyle(transformValue); if ( upSellCarousel ) { if (orientation === 'landscape') { @@ -101,12 +218,21 @@ document.observe("dom:loaded", function() { var groupItems = Class.create({ initialize: function (handle, removeHandle, photos, form) { + var that = this; this.handle = handle; this.removeHandle = removeHandle; this.photos = this.handle.select(photos); if ( this.photos.size() < 2 ) { return } + if ( !('ongesturestart' in window) && this.photos.size() > 1 ) { + $$('.remove-all-button').each(function(btn) { + btn.setStyle({ 'display' : 'block' }).observe('click', function(e) { + e.preventDefault(); + that.removeAll(); + }); + }); + } this.form = form; this.removeHandle.observe('click', this.removeAll.bind(this)); @@ -157,7 +283,7 @@ document.observe("dom:loaded", function() { } if ( $$('.wishlist-wrap')[0] ) { - var wishlistGroup = new groupItems($$('.wishlist-wrap')[0], $('remove-all-wishlist'), 'li > a', $('wishlist-view-form')); + var wishlistGroup = new groupItems($$('.wishlist-wrap')[0], $('remove-all-wishlist'), '#wishlist-list li > a', $('wishlist-view-form')); } if ( $$('#remember-me-box a')[0] ) { @@ -185,8 +311,11 @@ document.observe("dom:loaded", function() { }); } + //alert(Modernizr.prefixed('transform')); + // Home Page Slider + //alert(transformPref); var sliderPosition = 0, viewportWidth = document.body.offsetWidth, last, @@ -201,6 +330,8 @@ document.observe("dom:loaded", function() { sliderLink.observe('click', function(e) { + var transformValue = {} + homeLink.hasClassName('disabled') ? homeLink.removeClassName('disabled') : ''; if (last) { @@ -228,7 +359,12 @@ document.observe("dom:loaded", function() { if (diff && diff < 200) { return } - $("nav-container").setStyle({"-webkit-transform" : "translate3d(" + (document.body.offsetWidth + sliderPosition) + "px, 0, 0)"}); + if ( Modernizr.csstransforms3d ) { + transformValue[transformPref] = "translate3d(" + (document.body.offsetWidth + sliderPosition) + "px, 0, 0)"; + } else if ( Modernizr.csstransforms ) { + transformValue[transformPref] = "translate(" + (document.body.offsetWidth + sliderPosition) + "px, 0)"; + } + $("nav-container").setStyle(transformValue); sliderPosition = sliderPosition + document.body.offsetWidth; setTimeout(function() { $$("#nav-container > ul:last-child")[0].remove(); $("nav-container").setStyle({'height' : 'auto'}) }, 250) }); @@ -237,7 +373,18 @@ document.observe("dom:loaded", function() { $("nav-container").insert(this.clonedSubmenuList.setStyle({'width' : document.body.offsetWidth + 'px'})); $('nav-container').setStyle({'height' : this.clonedSubmenuList.getHeight() + 'px'}); - $("nav-container").setStyle({"-webkit-transform" : "translate3d(" + (sliderPosition - document.body.offsetWidth) + "px, 0, 0)"}); + + if ( Modernizr.csstransforms3d ) { + + transformValue[transformPref] = "translate3d(" + (sliderPosition - document.body.offsetWidth) + "px, 0, 0)"; + + } else if ( Modernizr.csstransforms ) { + + transformValue[transformPref] = "translate(" + (sliderPosition - document.body.offsetWidth) + "px, 0)"; + + } + + $("nav-container").setStyle(transformValue); sliderPosition = sliderPosition - document.body.offsetWidth; e.preventDefault(); @@ -245,7 +392,19 @@ document.observe("dom:loaded", function() { }; }); + function getSupportedProp(proparray){ + var root = document.documentElement; + for ( var i = 0; i < proparray.length; i++ ) { + if ( typeof root.style[proparray[i]] === "string") { + return proparray[i]; + } + } + } + function NoClickDelay(el) { + if ( getSupportedProp(['OTransform']) ) { + return + } this.element = typeof el == 'object' ? el : document.getElementById(el); if( window.Touch ) this.element.addEventListener('touchstart', this, false); } @@ -296,24 +455,51 @@ document.observe("dom:loaded", function() { new NoClickDelay(document.getElementById('nav')); } + var supports3d = window.WebKitCSSMatrix ? true : false; + + //alert(Modernizr.csstransforms3d); + //alert(Modernizr.csstransforms); + //iPhone header menu $$('dt.dropdown a').each(function (elem) { elem.observe('click', function(e) { - var parent = elem.up(); + var parent = elem.up(), transformValue = {}; if (parent.hasClassName('active')) { parent.removeClassName('active'); $$('#menu dd').each(function(elem) { - elem.setStyle({'webkitTransform' : 'translate3d(0, -100%, -1px)'}); + + if ( Modernizr.csstransforms3d ) { + transformValue[transformPref] = 'translate3d(0, -100%, -1px)'; + } else if ( Modernizr.csstransforms ) { + transformValue[transformPref] = 'translate3d(0, -100%)'; + transformValue['visibility'] = 'hidden'; + } + + elem.setStyle(transformValue); }) - } - else { + } else { $$('#menu dt').each(function (elem){ elem.removeClassName('active'); - elem.next('dd').setStyle({'webkitTransform' : 'translate3d(0, -100%, -1px)'}); + + if ( Modernizr.csstransforms3d ) { + transformValue[transformPref] = 'translate3d(0, -100%, -1px)'; + } else if ( Modernizr.csstransforms ) { + transformValue[transformPref] = 'translate3d(0, -100%)'; + transformValue['visibility'] = 'hidden'; + } + + elem.next('dd').setStyle(transformValue); }); parent.addClassName('active'); - parent.next().setStyle({'webkitTransform' : 'translate3d(0, 0, -1px)', 'visibility' : 'visible'}); + if ( Modernizr.csstransforms3d ) { + transformValue[transformPref] = 'translate3d(0, 0%, -1px)'; + transformValue['visibility'] = 'visible'; + } else if ( Modernizr.csstransforms ) { + transformValue[transformPref] = 'translate3d(0, 0%)'; + transformValue['visibility'] = 'visible'; + } + parent.next().setStyle(transformValue); if ( cartDrag ) { cartDrag.cartHide(); } @@ -346,7 +532,7 @@ document.observe("dom:loaded", function() { elem.innerHTML = elem.innerHTML.replace(/\((\d+)\)/, '$1') }) - $$('.top-link-cart')[0].up().remove(); + //$$('.top-link-cart')[0].up().remove(); var sum = 0; $$('.menu-box .badge').each(function (badge) { @@ -398,6 +584,7 @@ document.observe("dom:loaded", function() { this.carousel = carousel; this.items = itemsContainer.addClassName('carousel-items'); + this.itemsWrap = this.items.wrap('div', {'class' : 'carousel-items-wrap'}); this.itemsLength = this.items.childElements().size(); this.counter = this.carousel.insert(new Element('div', {'class' : 'counter'})).select('.counter')[0]; this.controls = carousel.select('.controls')[0]; @@ -410,9 +597,9 @@ document.observe("dom:loaded", function() { this.nextButton.observe('click', this.moveRight.bind(this)); this.prevButton.observe('click', this.moveLeft.bind(this)); - this.items.observe('touchstart', this.touchStart.bind(this)); - this.items.observe('touchmove', this.touchMove.bind(this)); - this.items.observe('touchend', this.touchEnd.bind(this)); + this.itemsWrap.observe('touchstart', this.touchStart.bind(this)); + this.itemsWrap.observe('touchmove', this.touchMove.bind(this)); + this.itemsWrap.observe('touchend', this.touchEnd.bind(this)); }, init: function () { this.itemPos = 0; @@ -426,11 +613,15 @@ document.observe("dom:loaded", function() { return this; }, resize: function(visibleElements) { + var transformValue = {}; this.options.visibleElements = visibleElements; this.counter.childElements().invoke('remove'); - this.items.setStyle({ - '-webkit-transform': 'translateX(' + 0 + '%)' - }); + if ( Modernizr.csstransforms3d ) { + transformValue[transformPref] = 'translateX(' + 0 + '%)'; + } else if ( Modernizr.csstransforms ) { + transformValue[transformPref] = 'translate(' + 0 + '%, 0)'; + } + this.items.setStyle(transformValue); this.prevButton.addClassName('disabled'); this.nextButton.removeClassName('disabled'); this.init(); @@ -460,11 +651,16 @@ document.observe("dom:loaded", function() { }, moveRight: function (e) { if(Math.abs(this.itemPos) < this.lastItemPos) { + var transformValue = {}; this.itemPos -= 100/this.options.visibleElements * this.options.visibleElements; - this.items.setStyle({ - 'position': 'relative', - '-webkit-transform': 'translateX(' + this.itemPos + '%)' - }); + if ( Modernizr.csstransforms3d ) { + transformValue[transformPref] = 'translateX(' + this.itemPos + '%)'; + transformValue['position'] = 'relative'; + } else if ( Modernizr.csstransforms ) { + transformValue[transformPref] = 'translate(' + this.itemPos + '%, 0)'; + transformValue['position'] = 'relative'; + } + this.items.setStyle(transformValue); if (Math.abs(this.itemPos) >= this.lastItemPos) { this.nextButton.addClassName('disabled'); } @@ -477,11 +673,16 @@ document.observe("dom:loaded", function() { }, moveLeft: function (e) { if (this.itemPos !== 0) { + var transformValue = {}; this.itemPos += 100/this.options.visibleElements * this.options.visibleElements; - this.items.setStyle({ - 'position': 'relative', - '-webkit-transform': 'translateX(' + this.itemPos + '%)' - }); + if ( Modernizr.csstransforms3d ) { + transformValue[transformPref] = 'translateX(' + this.itemPos + '%)'; + transformValue['position'] = 'relative'; + } else if ( Modernizr.csstransforms ) { + transformValue[transformPref] = 'translate(' + this.itemPos + '%, 0)'; + transformValue['position'] = 'relative'; + } + this.items.setStyle(transformValue); if(this.itemPos === 0) { this.prevButton.addClassName('disabled'); @@ -637,18 +838,27 @@ document.observe("dom:loaded", function() { this.startMin = this.headerHeight - this.minHeight; this.startMax = this.headerHeight - this.maxHeight; this.visible = false; - this.empty = this.cartHolder.hasClassName('cart-empty') ? true : false; + this.empty = this.cartHolder.hasClassName('cart-empty'); this.range = 0; this.originalCoord = { x: 0, y: 0 }; this.finalCoord = { x: 0, y: 0 }; - this.cart.observe('touchstart', this.touchStart.bind(this)); - this.cart.observe('touchmove', this.touchMove.bind(this)); - this.cart.observe('touchend', this.touchEnd.bind(this)); + if ( Modernizr.touch ) { + this.cart.observe('touchstart', this.touchStart.bind(this)); + this.cart.observe('touchmove', this.touchMove.bind(this)); + this.cart.observe('touchend', this.touchEnd.bind(this)); + } else { + this.cart.observe('click', this.toggleView.bind(this)); + } + this.cartHolder.observe('webkitTransitionEnd', this.transitionEnd.bind(this)); - this.cartHolder.setStyle({'webkitTransform':'translate3d(0,' + this.startMax + 'px, 0)'}); + if ( supports3d ) { + this.cartHolder.setStyle({'webkitTransform':'translate3d(0,' + this.startMax + 'px, 0)'}); + } else { + this.cartHolder.setStyle({'OTransform':'translate(0,' + this.startMax + 'px)'}); + } setTimeout(function () { this.cartHolder.setStyle({'visibility': 'visible'}); }.bind(this), 100); @@ -657,11 +867,19 @@ document.observe("dom:loaded", function() { touchStart : function (e) { e.preventDefault(); + var transformValue = {}; + $$('#menu dt.active').each(function(elem) { elem.removeClassName('active'); }); $$('#menu dd').each(function(elem) { - elem.setStyle({'webkitTransform' : 'translate3d(0, -100%, -1px)'}); + if ( Modernizr.csstransforms3d ) { + transformValue[transformPref] = 'translate3d(0, -100%, -1px)'; + } else if ( Modernizr.csstransforms ) { + transformValue[transformPref] = 'translate3d(0, -100%)'; + transformValue['visibility'] = 'hidden'; + } + elem.setStyle(transformValue); }); this.originalCoord.x = event.targetTouches[0].pageX; @@ -674,6 +892,8 @@ document.observe("dom:loaded", function() { return } + var transformValue = {}; + if ( Math.abs(this.finalCoord.y - this.originalCoord.y) > 1 && this.finalCoord.y - this.originalCoord.y < 3 ) { this.cartHolder.removeClassName('animate').addClassName('cart-short'); } @@ -691,10 +911,17 @@ document.observe("dom:loaded", function() { this.visible = true; } - this.cartHolder.setStyle({'webkitTransform':'translate3d(0,' + this.range + 'px, 0)'}) + if ( Modernizr.csstransforms3d ) { + transformValue[transformPref] = 'translate3d(0,' + this.range + 'px, 0)'; + } else if ( Modernizr.csstransforms ) { + transformValue[transformPref] = 'translate(0,' + this.range + 'px)'; + } + + this.cartHolder.setStyle(transformValue); }, touchEnd : function (e) { e.preventDefault(); + var transformValue = {}; if ( Math.abs(this.originalCoord.y - this.finalCoord.y ? this.finalCoord.y : 0) < 10 && Math.abs(this.originalCoord.x - this.finalCoord.x ? this.finalCoord.x : 0) < 10 ) { if ( this.visible ) { this.cartHide(); @@ -702,28 +929,70 @@ document.observe("dom:loaded", function() { this.cartShow(); } } - if ( this.range + this.minHeight < (this.minHeight) && this.cartHolder.hasClassName('cart-short') && !this.empty ) { - this.cartHolder.addClassName('animate').setStyle({'webkitTransform':'translate3d(0,' + this.startMin + 'px, 0)'}); + if ( this.minHeight - this.headerHeight + this.range < (this.minHeight) && this.cartHolder.hasClassName('cart-short') && !this.empty ) { + if ( Modernizr.csstransforms3d ) { + transformValue[transformPref] = 'translate3d(0,' + this.startMin + 'px, 0)'; + } else if ( Modernizr.csstransforms ) { + transformValue[transformPref] = 'translate(0,' + this.startMin + 'px)'; + } + this.cartHolder.addClassName('animate').setStyle(transformValue); } }, cartHide : function () { + var transformValue = {}; this.cart.removeClassName('active'); - this.cartHolder.setStyle({'webkitTransform':'translate3d(0,' + this.startMax + 'px, 0)', 'top': '0px'}); + if ( Modernizr.csstransforms3d ) { + transformValue[transformPref] = 'translate3d(0,' + this.startMax + 'px, 0)'; + transformValue['top'] = '0px'; + } else if ( Modernizr.csstransforms ) { + transformValue[transformPref] = 'translate(0,' + this.startMax + 'px)'; + transformValue['top'] = '0px'; + } + this.cartHolder.setStyle(transformValue); this.visible = false; }, cartShow : function () { + var transformValue = {}; this.visible = true; this.cart.addClassName('active'); - this.cartHolder.removeClassName('cart-short').setStyle({'webkitTransform':'translate3d(0,' + this.startMax + 'px, 0)'}); - this.cartHolder.addClassName('animate').setStyle({'webkitTransform':'translate3d(0,' + this.headerHeight + 'px, 0)'}); + if ( Modernizr.csstransforms3d ) { + transformValue[transformPref] = 'translate3d(0,' + this.startMax + 'px, 0)'; + this.cartHolder.removeClassName('cart-short').setStyle(transformValue); + + transformValue[transformPref] = 'translate3d(0,' + this.headerHeight + 'px, 0)'; + this.cartHolder.addClassName('animate').setStyle(transformValue); + + } else if ( Modernizr.csstransforms ) { + transformValue[transformPref] = 'translate(0,' + this.startMax + 'px)'; + this.cartHolder.removeClassName('cart-short').setStyle(transformValue); + + transformValue[transformPref] = 'translate(0,' + this.headerHeight + 'px)'; + this.cartHolder.addClassName('animate').setStyle(transformValue); + } }, transitionEnd : function (e) { + var transformValue = {}; if ( this.visible ) { - this.cartHolder.removeClassName('animate').setStyle({'webkitTransform': 'translate3d(0, ' + (this.headerHeight-1) + 'px, 0)', 'top': '1px'}); + if ( Modernizr.csstransforms3d ) { + transformValue[transformPref] = 'translate3d(0, ' + (this.headerHeight-1) + 'px, 0)'; + transformValue['top'] = '1px'; + this.cartHolder.removeClassName('animate').setStyle(transformValue); + } else if ( Modernizr.csstransforms ) { + transformValue[transformPref] = 'translate(0, ' + (this.headerHeight-1) + 'px)'; + transformValue['top'] = '1px'; + this.cartHolder.removeClassName('animate').setStyle(transformValue); + } setTimeout(function () { this.cartHolder.addClassName('animate') }.bind(this), 100); } + }, + toggleView : function (e) { + if ( this.visible ) { + this.cartHide(); + } else { + this.cartShow(); + } } }); @@ -806,7 +1075,7 @@ document.observe("dom:loaded", function() { this.items = gallery.select('img'); this.itemsLength = this.items.size(); this.pos = 0; - this.step = 100/this.itemsLength; + this.step = (100/this.itemsLength).toFixed(2) * 1; this.lastPos = this.step * this.itemsLength; this.originalCoord = { x: 0, y: 0 }; this.finalCoord = { x: 0, y: 0 }; @@ -840,7 +1109,6 @@ document.observe("dom:loaded", function() { } }, moveRight: function (elem) { - //alert('move right'); if (this.pos !== this.lastPos - this.step) { @@ -852,10 +1120,17 @@ document.observe("dom:loaded", function() { this.scale = 1.0; this.pos += this.step; - this.wrap.setStyle({ - 'webkitTransition' : '300ms linear', - 'webkitTransform' : 'translate3d(' + this.pos*-1 + '%, 0, 0)' - }); + + var transformValue = {}; + if ( Modernizr.csstransforms3d ) { + this.wrap.setStyle({ + 'webkitTransition' : '300ms linear', + 'webkitTransform' : 'translate3d(' + this.pos*-1 + '%, 0, 0)' + }); + } else if ( Modernizr.csstransforms ) { + transformValue[transformPref] = 'translate(' + this.pos*-1 + '%, 0)'; + this.wrap.setStyle(transformValue); + } this.counter.select('.active')[0].removeClassName('active').next().addClassName('active'); @@ -873,10 +1148,18 @@ document.observe("dom:loaded", function() { this.scale = 1.0; this.pos -= this.step; - this.wrap.setStyle({ - 'webkitTransition' : '300ms linear', - 'webkitTransform' : 'translate3d(' + this.pos*-1 + '%, 0, 0)' - }); + + var transformValue = {}; + if ( Modernizr.csstransforms3d ) { + this.wrap.setStyle({ + 'webkitTransition' : '300ms linear', + 'webkitTransform' : 'translate3d(' + this.pos*-1 + '%, 0, 0)' + }); + } else if ( Modernizr.csstransforms ) { + transformValue[transformPref] = 'translate(' + this.pos*-1 + '%, 0)'; + this.wrap.setStyle(transformValue); + } + this.counter.select('.active')[0].removeClassName('active').previous().addClassName('active'); } @@ -985,10 +1268,10 @@ document.observe("dom:loaded", function() { changeX = this.originalCoord.x - this.finalCoord.x, changeY = this.originalCoord.y - this.finalCoord.y; - if(changeX > this.options.threshold.x && Math.abs(changeY) < 30 && timeDelta < 200) { + if(changeX > this.options.threshold.x && Math.abs(changeY) < 40 && timeDelta < 300) { this.moveRight($this); } - if(changeX < this.options.threshold.x * -1 && Math.abs(changeY) < 30 && timeDelta < 200) { + if(changeX < this.options.threshold.x * -1 && Math.abs(changeY) < 40 && timeDelta < 300) { this.moveLeft($this); } @@ -1041,4 +1324,54 @@ document.observe("dom:loaded", function() { }, }); + if ( $$('.c-list') && supportsTouchCallout() ) { + + $$('.c-list .product-image').each(function(n) { + var parent = n.up('a'), + clone = n.up().clone(true).addClassName('cloned'); + parent.insert(clone.wrap('div', {'class' : 'cloned-wrap'})); + + new webkit_draggable(clone.up(), { handle : clone.select('.product-image')[0], revert : true, scroll : true, onStart : function(r, e) { + r.setStyle({'opacity':'100'}).down('.wrap').addClassName('drop-start'); + }, + onEnd : function(r, e) { + r.setStyle({'opacity':'0'}).down('.wrap').removeClassName('drop-start'); + } + }); + }); + webkit_drop.add($('menu'), + { + onDrop : function(elem, e) { e.preventDefault(); setLocation(elem.up('li').down('.actions li:last-child a').readAttribute('href')); elem.remove(); }, + onOver : function(elem, e) { e.preventDefault(); elem.down().addClassName('to-cart-animate'); }, + onOut : function(elem) { elem.down().removeClassName('to-cart-animate'); } + }); + + } + + if ( $('c-grid') && supportsTouchCallout() ) { + + $$('.c-grid .cloned-wrap').each(function(n) { + new webkit_draggable(n, { handle : n.up('.cell').select('.product-image')[1], revert : true, scroll : true, onStart : function(r, e) { + r.setStyle({'opacity':'100', 'visibility':'visible'}).down('.wrap').addClassName('drop-start'); + }, + onEnd : function(r, e) { + r.setStyle({'opacity':'0', 'visibility':'hidden'}).down('.wrap').removeClassName('drop-start'); + } + }); + }); + webkit_drop.add($('menu'), + { + onDrop : function(elem, e) { e.preventDefault(); setLocation(elem.up('.cell').down('.add-to-cart').readAttribute('href')); elem.remove(); }, + onOver : function(elem, e) { e.preventDefault(); elem.down('.wrap').addClassName('to-cart-animate'); }, + onOut : function(elem) { elem.down('.wrap').removeClassName('to-cart-animate'); } + }); + + } + + if ( $('customer-reviews') ) { + $('customer-reviews').select('dt > a').each(function (a) { + a.replace('

    ' + a.innerHTML + '

    '); + }); + } + }); diff --git a/skin/frontend/default/iphone/js/modernizr.js b/skin/frontend/default/iphone/js/modernizr.js new file mode 100644 index 0000000000..3d88b168bd --- /dev/null +++ b/skin/frontend/default/iphone/js/modernizr.js @@ -0,0 +1,28 @@ +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Academic Free License (AFL 3.0) + * that is bundled with this package in the file LICENSE_AFL.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/afl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category design + * @package default_iphone + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) + */ +/* Modernizr 2.0.6 (Custom Build) | MIT & BSD + * Build: http://www.modernizr.com/download/#-csstransforms-csstransforms3d-cssclasses-prefixed-teststyles-testprop-testallprops-prefixes-domprefixes + */ +;window.Modernizr=function(a,b,c){function C(a,b){var c=a.charAt(0).toUpperCase()+a.substr(1),d=(a+" "+o.join(c+" ")+c).split(" ");return B(d,b)}function B(a,b){for(var d in a)if(k[a[d]]!==c)return b=="pfx"?a[d]:!0;return!1}function A(a,b){return!!~(""+a).indexOf(b)}function z(a,b){return typeof a===b}function y(a,b){return x(n.join(a+";")+(b||""))}function x(a){k.cssText=a}var d="2.0.6",e={},f=!0,g=b.documentElement,h=b.head||b.getElementsByTagName("head")[0],i="modernizr",j=b.createElement(i),k=j.style,l,m=Object.prototype.toString,n=" -webkit- -moz- -o- -ms- -khtml- ".split(" "),o="Webkit Moz O ms Khtml".split(" "),p={},q={},r={},s=[],t=function(a,c,d,e){var f,h,j,k=b.createElement("div");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:i+(d+1),k.appendChild(j);f=["­",""].join(""),k.id=i,k.innerHTML+=f,g.appendChild(k),h=c(k,a),k.parentNode.removeChild(k);return!!h},u,v={}.hasOwnProperty,w;!z(v,c)&&!z(v.call,c)?w=function(a,b){return v.call(a,b)}:w=function(a,b){return b in a&&z(a.constructor.prototype[b],c)};var D=function(a,c){var d=a.join(""),f=c.length;t(d,function(a,c){var d=b.styleSheets[b.styleSheets.length-1],g=d.cssRules&&d.cssRules[0]?d.cssRules[0].cssText:d.cssText||"",h=a.childNodes,i={};while(f--)i[h[f].id]=h[f];e.csstransforms3d=i.csstransforms3d.offsetLeft===9},f,c)}([,["@media (",n.join("transform-3d),("),i,")","{#csstransforms3d{left:9px;position:absolute}}"].join("")],[,"csstransforms3d"]);p.csstransforms=function(){return!!B(["transformProperty","WebkitTransform","MozTransform","OTransform","msTransform"])},p.csstransforms3d=function(){var a=!!B(["perspectiveProperty","WebkitPerspective","MozPerspective","OPerspective","msPerspective"]);a&&"webkitPerspective"in g.style&&(a=e.csstransforms3d);return a};for(var E in p)w(p,E)&&(u=E.toLowerCase(),e[u]=p[E](),s.push((e[u]?"":"no-")+u));x(""),j=l=null,e._version=d,e._prefixes=n,e._domPrefixes=o,e.testProp=function(a){return B([a])},e.testAllProps=C,e.testStyles=t,e.prefixed=function(a){return C(a,"pfx")},g.className=g.className.replace(/\bno-js\b/,"")+(f?" js "+s.join(" "):"");return e}(this,this.document); diff --git a/skin/frontend/default/modern/css/styles.css b/skin/frontend/default/modern/css/styles.css index 327bc4a8a8..e36cb288a8 100644 --- a/skin/frontend/default/modern/css/styles.css +++ b/skin/frontend/default/modern/css/styles.css @@ -885,9 +885,10 @@ tr.summary-details-excluded { font-style:italic; } .price-box-bundle { padding:0 0 10px 0; } .price-box-bundle .price-box { margin:0 !important; padding:0 !important; } .price-box-bundle .price { color:#222; } -f/********** Product Prices > */ +/********** Product Prices > */ /* Tier Prices */ +.product-pricing, .tier-prices { margin:10px 0; padding:10px; background:#f4f9ea; border:1px solid #ddd; } .tier-prices .benefit { font-style:italic; font-weight:bold; } .tier-prices .price { font-weight:bold;; } @@ -990,6 +991,7 @@ f/********** Product Prices > */ .product-options p.required { position:absolute; right:15px; top:10px; } .product-options-bottom { background-color:#f6f6f6; padding:15px 20px; border:1px solid #e4e4e4; border-top:0; } +.product-options-bottom .product-pricing, .product-options-bottom .tier-prices { margin:0; padding:0 0 10px; border:0; background:0; } .product-options-bottom .price-box { float:left; margin:0; } .product-options-bottom .add-to-links { clear:both; padding:5px 0 0; text-align:right; } diff --git a/var/package/Interface_Adminhtml_Default-1.7.0.0-beta1.xml b/var/package/Interface_Adminhtml_Default-1.7.0.0-beta1.xml deleted file mode 100644 index b426f77266..0000000000 --- a/var/package/Interface_Adminhtml_Default-1.7.0.0-beta1.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - Interface_Adminhtml_Default - 1.7.0.0-beta1 - beta - AFL v3.0 - community - - Default interface for Adminhtml - Default interface for Adminhtml - 1.7.0.0-beta1 - Magento Core Teamcorecore@magentocommerce.com - 2012-03-02 - - - - 5.2.06.0.0Mage_Core_Adminhtmlcommunity1.7.0.0-beta11.8.0.0Lib_Js_Extcommunity1.7.0.0-beta11.8.0.0 - diff --git a/var/package/Interface_Adminhtml_Default-1.7.0.0-rc1.xml b/var/package/Interface_Adminhtml_Default-1.7.0.0-rc1.xml new file mode 100644 index 0000000000..9c7b14d7c0 --- /dev/null +++ b/var/package/Interface_Adminhtml_Default-1.7.0.0-rc1.xml @@ -0,0 +1,18 @@ + + + Interface_Adminhtml_Default + 1.7.0.0-rc1 + beta + AFL v3.0 + community + + Default interface for Adminhtml + Default interface for Adminhtml + 1.7.0.0-rc1 + Magento Core Teamcorecore@magentocommerce.com + 2012-03-28 + + + + 5.2.06.0.0Mage_Core_Adminhtmlcommunity1.7.0.0-rc11.8.0.0Lib_Js_Extcommunity1.7.0.0-rc11.8.0.0 + diff --git a/var/package/Interface_Frontend_Base_Default-1.7.0.0-beta1.xml b/var/package/Interface_Frontend_Base_Default-1.7.0.0-beta1.xml deleted file mode 100644 index 41970bc47a..0000000000 --- a/var/package/Interface_Frontend_Base_Default-1.7.0.0-beta1.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - Interface_Frontend_Base_Default - 1.7.0.0-beta1 - beta - AFL v3.0 - community - - This is a Magento themes base - This is a Magento themes base - 1.7.0.0-beta1 - Magento Core Teamcorecore@magentocommerce.com - 2012-03-02 - - - - 5.2.06.0.0Mage_Core_Modulescommunity1.7.0.0-beta11.8.0.0 - diff --git a/var/package/Interface_Frontend_Base_Default-1.7.0.0-rc1.xml b/var/package/Interface_Frontend_Base_Default-1.7.0.0-rc1.xml new file mode 100644 index 0000000000..45514c63cd --- /dev/null +++ b/var/package/Interface_Frontend_Base_Default-1.7.0.0-rc1.xml @@ -0,0 +1,18 @@ + + + Interface_Frontend_Base_Default + 1.7.0.0-rc1 + beta + AFL v3.0 + community + + This is a Magento themes base + This is a Magento themes base + 1.7.0.0-rc1 + Magento Core Teamcorecore@magentocommerce.com + 2012-03-28 + + + + 5.2.06.0.0Mage_Core_Modulescommunity1.7.0.0-rc11.8.0.0 + diff --git a/var/package/Interface_Frontend_Default-1.7.0.0-beta1.xml b/var/package/Interface_Frontend_Default-1.7.0.0-rc1.xml similarity index 97% rename from var/package/Interface_Frontend_Default-1.7.0.0-beta1.xml rename to var/package/Interface_Frontend_Default-1.7.0.0-rc1.xml index 3b3074543a..5a25fdb015 100644 --- a/var/package/Interface_Frontend_Default-1.7.0.0-beta1.xml +++ b/var/package/Interface_Frontend_Default-1.7.0.0-rc1.xml @@ -1,18 +1,18 @@ Interface_Frontend_Default - 1.7.0.0-beta1 + 1.7.0.0-rc1 beta AFL v3.0 community Default interface for Frontend Default interface for Frontend - 1.7.0.0-beta1 + 1.7.0.0-rc1 Magento Core Teamcorecore@magentocommerce.com - 2012-03-02 - - + 2012-03-28 + + - 5.2.06.0.0Mage_Core_Modulescommunity1.7.0.0-beta11.8.0.0 + 5.2.06.0.0Mage_Core_Modulescommunity1.7.0.0-rc11.8.0.0 diff --git a/var/package/Interface_Install_Default-1.7.0.0-beta1.xml b/var/package/Interface_Install_Default-1.7.0.0-rc1.xml similarity index 94% rename from var/package/Interface_Install_Default-1.7.0.0-beta1.xml rename to var/package/Interface_Install_Default-1.7.0.0-rc1.xml index 1bc7104f52..341fc09c92 100644 --- a/var/package/Interface_Install_Default-1.7.0.0-beta1.xml +++ b/var/package/Interface_Install_Default-1.7.0.0-rc1.xml @@ -1,18 +1,18 @@ Interface_Install_Default - 1.7.0.0-beta1 + 1.7.0.0-rc1 beta AFL v3.0 community Default interface for Install Default interface for Install - 1.7.0.0-beta1 + 1.7.0.0-rc1 Magento Core Teamcorecore@magentocommerce.com - 2012-03-02 - + 2012-03-28 + - 5.2.06.0.0Mage_Core_Modulescommunity1.7.0.0-beta11.8.0.0 + 5.2.06.0.0Mage_Core_Modulescommunity1.7.0.0-rc11.8.0.0 diff --git a/var/package/Lib_Js_Calendar-1.51.1.1-beta1.xml b/var/package/Lib_Js_Calendar-1.51.1.1-rc1.xml similarity index 95% rename from var/package/Lib_Js_Calendar-1.51.1.1-beta1.xml rename to var/package/Lib_Js_Calendar-1.51.1.1-rc1.xml index 651b520a09..645fe7780e 100644 --- a/var/package/Lib_Js_Calendar-1.51.1.1-beta1.xml +++ b/var/package/Lib_Js_Calendar-1.51.1.1-rc1.xml @@ -1,17 +1,17 @@ Lib_Js_Calendar - 1.51.1.1-beta1 + 1.51.1.1-rc1 beta Mixed community Javascript Calendar for Magento Javascript Calendar for Magento - 1.51.1.1-beta1 + 1.51.1.1-rc1 Magento Core Teamcorecore@magentocommerce.com - 2012-03-02 - + 2012-03-28 + 5.2.06.0.0 diff --git a/var/package/Lib_Js_Ext-1.7.0.0-beta1.xml b/var/package/Lib_Js_Ext-1.7.0.0-rc1.xml similarity index 99% rename from var/package/Lib_Js_Ext-1.7.0.0-beta1.xml rename to var/package/Lib_Js_Ext-1.7.0.0-rc1.xml index 161101c963..5816e2628f 100644 --- a/var/package/Lib_Js_Ext-1.7.0.0-beta1.xml +++ b/var/package/Lib_Js_Ext-1.7.0.0-rc1.xml @@ -1,17 +1,17 @@ Lib_Js_Ext - 1.7.0.0-beta1 + 1.7.0.0-rc1 beta Mixed community Extjs Javascript Libraries for Magento Extjs Javascript Libraries for Magento - 1.7.0.0-beta1 + 1.7.0.0-rc1 Magento Core Teamcorecore@magentocommerce.com - 2012-03-02 - + 2012-03-28 + 5.2.06.0.0 diff --git a/var/package/Lib_Js_Mage-1.7.0.0-beta1.xml b/var/package/Lib_Js_Mage-1.7.0.0-rc1.xml similarity index 90% rename from var/package/Lib_Js_Mage-1.7.0.0-beta1.xml rename to var/package/Lib_Js_Mage-1.7.0.0-rc1.xml index 36ee3f595f..241af8f9e6 100644 --- a/var/package/Lib_Js_Mage-1.7.0.0-beta1.xml +++ b/var/package/Lib_Js_Mage-1.7.0.0-rc1.xml @@ -1,18 +1,18 @@ Lib_Js_Mage - 1.7.0.0-beta1 + 1.7.0.0-rc1 beta Mixed community Javascript Libraries for Magento Javascript Libraries for Magento - 1.7.0.0-beta1 + 1.7.0.0-rc1 Magento Core Teamcorecore@magentocommerce.com - 2012-03-02 - - + 2012-03-28 + + - 5.2.06.0.0Lib_Js_Prototypecommunity1.7.0.0-beta11.7.1.0 + 5.2.06.0.0Lib_Js_Prototypecommunity1.7.0.0-rc11.7.1.0 diff --git a/var/package/Lib_Js_Prototype-1.7.0.0.3-beta1.xml b/var/package/Lib_Js_Prototype-1.7.0.0.3-rc1.xml similarity index 98% rename from var/package/Lib_Js_Prototype-1.7.0.0.3-beta1.xml rename to var/package/Lib_Js_Prototype-1.7.0.0.3-rc1.xml index 22d5e57d17..19558cbaf3 100644 --- a/var/package/Lib_Js_Prototype-1.7.0.0.3-beta1.xml +++ b/var/package/Lib_Js_Prototype-1.7.0.0.3-rc1.xml @@ -1,18 +1,18 @@ Lib_Js_Prototype - 1.7.0.0.3-beta1 + 1.7.0.0.3-rc1 beta Mixed community Prototype and Scriptaculous Javascript Libraries for Magento Prototype and Scriptaculous Javascript Libraries for Magento - 1.7.0.0.3-beta1 + 1.7.0.0.3-rc1 Magento Core Teamcorecore@magentocommerce.com - 2012-03-02 - - + 2012-03-28 + + 5.2.06.0.0 diff --git a/var/package/Lib_Js_TinyMCE-3.4.7.0-beta1.xml b/var/package/Lib_Js_TinyMCE-3.4.7.0-rc1.xml similarity index 99% rename from var/package/Lib_Js_TinyMCE-3.4.7.0-beta1.xml rename to var/package/Lib_Js_TinyMCE-3.4.7.0-rc1.xml index e09b7838fb..3a9e52920f 100644 --- a/var/package/Lib_Js_TinyMCE-3.4.7.0-beta1.xml +++ b/var/package/Lib_Js_TinyMCE-3.4.7.0-rc1.xml @@ -1,17 +1,17 @@ Lib_Js_TinyMCE - 3.4.7.0-beta1 + 3.4.7.0-rc1 beta Mixed community TinyMCE Javascript Libraries for Magento TinyMCE Javascript Libraries for Magento - 3.4.7.0-beta1 + 3.4.7.0-rc1 Magento Core Teamcorecore@magentocommerce.com - 2012-03-02 - + 2012-03-28 + 5.2.06.0.0 diff --git a/var/package/Lib_LinLibertineFont-2.8.14.1-beta1.xml b/var/package/Lib_LinLibertineFont-2.8.14.1-rc1.xml similarity index 92% rename from var/package/Lib_LinLibertineFont-2.8.14.1-beta1.xml rename to var/package/Lib_LinLibertineFont-2.8.14.1-rc1.xml index a2e3421cd9..36fa661b1c 100644 --- a/var/package/Lib_LinLibertineFont-2.8.14.1-beta1.xml +++ b/var/package/Lib_LinLibertineFont-2.8.14.1-rc1.xml @@ -1,17 +1,17 @@ Lib_LinLibertineFont - 2.8.14.1-beta1 + 2.8.14.1-rc1 beta GPL community Libertine Open Fonts Project fonts for PDF print-outs Libertine Open Fonts Project fonts for PDF print-outs - 2.8.14.1-beta1 + 2.8.14.1-rc1 Magento Core Teamcorecore@magentocommerce.com - 2012-03-02 - + 2012-03-28 + 5.2.06.0.0 diff --git a/var/package/Lib_Mage-1.7.0.0-beta1.xml b/var/package/Lib_Mage-1.7.0.0-beta1.xml deleted file mode 100644 index 94cab07c6f..0000000000 --- a/var/package/Lib_Mage-1.7.0.0-beta1.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - Lib_Mage - 1.7.0.0-beta1 - beta - OSL v3.0 - community - - Mage Library - Mage Library - 1.7.0.0-beta1 - Magento Core Teamcorecore@magentocommerce.com - 2012-03-02 - - - - 5.2.06.0.0 - diff --git a/var/package/Lib_Mage-1.7.0.0-rc1.xml b/var/package/Lib_Mage-1.7.0.0-rc1.xml new file mode 100644 index 0000000000..4623bc80de --- /dev/null +++ b/var/package/Lib_Mage-1.7.0.0-rc1.xml @@ -0,0 +1,18 @@ + + + Lib_Mage + 1.7.0.0-rc1 + beta + OSL v3.0 + community + + Mage Library + Mage Library + 1.7.0.0-rc1 + Magento Core Teamcorecore@magentocommerce.com + 2012-03-28 + + + + 5.2.06.0.0 + diff --git a/var/package/Lib_Varien-1.7.0.0-beta1.xml b/var/package/Lib_Varien-1.7.0.0-rc1.xml similarity index 98% rename from var/package/Lib_Varien-1.7.0.0-beta1.xml rename to var/package/Lib_Varien-1.7.0.0-rc1.xml index 83808f43e5..d606856351 100644 --- a/var/package/Lib_Varien-1.7.0.0-beta1.xml +++ b/var/package/Lib_Varien-1.7.0.0-rc1.xml @@ -1,18 +1,18 @@ Lib_Varien - 1.7.0.0-beta1 + 1.7.0.0-rc1 beta OSL v3.0 community Varien Library Varien Library - 1.7.0.0-beta1 + 1.7.0.0-rc1 Magento Core Teamcorecore@magentocommerce.com - 2012-03-02 - - + 2012-03-28 + + 5.2.06.0.0Lib_ZFcommunity1.11.1.01.12.0.0PDOSPLcurlSimpleXMLdomgdiconvpdo_mysqlmcryptpcreReflectionsession diff --git a/var/package/Mage_All_Latest-1.7.0.0-beta1.xml b/var/package/Mage_All_Latest-1.7.0.0-beta1.xml deleted file mode 100644 index f79ddc2e01..0000000000 --- a/var/package/Mage_All_Latest-1.7.0.0-beta1.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - Mage_All_Latest - 1.7.0.0-beta1 - beta - OSL v3.0 - community - - Metapackage for latest Magento 1.7.0.0-beta1 release - Metapackage for latest Magento 1.7.0.0-beta1 release - 1.7.0.0-beta1 - Magento Core Teamcorecore@magentocommerce.com - 2012-03-02 - - - - 5.2.06.0.0Mage_Core_Modulescommunity1.7.0.0-beta11.7.0.0-beta1Mage_Core_Adminhtmlcommunity1.7.0.0-beta11.7.0.0-beta1Interface_Adminhtml_Defaultcommunity1.7.0.0-beta11.7.0.0-beta1Interface_Frontend_Defaultcommunity1.7.0.0-beta11.7.0.0-beta1Interface_Install_Defaultcommunity1.7.0.0-beta11.7.0.0-beta1Mage_Downloadercommunity1.7.0.0-beta11.7.0.0-beta1Mage_Centinelcommunity1.7.0.0-beta11.7.0.0-beta1Interface_Frontend_Base_Defaultcommunity1.7.0.0-beta11.7.0.0-beta1Phoenix_Moneybookerscommunity1.3.1-beta11.3.1-beta1Mage_Compilercommunity1.7.0.0-beta11.7.0.0-beta1Magento_Mobilecommunity1.6.0.0.22.11.7.0.0 - diff --git a/var/package/Mage_All_Latest-1.7.0.0-rc1.xml b/var/package/Mage_All_Latest-1.7.0.0-rc1.xml new file mode 100644 index 0000000000..ad8c5fd7a9 --- /dev/null +++ b/var/package/Mage_All_Latest-1.7.0.0-rc1.xml @@ -0,0 +1,18 @@ + + + Mage_All_Latest + 1.7.0.0-rc1 + beta + OSL v3.0 + community + + Metapackage for latest Magento 1.7.0.0-rc1 release + Metapackage for latest Magento 1.7.0.0-rc1 release + 1.7.0.0-rc1 + Magento Core Teamcorecore@magentocommerce.com + 2012-03-28 + + + + 5.2.06.0.0Mage_Core_Modulescommunity1.7.0.0-rc11.7.0.0-rc1Mage_Core_Adminhtmlcommunity1.7.0.0-rc11.7.0.0-rc1Interface_Adminhtml_Defaultcommunity1.7.0.0-rc11.7.0.0-rc1Interface_Frontend_Defaultcommunity1.7.0.0-rc11.7.0.0-rc1Interface_Install_Defaultcommunity1.7.0.0-rc11.7.0.0-rc1Mage_Downloadercommunity1.7.0.0-rc11.7.0.0-rc1Mage_Centinelcommunity1.7.0.0-rc11.7.0.0-rc1Interface_Frontend_Base_Defaultcommunity1.7.0.0-rc11.7.0.0-rc1Phoenix_Moneybookerscommunity1.3.1-rc11.3.1-rc1Mage_Compilercommunity1.7.0.0-rc11.7.0.0-rc1Magento_Mobilecommunity1.6.0.0.22.11.7.0.0 + diff --git a/var/package/Mage_Centinel-1.7.0.0-beta1.xml b/var/package/Mage_Centinel-1.7.0.0-rc1.xml similarity index 95% rename from var/package/Mage_Centinel-1.7.0.0-beta1.xml rename to var/package/Mage_Centinel-1.7.0.0-rc1.xml index b663321573..3c17b357ca 100644 --- a/var/package/Mage_Centinel-1.7.0.0-beta1.xml +++ b/var/package/Mage_Centinel-1.7.0.0-rc1.xml @@ -1,18 +1,18 @@ Mage_Centinel - 1.7.0.0-beta1 + 1.7.0.0-rc1 beta mixed community 3D Secure Card Validation An integration with Cardinalcommerce Centinel service. Provides option to validate Visa and Mastercard cards for eliminating possible fraudlent order placement attempts. Adds information about Electronic Commerce Identifier, that designates liability for chargeback. - 1.7.0.0-beta1 + 1.7.0.0-rc1 Magento Core Teamcorecore@magentocommerce.com - 2012-03-02 - + 2012-03-28 + - 5.2.06.0.0Mage_Core_Modulescommunity1.7.0.0-beta11.8.0.0 + 5.2.06.0.0Mage_Core_Modulescommunity1.7.0.0-rc11.8.0.0 diff --git a/var/package/Mage_Compiler-1.7.0.0-beta1.xml b/var/package/Mage_Compiler-1.7.0.0-rc1.xml similarity index 83% rename from var/package/Mage_Compiler-1.7.0.0-beta1.xml rename to var/package/Mage_Compiler-1.7.0.0-rc1.xml index 033f43fb2a..9f93794203 100644 --- a/var/package/Mage_Compiler-1.7.0.0-beta1.xml +++ b/var/package/Mage_Compiler-1.7.0.0-rc1.xml @@ -1,18 +1,18 @@ Mage_Compiler - 1.7.0.0-beta1 + 1.7.0.0-rc1 beta OSL v3.0 community This module compiles all files of a Magento installation and creates a single include path to increase performance This module compiles all files of a Magento installation and creates a single include path to increase performance - 1.7.0.0-beta1 + 1.7.0.0-rc1 Magento Core Teamcorecore@magentocommerce.com - 2012-03-02 - - + 2012-03-28 + + - 5.2.06.0.0Mage_Core_Modulescommunity1.7.0.0-beta11.8.0.0Mage_Core_Adminhtmlcommunity1.7.0.0-beta11.8.0.0 + 5.2.06.0.0Mage_Core_Modulescommunity1.7.0.0-rc11.8.0.0Mage_Core_Adminhtmlcommunity1.7.0.0-rc11.8.0.0 diff --git a/var/package/Mage_Core_Adminhtml-1.7.0.0-beta1.xml b/var/package/Mage_Core_Adminhtml-1.7.0.0-rc1.xml similarity index 53% rename from var/package/Mage_Core_Adminhtml-1.7.0.0-beta1.xml rename to var/package/Mage_Core_Adminhtml-1.7.0.0-rc1.xml index 8a9e60e3aa..b056dc39eb 100644 --- a/var/package/Mage_Core_Adminhtml-1.7.0.0-beta1.xml +++ b/var/package/Mage_Core_Adminhtml-1.7.0.0-rc1.xml @@ -1,18 +1,18 @@ Mage_Core_Adminhtml - 1.7.0.0-beta1 + 1.7.0.0-rc1 beta OSL v3.0 community Magento Administration Panel Magento Administration Panel - 1.7.0.0-beta1 + 1.7.0.0-rc1 Magento Core Teamcorecore@magentocommerce.com - 2012-03-02 - - + 2012-03-28 + + - 5.2.06.0.0Mage_Core_Modulescommunity1.7.0.0-beta11.8.0.0Lib_Js_Calendarcommunity1.51.1.1-beta11.52Lib_Js_Extcommunity1.7.0.0-beta11.8.0.0Lib_LinLibertineFontcommunity2.8.14.1-beta12.9.0.0Lib_Js_TinyMCEcommunity3.4.7.0-beta13.5.0.0 + 5.2.06.0.0Mage_Core_Modulescommunity1.7.0.0-rc11.8.0.0Lib_Js_Calendarcommunity1.51.1.1-rc11.52Lib_Js_Extcommunity1.7.0.0-rc11.8.0.0Lib_LinLibertineFontcommunity2.8.14.1-rc12.9.0.0Lib_Js_TinyMCEcommunity3.4.7.0-rc13.5.0.0 diff --git a/var/package/Mage_Core_Modules-1.7.0.0-beta1.xml b/var/package/Mage_Core_Modules-1.7.0.0-beta1.xml deleted file mode 100644 index 2a111fa123..0000000000 --- a/var/package/Mage_Core_Modules-1.7.0.0-beta1.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - Mage_Core_Modules - 1.7.0.0-beta1 - beta - OSL v3.0 - community - - Collection of Magento Core Modules - Collection of Magento Core Modules - 1.7.0.0-beta1 - Magento Core Teamcorecore@magentocommerce.com - 2012-03-02 - - - - 5.2.06.0.0Lib_Variencommunity1.7.0.0-beta11.8.0.0Lib_Google_Checkoutcommunity1.5.0.01.5.1.0Lib_Js_Calendarcommunity1.51.1.1-beta11.52Lib_Js_Magecommunity1.7.0.0-beta11.8.0.0Lib_Js_Prototypecommunity1.7.0.0.3-beta11.7.1.0Lib_Phpseclibcommunity1.5.0.01.5.1.0Mage_Locale_en_UScommunity1.7.0.0-beta11.8.0.0Lib_Magecommunity1.7.0.0-beta11.8.0.0 - diff --git a/var/package/Mage_Core_Modules-1.7.0.0-rc1.xml b/var/package/Mage_Core_Modules-1.7.0.0-rc1.xml new file mode 100644 index 0000000000..a83b8ed36e --- /dev/null +++ b/var/package/Mage_Core_Modules-1.7.0.0-rc1.xml @@ -0,0 +1,18 @@ + + + Mage_Core_Modules + 1.7.0.0-rc1 + beta + OSL v3.0 + community + + Collection of Magento Core Modules + Collection of Magento Core Modules + 1.7.0.0-rc1 + Magento Core Teamcorecore@magentocommerce.com + 2012-03-28 + + + + 5.2.06.0.0Lib_Variencommunity1.7.0.0-rc11.8.0.0Lib_Google_Checkoutcommunity1.5.0.01.5.1.0Lib_Js_Calendarcommunity1.51.1.1-rc11.52Lib_Js_Magecommunity1.7.0.0-rc11.8.0.0Lib_Js_Prototypecommunity1.7.0.0.3-rc11.7.1.0Lib_Phpseclibcommunity1.5.0.01.5.1.0Mage_Locale_en_UScommunity1.7.0.0-rc11.8.0.0Lib_Magecommunity1.7.0.0-rc11.8.0.0 + diff --git a/var/package/Mage_Downloader-1.7.0.0-beta1.xml b/var/package/Mage_Downloader-1.7.0.0-rc1.xml similarity index 52% rename from var/package/Mage_Downloader-1.7.0.0-beta1.xml rename to var/package/Mage_Downloader-1.7.0.0-rc1.xml index f959f5c8fd..1f745139c2 100644 --- a/var/package/Mage_Downloader-1.7.0.0-beta1.xml +++ b/var/package/Mage_Downloader-1.7.0.0-rc1.xml @@ -1,18 +1,18 @@ Mage_Downloader - 1.7.0.0-beta1 + 1.7.0.0-rc1 beta OSL v3.0 community Magento Downloader Magento Downloader - 1.7.0.0-beta1 + 1.7.0.0-rc1 Magento Core Teamcorecore@magentocommerce.com - 2012-03-02 - - + 2012-03-28 + + 5.2.06.0.0 diff --git a/var/package/Mage_Locale_en_US-1.7.0.0-beta1.xml b/var/package/Mage_Locale_en_US-1.7.0.0-beta1.xml deleted file mode 100644 index 7f9adf0b85..0000000000 --- a/var/package/Mage_Locale_en_US-1.7.0.0-beta1.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - Mage_Locale_en_US - 1.7.0.0-beta1 - beta - OSL v3.0 - community - - en_US locale - en_US locale - 1.7.0.0-beta1 - Magento Core Teamcorecore@magentocommerce.com - 2012-03-02 - - - - 5.2.06.0.0 - diff --git a/var/package/Mage_Locale_en_US-1.7.0.0-rc1.xml b/var/package/Mage_Locale_en_US-1.7.0.0-rc1.xml new file mode 100644 index 0000000000..dda5a10b1d --- /dev/null +++ b/var/package/Mage_Locale_en_US-1.7.0.0-rc1.xml @@ -0,0 +1,18 @@ + + + Mage_Locale_en_US + 1.7.0.0-rc1 + beta + OSL v3.0 + community + + en_US locale + en_US locale + 1.7.0.0-rc1 + Magento Core Teamcorecore@magentocommerce.com + 2012-03-28 + + + + 5.2.06.0.0 + diff --git a/var/package/Phoenix_Moneybookers-1.3.1-beta1.xml b/var/package/Phoenix_Moneybookers-1.3.1-rc1.xml similarity index 97% rename from var/package/Phoenix_Moneybookers-1.3.1-beta1.xml rename to var/package/Phoenix_Moneybookers-1.3.1-rc1.xml index ec593113b6..cb258741a5 100644 --- a/var/package/Phoenix_Moneybookers-1.3.1-beta1.xml +++ b/var/package/Phoenix_Moneybookers-1.3.1-rc1.xml @@ -1,7 +1,7 @@ Phoenix_Moneybookers - 1.3.1-beta1 + 1.3.1-rc1 beta mixed community @@ -25,11 +25,11 @@ Przelewy24 - Poland Sofortüberweisung - Germany Nordea Solo - Finland Moneybookers eWallet - 1.3.1-beta1 + 1.3.1-rc1 Magento Core Teamcorecore@magentocommerce.com - 2012-03-02 - + 2012-03-28 + - 5.2.06.0.0Mage_Core_Modulescommunity1.7.0.0-beta11.8.0.0 + 5.2.06.0.0Mage_Core_Modulescommunity1.7.0.0-rc11.8.0.0