diff --git a/LICENSE.html b/LICENSE.html new file mode 100644 index 0000000000..e2315f6a41 --- /dev/null +++ b/LICENSE.html @@ -0,0 +1,42 @@ +

Open Software License ("OSL") v. 3.0

+ +

This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work:

+
Licensed under the Open Software License version 3.0
+

Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following:

+ + +

Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works.

+ +

Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work.

+ +

Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license.

+ +

External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c).

+ +

Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work.

+ +

Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer.

+ +

Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation.

+ +

Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c).

+ +

Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware.

+ +

Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License.

+ +

Attorneys Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License.

+ +

Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable.

+ +

Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.

+ +

Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You.

+ +

Modification of This License. This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under Open Software License ("OSL") v. 3.0" or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process.

diff --git a/app/Mage.php b/app/Mage.php index 4720e398ae..482bd1172d 100644 --- a/app/Mage.php +++ b/app/Mage.php @@ -82,7 +82,7 @@ final class Mage { public static function getVersion() { - return '1.3.0'; + return '1.3.1'; } /** diff --git a/app/code/core/Mage/Admin/Model/Mysql4/User.php b/app/code/core/Mage/Admin/Model/Mysql4/User.php index 3c7061ff7c..3f129d3927 100644 --- a/app/code/core/Mage/Admin/Model/Mysql4/User.php +++ b/app/code/core/Mage/Admin/Model/Mysql4/User.php @@ -279,4 +279,12 @@ public function userExists(Mage_Core_Model_Abstract $user) $select->where("({$usersTable}.username = '{$user->getUsername()}' OR {$usersTable}.email = '{$user->getEmail()}') AND {$usersTable}.user_id != '{$user->getId()}'"); return $this->_getReadAdapter()->fetchRow($select); } + + public function saveExtra($object, $data) + { + if ($object->getId()) { + $this->_getWriteAdapter()->update($this->getMainTable(), array('extra'=>$data)); + } + return $this; + } } diff --git a/app/code/core/Mage/Admin/Model/Observer.php b/app/code/core/Mage/Admin/Model/Observer.php index a205c1d872..9fc1e39724 100644 --- a/app/code/core/Mage/Admin/Model/Observer.php +++ b/app/code/core/Mage/Admin/Model/Observer.php @@ -53,6 +53,7 @@ public function actionPreDispatchAdmin($event) $username = $postLogin['username']; $password = $postLogin['password']; $user = $session->login($username, $password, $request); + $request->setPost('login', null); } if (!$request->getParam('forwarded')) { if ($request->getParam('isIframe')) { diff --git a/app/code/core/Mage/Admin/Model/Session.php b/app/code/core/Mage/Admin/Model/Session.php index d6a5d1e391..c1061326b0 100644 --- a/app/code/core/Mage/Admin/Model/Session.php +++ b/app/code/core/Mage/Admin/Model/Session.php @@ -70,6 +70,7 @@ public function login($username, $password, $request = null) $user = Mage::getModel('admin/user'); $user->login($username, $password); if ($user->getId()) { + if (Mage::getSingleton('adminhtml/url')->useSecretKey()) { Mage::getSingleton('adminhtml/url')->renewSecretUrls(); } @@ -78,6 +79,7 @@ public function login($username, $password, $request = null) $session->setUser($user); $session->setAcl(Mage::getResourceModel('admin/acl')->loadAcl()); if ($requestUri = $this->_getRequestUri($request)) { + Mage::dispatchEvent('admin_session_user_login_success', array('user'=>$user)); header('Location: ' . $requestUri); exit; } @@ -87,6 +89,7 @@ public function login($username, $password, $request = null) } } catch (Mage_Core_Exception $e) { + Mage::dispatchEvent('admin_session_user_login_failed', array('user_name'=>$username)); if ($request && !$request->getParam('messageSent')) { Mage::getSingleton('adminhtml/session')->addError($e->getMessage()); $request->setParam('messageSent', true); diff --git a/app/code/core/Mage/Admin/Model/User.php b/app/code/core/Mage/Admin/Model/User.php index 3ac0b2878c..a0b6e712bf 100644 --- a/app/code/core/Mage/Admin/Model/User.php +++ b/app/code/core/Mage/Admin/Model/User.php @@ -97,6 +97,21 @@ public function save() return $this; } + /** + * Save admin user extra data (like configuration sections state) + * + * @param array $data + * @return Mage_Admin_Model_User + */ + public function saveExtra($data) + { + if (is_array($data)) { + $data = serialize($data); + } + $this->_getResource()->saveExtra($this, $data); + return $this; + } + /** * Delete user * @@ -227,25 +242,22 @@ public function authenticate($username, $password) $result = false; try { $this->loadByUsername($username); - if ($this->getId()) { + if ($this->getId() && Mage::helper('core')->validateHash($password, $this->getPassword())) { if ($this->getIsActive() != '1') { Mage::throwException(Mage::helper('adminhtml')->__('This account is inactive.')); } - if (Mage::helper('core')->validateHash($password, $this->getPassword())) { - $result = true; + if (!$this->hasAssigned2Role($this->getId())) { + Mage::throwException(Mage::helper('adminhtml')->__('Access Denied.')); } + $result = true; } - Mage::dispatchEvent('admin_user_authenticated', array( + Mage::dispatchEvent('admin_user_authenticate_after', array( 'username' => $username, 'password' => $password, 'user' => $this, 'result' => $result, )); - - if (!$this->hasAssigned2Role($this->getId())) { - Mage::throwException(Mage::helper('adminhtml')->__('Access Denied.')); - } } catch (Mage_Core_Exception $e) { $this->unsetData(); @@ -270,14 +282,6 @@ public function login($username, $password) if ($this->authenticate($username, $password)) { $this->getResource()->recordLogin($this); } - - // dispatch event regardless the user was logged in or not - Mage::dispatchEvent('admin_user_on_login', array( - 'user' => $this, - 'username' => $username, - 'password' => $password, - )); - return $this; } diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Attribute/Edit.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Attribute/Edit.php index 7a75f02e5e..67e7171cf5 100644 --- a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Attribute/Edit.php +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Attribute/Edit.php @@ -76,7 +76,11 @@ public function __construct() public function getHeaderText() { if (Mage::registry('entity_attribute')->getId()) { - return Mage::helper('catalog')->__('Edit Product Attribute "%s"', $this->htmlEscape(Mage::registry('entity_attribute')->getFrontendLabel())); + $frontendLabel = Mage::registry('entity_attribute')->getFrontendLabel(); + if (is_array($frontendLabel)) { + $frontendLabel = $frontendLabel[0]; + } + return Mage::helper('catalog')->__('Edit Product Attribute "%s"', $this->htmlEscape($frontendLabel)); } else { return Mage::helper('catalog')->__('New Product Attribute'); diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Super/Config.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Super/Config.php index bfd321e185..19a85a6668 100644 --- a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Super/Config.php +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Super/Config.php @@ -1,212 +1,310 @@ - - */ - -class Mage_Adminhtml_Block_Catalog_Product_Edit_Tab_Super_Config extends Mage_Adminhtml_Block_Widget implements Mage_Adminhtml_Block_Widget_Tab_Interface -{ - public function __construct() - { - parent::__construct(); - $this->setProductId($this->getRequest()->getParam('id')); - $this->setTemplate('catalog/product/edit/super/config.phtml'); - $this->setId('config_super_product'); - } - - public function getTabClass() - { - return 'ajax'; - } - - protected function _prepareLayout() - { - $this->setChild('grid', - $this->getLayout()->createBlock('adminhtml/catalog_product_edit_tab_super_config_grid') - ); - - - - $this->setChild('create_empty', - $this->getLayout()->createBlock('adminhtml/widget_button') - ->setData(array( - 'label' => Mage::helper('catalog')->__('Create Empty'), - 'class' => 'add', - 'onclick' => 'superProduct.createEmptyProduct()' - )) - ); - - if ($this->_getProduct()->getId()) { - $this->setChild('simple', - $this->getLayout()->createBlock('adminhtml/catalog_product_edit_tab_super_config_simple') - ); - - $this->setChild('create_from_configurable', - $this->getLayout()->createBlock('adminhtml/widget_button') - ->setData(array( - 'label' => Mage::helper('catalog')->__('Copy From Configurable'), - 'class' => 'add', - 'onclick' => 'superProduct.createNewProduct()' - )) - ); - } - - return parent::_prepareLayout(); - } - - /** - * Retrieve currently edited product object - * - * @return Mage_Catalog_Model_Product - */ - protected function _getProduct() - { - return Mage::registry('current_product'); - } - - public function getAttributesJson() - { - $attributes = $this->_getProduct()->getTypeInstance(true)->getConfigurableAttributesAsArray($this->_getProduct()); - if(!$attributes) { - return '[]'; - } - return Zend_Json::encode($attributes); - } - - public function getLinksJson() - { - $products = $this->_getProduct()->getTypeInstance(true)->getUsedProducts(null, $this->_getProduct()); - if(!$products) { - return '{}'; - } - $data = array(); - foreach ($products as $product) { - $data[$product->getId()] = $this->getConfigurableSettings($product); - } - return Zend_Json::encode($data); - } - - public function getConfigurableSettings($product) { - $data = array(); - foreach ($this->_getProduct()->getTypeInstance(true)->getUsedProductAttributes($this->_getProduct()) as $attribute) { - $data[] = array( - 'attribute_id' => $attribute->getId(), - 'label' => $product->getAttributeText($attribute->getAttributeCode()), - 'value_index' => $product->getData($attribute->getAttributeCode()) - ); - } - - return $data; - } - - public function getGridHtml() - { - return $this->getChildHtml('grid'); - } - - public function getGridJsObject() - { - return $this->getChild('grid')->getJsObjectName(); - } - - public function getNewEmptyProductUrl() - { - return $this->getUrl( - '*/*/new', - array( - 'set' => $this->_getProduct()->getAttributeSetId(), - 'type' => Mage_Catalog_Model_Product_Type::TYPE_SIMPLE, - 'required' => $this->_getRequiredAttributesIds(), - 'popup' => 1 - ) - ); - } - - public function getNewProductUrl() - { - return $this->getUrl( - '*/*/new', - array( - 'set' => $this->_getProduct()->getAttributeSetId(), - 'type' => Mage_Catalog_Model_Product_Type::TYPE_SIMPLE, - 'required' => $this->_getRequiredAttributesIds(), - 'popup' => 1, - 'product' => $this->_getProduct()->getId() - ) - ); - } - - - - public function getQuickCreationUrl() - { - return $this->getUrl( - '*/*/quickCreate', - array( - 'product' => $this->_getProduct()->getId() - ) - ); - } - - - protected function _getRequiredAttributesIds() - { - $attributesIds = array(); - foreach ($this->_getProduct()->getTypeInstance(true)->getConfigurableAttributes($this->_getProduct()) as $attribute) { - $attributesIds[] = $attribute->getProductAttribute()->getId(); - } - - return implode(',', $attributesIds); - } - - public function escapeJs($string) - { - return addcslashes($string, "'\r\n\\"); - } - - public function getTabLabel() - { - return Mage::helper('catalog')->__('Associated Products'); - } - public function getTabTitle() - { - return Mage::helper('catalog')->__('Associated Products'); - } - public function canShowTab() - { - return true; - } - public function isHidden() - { - return false; - } - -}// Class Mage_Adminhtml_Block_Catalog_Product_Edit_Tab_Super_Config END \ No newline at end of file + + */ +class Mage_Adminhtml_Block_Catalog_Product_Edit_Tab_Super_Config extends Mage_Adminhtml_Block_Widget implements Mage_Adminhtml_Block_Widget_Tab_Interface +{ + /** + * Initialize block + * + */ + public function __construct() + { + parent::__construct(); + $this->setProductId($this->getRequest()->getParam('id')); + $this->setTemplate('catalog/product/edit/super/config.phtml'); + $this->setId('config_super_product'); + } + + /** + * Retrieve Tab class (for loading) + * + * @return string + */ + public function getTabClass() + { + return 'ajax'; + } + + /** + * Prepare Layout data + * + * @return Mage_Adminhtml_Block_Catalog_Product_Edit_Tab_Super_Config + */ + protected function _prepareLayout() + { + $this->setChild('grid', + $this->getLayout()->createBlock('adminhtml/catalog_product_edit_tab_super_config_grid') + ); + + $this->setChild('create_empty', + $this->getLayout()->createBlock('adminhtml/widget_button') + ->setData(array( + 'label' => Mage::helper('catalog')->__('Create Empty'), + 'class' => 'add', + 'onclick' => 'superProduct.createEmptyProduct()' + )) + ); + + if ($this->_getProduct()->getId()) { + $this->setChild('simple', + $this->getLayout()->createBlock('adminhtml/catalog_product_edit_tab_super_config_simple') + ); + + $this->setChild('create_from_configurable', + $this->getLayout()->createBlock('adminhtml/widget_button') + ->setData(array( + 'label' => Mage::helper('catalog')->__('Copy From Configurable'), + 'class' => 'add', + 'onclick' => 'superProduct.createNewProduct()' + )) + ); + } + + return parent::_prepareLayout(); + } + + /** + * Retrieve currently edited product object + * + * @return Mage_Catalog_Model_Product + */ + protected function _getProduct() + { + return Mage::registry('current_product'); + } + + /** + * Retrieve attributes data in JSON format + * + * @return string + */ + public function getAttributesJson() + { + $attributes = $this->_getProduct()->getTypeInstance(true) + ->getConfigurableAttributesAsArray($this->_getProduct()); + if(!$attributes) { + return '[]'; + } + return Zend_Json::encode($attributes); + } + + /** + * Retrieve Links in JSON format + * + * @return string + */ + public function getLinksJson() + { + $products = $this->_getProduct()->getTypeInstance(true) + ->getUsedProducts(null, $this->_getProduct()); + if(!$products) { + return '{}'; + } + $data = array(); + foreach ($products as $product) { + $data[$product->getId()] = $this->getConfigurableSettings($product); + } + return Zend_Json::encode($data); + } + + /** + * Retrieve configurable settings + * + * @param Mage_Catalog_Model_Product $product + * @return array + */ + public function getConfigurableSettings($product) { + $data = array(); + $attributes = $this->_getProduct()->getTypeInstance(true) + ->getUsedProductAttributes($this->_getProduct()); + foreach ($attributes as $attribute) { + $data[] = array( + 'attribute_id' => $attribute->getId(), + 'label' => $product->getAttributeText($attribute->getAttributeCode()), + 'value_index' => $product->getData($attribute->getAttributeCode()) + ); + } + + return $data; + } + + /** + * Retrieve Grid child HTML + * + * @return string + */ + public function getGridHtml() + { + return $this->getChildHtml('grid'); + } + + /** + * Retrieve Grid JavaScript object name + * + * @return string + */ + public function getGridJsObject() + { + return $this->getChild('grid')->getJsObjectName(); + } + + /** + * Retrieve Create New Empty Product URL + * + * @return string + */ + public function getNewEmptyProductUrl() + { + return $this->getUrl( + '*/*/new', + array( + 'set' => $this->_getProduct()->getAttributeSetId(), + 'type' => Mage_Catalog_Model_Product_Type::TYPE_SIMPLE, + 'required' => $this->_getRequiredAttributesIds(), + 'popup' => 1 + ) + ); + } + + /** + * Retrieve Create New Product URL + * + * @return string + */ + public function getNewProductUrl() + { + return $this->getUrl( + '*/*/new', + array( + 'set' => $this->_getProduct()->getAttributeSetId(), + 'type' => Mage_Catalog_Model_Product_Type::TYPE_SIMPLE, + 'required' => $this->_getRequiredAttributesIds(), + 'popup' => 1, + 'product' => $this->_getProduct()->getId() + ) + ); + } + + /** + * Retrieve Quick create product URL + * + * @return string + */ + public function getQuickCreationUrl() + { + return $this->getUrl( + '*/*/quickCreate', + array( + 'product' => $this->_getProduct()->getId() + ) + ); + } + + /** + * Retrieve Required attributes Ids (comma separated) + * + * @return string + */ + protected function _getRequiredAttributesIds() + { + $attributesIds = array(); + foreach ($this->_getProduct()->getTypeInstance(true)->getConfigurableAttributes($this->_getProduct()) as $attribute) { + $attributesIds[] = $attribute->getProductAttribute()->getId(); + } + + return implode(',', $attributesIds); + } + + /** + * Escape JavaScript string + * + * @param string $string + * @return string + */ + public function escapeJs($string) + { + return addcslashes($string, "'\r\n\\"); + } + + /** + * Retrieve Tab label + * + * @return string + */ + public function getTabLabel() + { + return Mage::helper('catalog')->__('Associated Products'); + } + + /** + * Retrieve Tab title + * + * @return string + */ + public function getTabTitle() + { + return Mage::helper('catalog')->__('Associated Products'); + } + + /** + * Can show tab flag + * + * @return bool + */ + public function canShowTab() + { + return true; + } + + /** + * Check is a hidden tab + * + * @return bool + */ + public function isHidden() + { + return false; + } + + /** + * Show "Use default price" checkbox + * + * @return bool + */ + public function getShowUseDefaultPrice() + { + return !Mage::helper('catalog')->isPriceGlobal() + && $this->_getProduct()->getStoreId(); + } +} diff --git a/app/code/core/Mage/Adminhtml/Block/Extensions/Custom/Edit/Tab/Package.php b/app/code/core/Mage/Adminhtml/Block/Extensions/Custom/Edit/Tab/Package.php index 767930e44a..59b30122b7 100644 --- a/app/code/core/Mage/Adminhtml/Block/Extensions/Custom/Edit/Tab/Package.php +++ b/app/code/core/Mage/Adminhtml/Block/Extensions/Custom/Edit/Tab/Package.php @@ -71,7 +71,6 @@ public function initForm() 'name' => 'channel', 'label' => Mage::helper('adminhtml')->__('Channel'), 'required' => true, - 'value' => 'var-dev.varien.com', )); $fieldset->addField('summary', 'textarea', array( diff --git a/app/code/core/Mage/Adminhtml/Block/Notification/Window.php b/app/code/core/Mage/Adminhtml/Block/Notification/Window.php index 830bdc985f..833af7c051 100644 --- a/app/code/core/Mage/Adminhtml/Block/Notification/Window.php +++ b/app/code/core/Mage/Adminhtml/Block/Notification/Window.php @@ -75,6 +75,11 @@ protected function _construct() */ public function canShow() { + if (!$this->_isObjectReadable()) { + $this->_available = false; + return false; + } + if (!$this->_isAllowed()) { $this->_available = false; return false; @@ -124,4 +129,17 @@ protected function _isAllowed() return true; } } + + /** + * Check whether or not the remote flash file is available + * + * @return boolean + */ + protected function _isObjectReadable() + { + if (@fopen($this->getObjectUrl() . '.swf', 'r') || @fopen($this->getObjectUrl() . '.dcr', 'r')) { + return true; + } + return false; + } } diff --git a/app/code/core/Mage/Adminhtml/Block/Permissions/User/Edit/Tab/Main.php b/app/code/core/Mage/Adminhtml/Block/Permissions/User/Edit/Tab/Main.php index 66ed859db9..61bf5d2432 100644 --- a/app/code/core/Mage/Adminhtml/Block/Permissions/User/Edit/Tab/Main.php +++ b/app/code/core/Mage/Adminhtml/Block/Permissions/User/Edit/Tab/Main.php @@ -94,7 +94,7 @@ protected function _prepareForm() 'label' => Mage::helper('adminhtml')->__('New Password'), 'id' => 'new_pass', 'title' => Mage::helper('adminhtml')->__('New Password'), - 'class' => 'input-text validate-password', + 'class' => 'input-text validate-admin-password', )); $fieldset->addField('confirmation', 'password', array( diff --git a/app/code/core/Mage/Adminhtml/Block/Report/Product/Sold.php b/app/code/core/Mage/Adminhtml/Block/Report/Product/Sold.php new file mode 100644 index 0000000000..10f02d5f63 --- /dev/null +++ b/app/code/core/Mage/Adminhtml/Block/Report/Product/Sold.php @@ -0,0 +1,48 @@ + + */ +class Mage_Adminhtml_Block_Report_Product_Sold extends Mage_Adminhtml_Block_Widget_Grid_Container +{ + /** + * Initialize container block settings + * + */ + public function __construct() + { + $this->_controller = 'report_product_sold'; + $this->_headerText = Mage::helper('reports')->__('Products Ordered'); + parent::__construct(); + $this->_removeButton('add'); + } +} diff --git a/app/code/core/Mage/Adminhtml/Block/Report/Product/Sold/Grid.php b/app/code/core/Mage/Adminhtml/Block/Report/Product/Sold/Grid.php new file mode 100644 index 0000000000..5c3815e025 --- /dev/null +++ b/app/code/core/Mage/Adminhtml/Block/Report/Product/Sold/Grid.php @@ -0,0 +1,93 @@ + + */ +class Mage_Adminhtml_Block_Report_Product_Sold_Grid extends Mage_Adminhtml_Block_Report_Grid +{ + /** + * Sub report size + * + * @var int + */ + protected $_subReportSize = 0; + + /** + * Initialize Grid settings + * + */ + public function __construct() + { + parent::__construct(); + $this->setId('gridProductsSold'); + } + + /** + * Prepare collection object for grid + * + * @return Mage_Adminhtml_Block_Report_Product_Sold_Grid + */ + protected function _prepareCollection() + { + parent::_prepareCollection(); + $this->getCollection() + ->initReport('reports/product_sold_collection'); + return $this; + } + + /** + * Prepare Grid columns + * + * @return Mage_Adminhtml_Block_Report_Product_Sold_Grid + */ + protected function _prepareColumns() + { + $this->addColumn('name', array( + 'header' =>Mage::helper('reports')->__('Product Name'), + 'index' =>'name' + )); + + $this->addColumn('ordered_qty', array( + 'header' =>Mage::helper('reports')->__('Quantity Ordered'), + 'width' =>'120px', + 'align' =>'right', + 'index' =>'ordered_qty', + 'total' =>'sum', + 'type' =>'number' + )); + + $this->addExportType('*/*/exportSoldCsv', Mage::helper('reports')->__('CSV')); + $this->addExportType('*/*/exportSoldExcel', Mage::helper('reports')->__('Excel')); + + return parent::_prepareColumns(); + } +} diff --git a/app/code/core/Mage/Adminhtml/Block/Report/Search.php b/app/code/core/Mage/Adminhtml/Block/Report/Search.php index 5bab3a0a8f..253c693cf5 100644 --- a/app/code/core/Mage/Adminhtml/Block/Report/Search.php +++ b/app/code/core/Mage/Adminhtml/Block/Report/Search.php @@ -34,7 +34,10 @@ class Mage_Adminhtml_Block_Report_Search extends Mage_Adminhtml_Block_Widget_Grid_Container { - + /** + * Initialize Grid Container + * + */ public function __construct() { $this->_controller = 'report_search'; @@ -42,5 +45,4 @@ public function __construct() parent::__construct(); $this->_removeButton('add'); } - } \ No newline at end of file diff --git a/app/code/core/Mage/Adminhtml/Block/Report/Search/Grid.php b/app/code/core/Mage/Adminhtml/Block/Report/Search/Grid.php index d7e39321fd..0c52a8f155 100644 --- a/app/code/core/Mage/Adminhtml/Block/Report/Search/Grid.php +++ b/app/code/core/Mage/Adminhtml/Block/Report/Search/Grid.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com) + * @copyright Copyright (c) 2009 Irubin Consulting Inc. DBA Varien (http://www.varien.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -29,11 +29,14 @@ * * @category Mage * @package Mage_Adminhtml - * @author Magento Core Team + * @author Magento Core Team */ class Mage_Adminhtml_Block_Report_Search_Grid extends Mage_Adminhtml_Block_Widget_Grid { - + /** + * Initialize Grid Properties + * + */ public function __construct() { parent::__construct(); @@ -42,17 +45,24 @@ public function __construct() $this->setDefaultDir('desc'); } + /** + * Prepare Search Report collection for grid + * + * @return Mage_Adminhtml_Block_Report_Search_Grid + */ protected function _prepareCollection() { - $collection = Mage::getResourceModel('catalogsearch/query_collection'); $this->setCollection($collection); - parent::_prepareCollection(); - - return $this; + return parent::_prepareCollection(); } + /** + * Prepare Grid columns + * + * @return Mage_Adminhtml_Block_Report_Search_Grid + */ protected function _prepareColumns() { $this->addColumn('query_id', array( @@ -101,5 +111,14 @@ protected function _prepareColumns() return parent::_prepareColumns(); } + /** + * Retrieve Row Click callback URL + * + * @return string + */ + public function getRowUrl($row) + { + return $this->getUrl('*/catalog_search/edit', array('id' => $row->getId())); + } } diff --git a/app/code/core/Mage/Adminhtml/Block/Sales/Items/Column/Default.php b/app/code/core/Mage/Adminhtml/Block/Sales/Items/Column/Default.php index 7acc4f3a61..78db1bf46a 100644 --- a/app/code/core/Mage/Adminhtml/Block/Sales/Items/Column/Default.php +++ b/app/code/core/Mage/Adminhtml/Block/Sales/Items/Column/Default.php @@ -60,6 +60,27 @@ public function getOrderOptions() return $result; } + /** + * Return custom option html + * + * @param array $optionInfo + * @return string + */ + public function getCustomizedOptionValue($optionInfo) + { + // render customized option view + $_default = $optionInfo['value']; + if (isset($optionInfo['option_type'])) { + try { + $group = Mage::getModel('catalog/product_option')->groupFactory($optionInfo['option_type']); + return $group->getCustomizedView($optionInfo); + } catch (Exception $e) { + return $_default; + } + } + return $_default; + } + public function getSku() { if ($this->getItem()->getProductType() == Mage_Catalog_Model_Product_Type::TYPE_CONFIGURABLE) { diff --git a/app/code/core/Mage/Adminhtml/Block/System/Account/Edit/Form.php b/app/code/core/Mage/Adminhtml/Block/System/Account/Edit/Form.php index 991479ec72..61b4c8199b 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Account/Edit/Form.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Account/Edit/Form.php @@ -86,7 +86,7 @@ protected function _prepareForm() 'name' => 'password', 'label' => Mage::helper('adminhtml')->__('New Password'), 'title' => Mage::helper('adminhtml')->__('New Password'), - 'class' => 'input-text validate-password', + 'class' => 'input-text validate-admin-password', ) ); diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid.php index 1a8890cf09..4e3e23b411 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid.php @@ -686,7 +686,7 @@ public function addRssList($url, $label) { $this->_rssLists[] = new Varien_Object( array( - 'url' => $this->getUrl($url), + 'url' => Mage::getModel('core/url')->getUrl($url), 'label' => $label ) ); diff --git a/app/code/core/Mage/Adminhtml/Controller/Action.php b/app/code/core/Mage/Adminhtml/Controller/Action.php index b0f8a468ac..32df005e11 100644 --- a/app/code/core/Mage/Adminhtml/Controller/Action.php +++ b/app/code/core/Mage/Adminhtml/Controller/Action.php @@ -135,10 +135,10 @@ public function preDispatch() if (Mage::getSingleton('admin/session')->isLoggedIn()) { if ($this->getRequest()->isPost()) { $_isValidFormKey = $this->_validateFormKey(); - $_keyErrorMsg = 'Invalid Form Key'; + $_keyErrorMsg = Mage::helper('adminhtml')->__('Invalid Form Key. Please refresh the page.'); } elseif (Mage::getSingleton('adminhtml/url')->useSecretKey()) { $_isValidSecretKey = $this->_validateSecretKey(); - $_keyErrorMsg = 'Invalid Secret Key'; + $_keyErrorMsg = Mage::helper('adminhtml')->__('Invalid Secret Key. Please refresh the page.'); } } if (!$_isValidFormKey || !$_isValidSecretKey) { @@ -147,7 +147,7 @@ public function preDispatch() if ($this->getRequest()->getQuery('isAjax', false) || $this->getRequest()->getQuery('ajax', false)) { $this->getResponse()->setBody(Zend_Json::encode(array( 'error' => true, - 'error_msg' => Mage::helper('adminhtml')->__($_keyErrorMsg) + 'message' => $_keyErrorMsg ))); } else { $this->_redirect('*/index/index'); diff --git a/app/code/core/Mage/Adminhtml/Model/Extension/Remote/Collection.php b/app/code/core/Mage/Adminhtml/Model/Extension/Remote/Collection.php index 7a026a85f3..b1899f767b 100644 --- a/app/code/core/Mage/Adminhtml/Model/Extension/Remote/Collection.php +++ b/app/code/core/Mage/Adminhtml/Model/Extension/Remote/Collection.php @@ -8,7 +8,6 @@ protected function _fetchPackages() $pear = Varien_Pear::getInstance(); $channels = Mage::getModel('adminhtml/extension')->getKnownChannels(); - #$channels = array('var-dev.varien.com'=>1);#, 'pear.php.net'=>1); $channelData = array(); foreach ($channels as $channel=>$name) { $data = array(); diff --git a/app/code/core/Mage/Adminhtml/Model/Sales/Order/Create.php b/app/code/core/Mage/Adminhtml/Model/Sales/Order/Create.php index 279f6eb48e..c8ff052b06 100644 --- a/app/code/core/Mage/Adminhtml/Model/Sales/Order/Create.php +++ b/app/code/core/Mage/Adminhtml/Model/Sales/Order/Create.php @@ -293,9 +293,11 @@ public function initFromOrderItem(Mage_Sales_Model_Order_Item $orderItem, $qty = 'order_item' => $orderItem, 'quote_item' => $item )); + + return $item; } - return $item; + return $this; } /** diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Cache.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Cache.php new file mode 100644 index 0000000000..4f2962ed45 --- /dev/null +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Cache.php @@ -0,0 +1,50 @@ +isValueChanged()) { + Mage::app()->cleanCache($this->_cacheTags); + } + } +} diff --git a/app/code/core/Mage/Adminhtml/Model/Url.php b/app/code/core/Mage/Adminhtml/Model/Url.php index 97124f5578..e2c8123562 100644 --- a/app/code/core/Mage/Adminhtml/Model/Url.php +++ b/app/code/core/Mage/Adminhtml/Model/Url.php @@ -53,6 +53,8 @@ public function setRouteParams(array $data, $unsetOldParams=true) if (isset($data['_nosecret'])) { $this->setNoSecret(true); unset($data['_nosecret']); + } else { + $this->setNoSecret(false); } return parent::setRouteParams($data, $unsetOldParams); @@ -69,7 +71,7 @@ public function getUrl($routePath=null, $routeParams=null) { $result = parent::getUrl($routePath, $routeParams); - if (!$this->useSecretKey() || $this->getNoSecret()) { + if (!$this->useSecretKey()) { return $result; } @@ -116,7 +118,29 @@ public function getSecretKey($controller = null, $action = null) */ public function useSecretKey() { - return Mage::getStoreConfigFlag('admin/security/use_form_key'); + return Mage::getStoreConfigFlag('admin/security/use_form_key') && !$this->getNoSecret(); + } + + /** + * Enable secret key using + * + * @return Mage_Adminhtml_Model_Url + */ + public function turnOnSecretKey() + { + $this->setNoSecret(false); + return $this; + } + + /** + * Disable secret key using + * + * @return Mage_Adminhtml_Model_Url + */ + public function turnOffSecretKey() + { + $this->setNoSecret(true); + return $this; } /** diff --git a/app/code/core/Mage/Adminhtml/controllers/CustomerController.php b/app/code/core/Mage/Adminhtml/controllers/CustomerController.php index 08c5bd104c..9cc40c8746 100644 --- a/app/code/core/Mage/Adminhtml/controllers/CustomerController.php +++ b/app/code/core/Mage/Adminhtml/controllers/CustomerController.php @@ -198,6 +198,10 @@ public function saveAction() $customer->setForceConfirmed(true); } + Mage::dispatchEvent('adminhtml_customer_prepare_save', + array('customer' => $customer, 'request' => $this->getRequest()) + ); + $customer->save(); // send welcome email diff --git a/app/code/core/Mage/Adminhtml/controllers/IndexController.php b/app/code/core/Mage/Adminhtml/controllers/IndexController.php index eb33ff2faf..88d9ab2124 100644 --- a/app/code/core/Mage/Adminhtml/controllers/IndexController.php +++ b/app/code/core/Mage/Adminhtml/controllers/IndexController.php @@ -178,7 +178,7 @@ public function forgotpasswordAction () foreach ($collection as $item) { $user = Mage::getModel('admin/user')->load($item->getId()); if ($user->getId()) { - $pass = substr(md5(uniqid(rand(), true)), 0, 6); + $pass = substr(md5(uniqid(rand(), true)), 0, 7); $user->setPassword($pass); $user->save(); $user->setPlainPassword($pass); diff --git a/app/code/core/Mage/Adminhtml/controllers/Report/ProductController.php b/app/code/core/Mage/Adminhtml/controllers/Report/ProductController.php index 5ca3a5dd29..80444be061 100644 --- a/app/code/core/Mage/Adminhtml/controllers/Report/ProductController.php +++ b/app/code/core/Mage/Adminhtml/controllers/Report/ProductController.php @@ -20,16 +20,17 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com) + * @copyright Copyright (c) 2009 Irubin Consulting Inc. DBA Varien (http://www.varien.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** * Product reports admin controller * * @category Mage * @package Mage_Adminhtml - * @author Magento Core Team + * @author Magento Core Team */ class Mage_Adminhtml_Report_ProductController extends Mage_Adminhtml_Controller_Action { @@ -49,6 +50,10 @@ public function _initAction() return $this; } + /** + * Bestsellers + * + */ public function orderedAction() { $this->_initAction() @@ -60,6 +65,7 @@ public function orderedAction() /** * Export products bestsellers report to CSV format + * */ public function exportOrderedCsvAction() { @@ -72,6 +78,7 @@ public function exportOrderedCsvAction() /** * Export products bestsellers report to XML format + * */ public function exportOrderedExcelAction() { @@ -82,6 +89,51 @@ public function exportOrderedExcelAction() $this->_prepareDownloadResponse($fileName, $content); } + /** + * Sold Products Report Action + * + */ + public function soldAction() + { + $this->_initAction() + ->_setActiveMenu('report/product/sold') + ->_addBreadcrumb(Mage::helper('reports')->__('Products Ordered'), Mage::helper('reports')->__('Products Ordered')) + ->_addContent($this->getLayout()->createBlock('adminhtml/report_product_sold')) + ->renderLayout(); + } + + /** + * Export Sold Products report to CSV format action + * + */ + public function exportSoldCsvAction() + { + $fileName = 'products_ordered.csv'; + $content = $this->getLayout() + ->createBlock('adminhtml/report_product_sold_grid') + ->getCsv(); + + $this->_prepareDownloadResponse($fileName, $content); + } + + /** + * Export Sold Products report to XML format action + * + */ + public function exportSoldExcelAction() + { + $fileName = 'products_ordered.xml'; + $content = $this->getLayout() + ->createBlock('adminhtml/report_product_sold_grid') + ->getExcel($fileName); + + $this->_prepareDownloadResponse($fileName, $content); + } + + /** + * Most viewed products + * + */ public function viewedAction() { $this->_initAction() @@ -93,6 +145,7 @@ public function viewedAction() /** * Export products most viewed report to CSV format + * */ public function exportViewedCsvAction() { @@ -105,6 +158,7 @@ public function exportViewedCsvAction() /** * Export products most viewed report to XML format + * */ public function exportViewedExcelAction() { @@ -115,6 +169,10 @@ public function exportViewedExcelAction() $this->_prepareDownloadResponse($fileName, $content); } + /** + * Low stock action + * + */ public function lowstockAction() { $this->_initAction() @@ -126,6 +184,7 @@ public function lowstockAction() /** * Export low stock products report to CSV format + * */ public function exportLowstockCsvAction() { @@ -139,6 +198,7 @@ public function exportLowstockCsvAction() /** * Export low stock products report to XML format + * */ public function exportLowstockExcelAction() { @@ -150,6 +210,10 @@ public function exportLowstockExcelAction() $this->_prepareDownloadResponse($fileName, $content); } + /** + * Downloads action + * + */ public function downloadsAction() { $this->_initAction() @@ -161,6 +225,7 @@ public function downloadsAction() /** * Export products downloads report to CSV format + * */ public function exportDownloadsCsvAction() { @@ -174,6 +239,7 @@ public function exportDownloadsCsvAction() /** * Export products downloads report to XLS format + * */ public function exportDownloadsExcelAction() { @@ -185,6 +251,11 @@ public function exportDownloadsExcelAction() $this->_prepareDownloadResponse($fileName, $content); } + /** + * Check is allowed for report + * + * @return bool + */ protected function _isAllowed() { switch ($this->getRequest()->getActionName()) { @@ -194,6 +265,9 @@ protected function _isAllowed() case 'viewed': return Mage::getSingleton('admin/session')->isAllowed('report/products/viewed'); break; + case 'sold': + return Mage::getSingleton('admin/session')->isAllowed('report/products/sold'); + break; case 'lowstock': return Mage::getSingleton('admin/session')->isAllowed('report/products/lowstock'); break; diff --git a/app/code/core/Mage/Adminhtml/controllers/System/AccountController.php b/app/code/core/Mage/Adminhtml/controllers/System/AccountController.php index 17be1df430..00cd23b06a 100644 --- a/app/code/core/Mage/Adminhtml/controllers/System/AccountController.php +++ b/app/code/core/Mage/Adminhtml/controllers/System/AccountController.php @@ -59,6 +59,14 @@ public function saveAction() try { try { + $_isValid = Zend_Validate::is($user->getUsername(), 'NotEmpty') + && Zend_Validate::is($user->getFirstname(), 'NotEmpty') + && Zend_Validate::is($user->getLastname(), 'NotEmpty') + && Zend_Validate::is($user->getEmail(), 'EmailAddress'); + + if (!$_isValid) { + Mage::throwException(Mage::helper('adminhtml')->__('Error while saving account. Please check all required fields')); + } if ($user->userExists()) { Mage::throwException(Mage::helper('adminhtml')->__('User with the same User Name or Email aleady exists')); } diff --git a/app/code/core/Mage/Adminhtml/controllers/System/ConfigController.php b/app/code/core/Mage/Adminhtml/controllers/System/ConfigController.php index 8e3b280bcf..3983456e2a 100644 --- a/app/code/core/Mage/Adminhtml/controllers/System/ConfigController.php +++ b/app/code/core/Mage/Adminhtml/controllers/System/ConfigController.php @@ -321,10 +321,7 @@ protected function _saveState($configState = array()) foreach ($configState as $fieldset => $state) { $extra['configState'][$fieldset] = $state; } - $id = $adminUser->getId(); - $adminUser->setData(array('extra'=>$extra)) - ->setId($id); - $adminUser->save(); + $adminUser->saveExtra($extra); } return true; diff --git a/app/code/core/Mage/AmazonPayments/Block/Adminhtml/Shipping/Methods.php b/app/code/core/Mage/AmazonPayments/Block/Adminhtml/Shipping/Methods.php new file mode 100644 index 0000000000..2a05f6f502 --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/Block/Adminhtml/Shipping/Methods.php @@ -0,0 +1,103 @@ +setElement($element); + + $html = ''; + + return $html; + } + + protected function getShippingMethods() + { + if (!$this->hasData('shipping_methods')) { + $website = $this->getRequest()->getParam('website'); + $store = $this->getRequest()->getParam('store'); + + $storeId = null; + if (!is_null($website)) { + $storeId = Mage::getModel('core/website')->load($website, 'code')->getDefaultGroup()->getDefaultStoreId(); + } elseif (!is_null($store)) { + $storeId = Mage::getModel('core/store')->load($store, 'code')->getId(); + } + + $methods = array(); + $carriers = Mage::getSingleton('shipping/config')->getActiveCarriers($storeId); + foreach ($carriers as $carrierCode=>$carrierModel) { + if (!$carrierModel->isActive()) { + continue; + } + $carrierMethods = $carrierModel->getAllowedMethods(); + if (!$carrierMethods) { + continue; + } + $carrierTitle = Mage::getStoreConfig('carriers/'.$carrierCode.'/title', $storeId); + $methods[$carrierCode] = array( + 'title' => $carrierTitle, + 'methods' => array(), + ); + foreach ($carrierMethods as $methodCode=>$methodTitle) { + $methods[$carrierCode]['methods'][$methodCode] = array( + 'title' => '['.$carrierCode.'] '.$methodTitle, + ); + } + } + $this->setData('shipping_methods', $methods); + } + return $this->getData('shipping_methods'); + } + + protected function _getDisabled() + { + return $this->getElement()->getDisabled() ? ' disabled' : ''; + } + + protected function _getValue($key) + { + return $this->getElement()->getData('value/'.$key); + } + + protected function _getSelected($value) + { + return $this->getElement()->getData('value/method') == $value ? 'selected="selected"' : ''; + } +} \ No newline at end of file diff --git a/app/code/core/Mage/AmazonPayments/Block/Asp/Form.php b/app/code/core/Mage/AmazonPayments/Block/Asp/Form.php new file mode 100644 index 0000000000..4f0e2badd0 --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/Block/Asp/Form.php @@ -0,0 +1,44 @@ + + */ +class Mage_AmazonPayments_Block_Asp_Form extends Mage_Payment_Block_Form +{ + /** + * Varien constructor + */ + protected function _construct() + { + $this->setTemplate('amazonpayments/asp/form.phtml'); + parent::_construct(); + } +} diff --git a/app/code/core/Mage/AmazonPayments/Block/Asp/Redirect.php b/app/code/core/Mage/AmazonPayments/Block/Asp/Redirect.php new file mode 100644 index 0000000000..74f19f342a --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/Block/Asp/Redirect.php @@ -0,0 +1,55 @@ + + */ +class Mage_AmazonPayments_Block_Asp_Redirect extends Mage_Core_Block_Template +{ + /** + * Return Amazon Simple Pay payment url + * + * @return string + */ + public function getRedirectUrl() + { + return Mage::registry('amazonpayments_payment_asp')->getPayRedirectUrl(); + } + + /** + * Return pay params for current order + * + * @return array + */ + public function getRedirectParams() + { + return Mage::registry('amazonpayments_payment_asp')->getPayRedirectParams(); + } +} diff --git a/app/code/core/Mage/AmazonPayments/Block/Asp/Shortcut.php b/app/code/core/Mage/AmazonPayments/Block/Asp/Shortcut.php new file mode 100644 index 0000000000..43fb8bbbec --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/Block/Asp/Shortcut.php @@ -0,0 +1,55 @@ + + */ +class Mage_AmazonPayments_Block_Asp_Shortcut extends Mage_Core_Block_Template +{ + public function getCheckoutUrl() + { + return $this->getUrl('amazonpayments/asp/checkout'); + } + + public function getButtonImageUrl() + { + return Mage::getStoreConfig('payment/amazonpayments_asp/pay_now_button_image_url'); + } + + public function _toHtml() + { + if (Mage::getStoreConfigFlag('payment/amazonpayments_asp/active') + && Mage::getSingleton('checkout/session')->getQuote()->validateMinimumAmount()) { + return parent::_toHtml(); + } + + return ''; + } +} \ No newline at end of file diff --git a/app/code/core/Mage/AmazonPayments/Block/Cba/Form.php b/app/code/core/Mage/AmazonPayments/Block/Cba/Form.php new file mode 100644 index 0000000000..9d890244fb --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/Block/Cba/Form.php @@ -0,0 +1,46 @@ + + */ +class Mage_AmazonPayments_Block_Cba_Form extends Mage_Payment_Block_Form +{ + + /** + * Varien constructor + */ + protected function _construct() + { + $this->setTemplate('amazonpayments/cba/form.phtml'); + parent::_construct(); + } + +} diff --git a/app/code/core/Mage/AmazonPayments/Block/Cba/Redirect.php b/app/code/core/Mage/AmazonPayments/Block/Cba/Redirect.php new file mode 100644 index 0000000000..ef9da0b73d --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/Block/Cba/Redirect.php @@ -0,0 +1,69 @@ + + */ +class Mage_AmazonPayments_Block_Cba_Redirect extends Mage_Core_Block_Abstract +{ + /** + * Shopping cart form to CBA in case XML-based cart + * + * @return string + */ + protected function _toHtml() + { + $cba = Mage::getModel('amazonpayments/payment_cba'); + /* @var $cba Mage_AmazonPayments_Model_Payment_Cba */ + + $form = new Varien_Data_Form(); + $form->setAction($cba->getAmazonRedirectUrl()) + ->setId('amazonpayments_cba') + ->setName('amazonpayments_cba') + ->setMethod('POST') + ->setUseContainer(true); + + $formFields = $cba->getCheckoutXmlFormFields(); + $values = ''; + $i = 1; + foreach ($formFields as $field=>$value) { + $form->addField($field, 'hidden', array('name'=>$field, 'value'=>$value)); + $values .= ($i++ > 1) ? '&' : ''; + $values .= "{$field}={$value}"; + } + $html = ''; + $html.= $this->__('You will be redirected to Checkout by Amazon in a few seconds.'); + $html.= $form->toHtml(); + $html.= ''; + $html.= ''; + + return $html; + } +} \ No newline at end of file diff --git a/app/code/core/Mage/AmazonPayments/Block/Cba/Success.php b/app/code/core/Mage/AmazonPayments/Block/Cba/Success.php new file mode 100644 index 0000000000..f6e181f32a --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/Block/Cba/Success.php @@ -0,0 +1,50 @@ + + */ +class Mage_AmazonPayments_Block_Cba_Success extends Mage_Core_Block_Abstract +{ + /** + * Success page + * + * @return string + */ + protected function _toHtml() + { + echo "

{$this->__('Thank you for your purchase!') }

\n"; + echo $this->__('You will receive an order confirmation email with details of your order and a link to track its progress.
'); + + + return ''; + } + +} \ No newline at end of file diff --git a/app/code/core/Mage/AmazonPayments/Block/Form.php b/app/code/core/Mage/AmazonPayments/Block/Form.php new file mode 100644 index 0000000000..24bd2be700 --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/Block/Form.php @@ -0,0 +1,46 @@ + + */ +class Mage_AmazonPayments_Block_Form extends Mage_Payment_Block_Form +{ + + /** + * Varien constructor + */ + protected function _construct() + { + $this->setTemplate('amazonpayments/form.phtml'); + parent::_construct(); + } + +} diff --git a/app/code/core/Mage/AmazonPayments/Block/Link/Shortcut.php b/app/code/core/Mage/AmazonPayments/Block/Link/Shortcut.php new file mode 100644 index 0000000000..9d01cf72f0 --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/Block/Link/Shortcut.php @@ -0,0 +1,90 @@ + + */ +class Mage_AmazonPayments_Block_Link_Shortcut extends Mage_Core_Block_Template +{ + public function getCheckoutUrl() + { + #return $this->getUrl('amazonpayments/cba', array('_secure'=>true)); + return $this->getUrl('amazonpayments/cba/shortcut'); + } + + public function getImageUrl() + { + return Mage::getStoreConfig('payment/amazonpayments_cba/button_url'); + #return 'http://g-ecx.images-amazon.com/images/G/01/cba/images/buttons/btn_Chkout-orange-large.gif'; + } + + public function _toHtml() + { + if (Mage::getStoreConfigFlag('payment/amazonpayments_cba/active') + && Mage::getSingleton('checkout/session')->getQuote()->validateMinimumAmount()) { + return parent::_toHtml(); + } + + return ''; + } + + public function getCbaOneClickJsUrl() + { + if (Mage::getStoreConfigFlag('payment/amazonpayments_cba/sandbox_flag')) { + $url = 'https://images-na.ssl-images-amazon.com/images/G/01/cba/js/widget/sandbox/widget.js'; + } else { + $url = 'https://images-na.ssl-images-amazon.com/images/G/01/cba/js/widget/widget.js'; + } + return $url; + } + + public function getCbaStylesheetUrl() + { + $url = 'https://images-na.ssl-images-amazon.com/images/G/01/cba/styles/one-click.css'; + return $url; + } + + public function getCbaJquerySetupUrl() + { + $url = 'https://images-na.ssl-images-amazon.com/images/G/01/cba/js/jquery.js'; + return $url; + } + + /** + * Return true if 1-Click is enabled + * + * @return boolean + */ + public function getIsOneClickEnabled() + { + return Mage::getStoreConfigFlag('payment/amazonpayments_cba/use_oneclick'); + } + +} diff --git a/app/code/core/Mage/AmazonPayments/Helper/Data.php b/app/code/core/Mage/AmazonPayments/Helper/Data.php new file mode 100644 index 0000000000..c5a46ef205 --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/Helper/Data.php @@ -0,0 +1,47 @@ + + */ +class Mage_AmazonPayments_Helper_Data extends Mage_Core_Helper_Abstract +{ + /** + * Format amount value (2 digits after the decimal point) + * + * @param float $amount + * @return float + */ + public function formatAmount($amount) + { + return round($amount, 2); + } +} \ No newline at end of file diff --git a/app/code/core/Mage/AmazonPayments/Model/Api/Abstract.php b/app/code/core/Mage/AmazonPayments/Model/Api/Abstract.php new file mode 100644 index 0000000000..4bb052d152 --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/Model/Api/Abstract.php @@ -0,0 +1,157 @@ + + */ +abstract class Mage_AmazonPayments_Model_Api_Abstract extends Varien_Object +{ + /* + * payment actions + */ + const PAYMENT_TYPE_AUTH = 'Authorization'; + const USER_ACTION_COMMIT = 'commit'; + + /* + * signature generation algorithm name + */ + protected static $HMAC_SHA1_ALGORITHM = 'sha1'; + + /* + * payment module code + */ + protected $paymentCode; + + /** + * Set payment module code + * + * @return string + */ + public function setPaymentCode($paymentCode) + { + if(is_null($this->paymentCode)) { + $this->paymentCode = $paymentCode; + $this->setData(Mage::getStoreConfig('payment/' . $paymentCode)); + } + } + + /** + * Return payment url + * + * @return string + */ + public function getPayServiceUrl() + { + if ($this->getSandboxFlag()) { + return $this->getData('sandbox_pay_service_url'); + } + return $this->getData('pay_service_url'); + } + + /** + * Get value from the payment module config + * + * @param string $kay + * @param string $default + * @return string + */ + public function getConfigData($key, $default = false) + { + if (!$this->hasData($key)) { + $value = Mage::getStoreConfig('payment/' . $paymentCode . '/' . $key); + if (is_null($value) || false===$value) { + $value = $default; + } + $this->setData($key, $value); + } + return $this->getData($key); + } + + /** + * Check signed params + * + * @param array $params + * @return bool + */ + public function checkSignParams($params) + { + if (is_array($params) && isset($params[$this->getRequestSignatureParamName()])) { + $paramSignature = $params[$this->getRequestSignatureParamName()]; + unset($params[$this->getRequestSignatureParamName()]); + $generateSignature = $this->_getSignatureForArray($params, $this->getSecretKey()); + return $paramSignature == $generateSignature; + } + + return false; + } + + /** + * Add signature param to params array + * + * @param array $params + * @return array + */ + public function signParams($params) + { + $signature = $this->_getSignatureForArray($params, $this->getSecretKey()); + $params[$this->getRequestSignatureParamName()] = $signature; + return $params; + } + + /** + * Return signature for array + * + * @param array $array + * @param string $secretKey + * @return array + */ + protected function _getSignatureForArray($array, $secretKey) + { + uksort($array, 'strcasecmp'); + $tmpString = ''; + foreach ($array as $paramName => $paramValue) { + $tmpString = $tmpString . $paramName . $paramValue; + } + return $this->_getSignatureForString($tmpString, $secretKey); + } + + /** + * Return signature for string + * + * @param string $string + * @param string $secretKey + * @return string + */ + protected function _getSignatureForString($string, $secretKey) + { + $rawHmac = hash_hmac(self::$HMAC_SHA1_ALGORITHM, $string, $secretKey, true); + return base64_encode($rawHmac); + } +} diff --git a/app/code/core/Mage/AmazonPayments/Model/Api/Asp.php b/app/code/core/Mage/AmazonPayments/Model/Api/Asp.php new file mode 100644 index 0000000000..3de6ae302e --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/Model/Api/Asp.php @@ -0,0 +1,217 @@ + + */ +class Mage_AmazonPayments_Model_Api_Asp extends Mage_AmazonPayments_Model_Api_Asp_Abstract +{ + /** + * collect shipping address to IPN notification request + */ + protected $_collectShippingAddress = 0; + + /** + * IPN notification request model path + */ + protected $_ipnRequest = 'amazonpayments/api_asp_ipn_request'; + + /** + * FPS model path + */ + protected $_fpsModel = 'amazonpayments/api_asp_fps'; + + /** + * Get singleton with AmazonPayments ASP API FPS Model + * + * @return Mage_AmazonPayments_Model_Api_Asp_Fps + */ + protected function _getFps() + { + return Mage::getSingleton($this->_fpsModel)->setStoreId($this->getStoreId()); + } + + /** + * Get singleton with AmazonPayments ASP IPN notification request Model + * + * @return Mage_AmazonPayments_Model_Api_Asp_Ipn_Request + */ + protected function _getIpnRequest() + { + return Mage::getSingleton($this->_ipnRequest); + } + + + /** + * Return Amazon Simple Pay payment url + * + * @return string + */ + public function getPayUrl () + { + if ($this->_isSandbox()) { + return $this->_getConfig('pay_service_url_sandbox'); + } + return $this->_getConfig('pay_service_url'); + } + + /** + * Return Amazon Simple Pay payment params + * + * @param string $referenceId + * @param string $amountValue + * @param string $currencyCode + * @param string $abandonUrl + * @param string $returnUrl + * @param string $ipnUrl + * @return array + */ + public function getPayParams($referenceId, $amountValue, $currencyCode, $abandonUrl, $returnUrl, $ipnUrl) + { + $amount = Mage::getSingleton('amazonpayments/api_asp_amount') + ->setValue($amountValue) + ->setCurrencyCode($currencyCode); + + $requestParams = array(); + $requestParams['referenceId'] = $referenceId; + $requestParams['amount'] = $amount->toString(); + $requestParams['description'] = $this->_getConfig('pay_description'); + + $requestParams['accessKey'] = $this->_getConfig('access_key'); + $requestParams['processImmediate'] = $this->_getConfig('pay_process_immediate'); + $requestParams['immediateReturn'] = $this->_getConfig('pay_immediate_return'); + $requestParams['collectShippingAddress'] = $this->_collectShippingAddress; + $requestParams['abandonUrl'] = $abandonUrl; + $requestParams['returnUrl'] = $returnUrl; + $requestParams['ipnUrl'] = $ipnUrl; + + $signature = $this->_getSignatureForArray($requestParams, $this->_getConfig('secret_key')); + $requestParams['signature'] = $signature; + + return $requestParams; + } + + /** + * process notification request + * + * @param array $requestParams + * @return Mage_AmazonPayments_Model_Api_Asp_Ipn_Request + */ + public function processNotification($requestParams) + { + $requestSignature = false; + + if (isset($requestParams['signature'])) { + $requestSignature = $requestParams['signature']; + unset($requestParams['signature']); + } + + $originalSignature = $this->_getSignatureForArray($requestParams, $this->_getConfig('secret_key')); + if ($requestSignature != $originalSignature) { + Mage::throwException(Mage::helper('amazonpayments')->__('Request signed an incorrect or missing signature')); + } + + $ipnRequest = $this->_getIpnRequest(); + + if(!$ipnRequest->init($requestParams)) { + Mage::throwException(Mage::helper('amazonpayments')->__('Request is not a valid IPN request')); + } + + return $ipnRequest; + } + + /** + * cancel payment through FPS api + * + * @param string $transactionId + * @return Mage_AmazonPayments_Model_Api_Asp_Fps_Response_Abstract + */ + public function cancel($transactionId) + { + $fps = $this->_getFps(); + + $request = $fps->getRequest(Mage_AmazonPayments_Model_Api_Asp_Fps::ACTION_CODE_CANCEL) + ->setTransactionId($transactionId) + ->setDescription($this->_getConfig('cancel_description')); + + $response = $fps->process($request); + return $response; + } + + /** + * capture payment through FPS api + * + * @param string $transactionId + * @param string $amount + * @param string $currencyCode + * @return Mage_AmazonPayments_Model_Api_Asp_Fps_Response_Abstract + */ + public function capture($transactionId, $amount, $currencyCode) + { + $fps = $this->_getFps(); + $amount = $this->_getAmount() + ->setValue($amount) + ->setCurrencyCode($currencyCode); + + $request = $fps->getRequest(Mage_AmazonPayments_Model_Api_Asp_Fps::ACTION_CODE_SETTLE) + ->setTransactionId($transactionId) + ->setAmount($amount); + + $response = $fps->process($request); + return $response; + } + + /** + * capture payment through FPS api + * + * @param string $transactionId + * @param string $amount + * @param string $currencyCode + * @param string $referenceId + * @return Mage_AmazonPayments_Model_Api_Asp_Fps_Response_Abstract + */ + public function refund($transactionId, $amount, $currencyCode, $referenceId) + { + $fps = $this->_getFps(); + + $amount = $this->_getAmount() + ->setValue($amount) + ->setCurrencyCode($currencyCode); + + $request = $fps->getRequest(Mage_AmazonPayments_Model_Api_Asp_Fps::ACTION_CODE_REFUND) + ->setTransactionId($transactionId) + ->setReferenceId($referenceId) + ->setAmount($amount) + ->setDescription($this->_getConfig('refund_description')); + + $response = $fps->process($request); + return $response; + } +} diff --git a/app/code/core/Mage/AmazonPayments/Model/Api/Asp/Abstract.php b/app/code/core/Mage/AmazonPayments/Model/Api/Asp/Abstract.php new file mode 100644 index 0000000000..d9ce3fcfe7 --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/Model/Api/Asp/Abstract.php @@ -0,0 +1,109 @@ + + */ +class Mage_AmazonPayments_Model_Api_Asp_Abstract extends Mage_AmazonPayments_Model_Api_Abstract +{ + /** + * payment actions + */ + const PAY_ACTION_SETTLE = 0; + const PAY_ACTION_SETTLE_CAPTURE = 1; + + /** + * rewrited for Mage_AmazonPayments_Model_Api_Abstract + */ + protected $paymentCode = 'amazonpayments_asp'; + + /** + * Amount model path + */ + protected $_amountModel = 'amazonpayments/api_asp_amount'; + + /** + * Store id for current operation + */ + protected $_storeId = null; + + /** + * Set store id for current operation + * + * @param $id string + * @return Mage_AmazonPayments_Model_Api_Asp_Abstract + */ + public function setStoreId($id) + { + $this->_storeId = $id; + return $this; + } + + /** + * Get store id for current operation + * + * @return string + */ + public function getStoreId() + { + return $this->_storeId; + } + + /** + * Get singleton with AmazonPayments ASP Amount Model + * + * @return Mage_AmazonPayments_Model_Api_Asp_Amount + */ + protected function _getAmount() + { + return Mage::getSingleton($this->_amountModel); + } + + /** + * Return sandbox mode flag + * + * @return bool + */ + protected function _isSandbox() + { + return $this->_getConfig('is_sandbox'); + } + + /** + * Get value from the module config + * + * @param string $path + * @return string + */ + protected function _getConfig($path) + { + return Mage::getStoreConfig('payment/' . $this->paymentCode . '/' . $path, $this->getStoreId()); + } +} diff --git a/app/code/core/Mage/AmazonPayments/Model/Api/Asp/Amount.php b/app/code/core/Mage/AmazonPayments/Model/Api/Asp/Amount.php new file mode 100644 index 0000000000..54abce4ffd --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/Model/Api/Asp/Amount.php @@ -0,0 +1,139 @@ + + */ +class Mage_AmazonPayments_Model_Api_Asp_Amount +{ + /* + * Amount value + */ + private $_value; + + /* + * Amount currency code + */ + private $_currencyCode; + + /* + * Template union amount + */ + protected $_amountStringTemplate = "/^([A-Z]{3})\s([0-9]{1,}|[0-9]{1,}[.][0-9]{1,})$/"; + + /* + * Template amount value + */ + protected $_valueStringTemplate = "/^([0-9]{1,}|[0-9]{1,}[.][0-9]{1,})$/"; + + /* + * Template amount currency code + */ + protected $_currencyCodeStringTemplate = "/^([A-Z]{3})$/"; + + /** + * Init object + * + * @param string $amount - union amount + * @return Mage_AmazonPayments_Model_Api_Asp_Amount + */ + public function init($amount) + { + $tmpArr = array(); + if (!preg_match($this->_amountStringTemplate, $amount, $tmpArr)) { + return false; + } + $this->_value = $tmpArr[2]; + $this->_currencyCode = $tmpArr[1]; + return $this; + } + + /** + * Set amount value + * + * @param string $value + * @return Mage_AmazonPayments_Model_Api_Asp_Amount + */ + public function setValue($value) + { + $tmpArr = array(); + if (!preg_match($this->_valueStringTemplate, $value, $tmpArr)) { + return false; + } + $this->_value = $tmpArr[1]; + return $this; + } + + /** + * Set amount currency code + * + * @param string $currencyCode + * @return Mage_AmazonPayments_Model_Api_Asp_Amount + */ + public function setCurrencyCode($currencyCode) + { + $tmpArr = array(); + if (!preg_match($this->_currencyCodeStringTemplate, $currencyCode, $tmpArr)) { + return false; + } + $this->_currencyCode = $tmpArr[1]; + return $this; + } + + /** + * Get amount value + * + * @return string + */ + public function getValue() + { + return $this->_value; + } + + /** + * Get amount currency code + * + * @return string + */ + public function getCurrencyCode() + { + return $this->_currencyCode; + } + + /** + * Return union amount string + * + * @return string + */ + public function toString() + { + return $this->getCurrencyCode() . ' ' . $this->getValue(); + } +} diff --git a/app/code/core/Mage/AmazonPayments/Model/Api/Asp/Fps.php b/app/code/core/Mage/AmazonPayments/Model/Api/Asp/Fps.php new file mode 100644 index 0000000000..22aaf170df --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/Model/Api/Asp/Fps.php @@ -0,0 +1,217 @@ + + */ +class Mage_AmazonPayments_Model_Api_Asp_Fps extends Mage_AmazonPayments_Model_Api_Asp_Abstract +{ + /* + * Amazon FPS version protocol + */ + const SERVICE_VERSION = '2008-09-17'; + + /* + * FPS action codes + */ + const ACTION_CODE_CANCEL = 'Cancel'; + const ACTION_CODE_SETTLE = 'Settle'; + const ACTION_CODE_REFUND = 'Refund'; + + /* + * Exeption identifiers + */ + const EXCEPTION_INVALID_ACTION_CODE = 10031; + const EXCEPTION_INVALID_REQUEST = 10031; + const EXCEPTION_INVALID_RESPONSE = 10032; + + /** + * Return FPS request model + * + * @param string $actionCode + * @return Mage_AmazonPayments_Model_Api_Asp_Fps_Request_Abstract + */ + public function getRequest($actionCode) + { + switch ($actionCode) { + case self::ACTION_CODE_CANCEL: + $requestModelPath = 'amazonpayments/api_asp_fps_request_cancel'; + break; + case self::ACTION_CODE_SETTLE: + $requestModelPath = 'amazonpayments/api_asp_fps_request_settle'; + break; + case self::ACTION_CODE_REFUND: + $requestModelPath = 'amazonpayments/api_asp_fps_request_refund'; + break; + default: $this->_throwExeptionInvalidActionCode(); + } + + return Mage::getSingleton($requestModelPath)->init($actionCode); + } + + /** + * Return FPS response model for response body + * + * @param string $requestActionCode + * @param string $responseBody + * @return Mage_AmazonPayments_Model_Api_Asp_Fps_Response_Abstract + */ + protected function _getResponse($requestActionCode, $responseBody) + { + switch ($requestActionCode) { + case self::ACTION_CODE_CANCEL: + $responseModelPath = 'amazonpayments/api_asp_fps_response_cancel'; + break; + case self::ACTION_CODE_SETTLE: + $responseModelPath = 'amazonpayments/api_asp_fps_response_settle'; + break; + case self::ACTION_CODE_REFUND: + $responseModelPath = 'amazonpayments/api_asp_fps_response_refund'; + break; + default: $this->_throwExeptionInvalidActionCode(); + } + + $actionResponse = Mage::getSingleton($responseModelPath); + if ($actionResponse->init($responseBody)) { + return $actionResponse; + } + + $errorResponse = Mage::getSingleton('amazonpayments/api_asp_fps_response_error'); + if ($errorResponse->init($responseBody)) { + return $errorResponse; + } + + throw new Exception( + Mage::helper('amazonpayments')->__('Response body is not valid FPS respons'), + self::EXCEPTION_INVALID_RESPONSE + ); + } + + /** + * Process FPS request + * + * @param object Mage_AmazonPayments_Model_Api_Asp_Fps_Request_Abstract $request + * @return Mage_AmazonPayments_Model_Api_Asp_Fps_Response_Abstract + */ + public function process($request) + { + if (!$request->isValid()) { + throw new Exception( + Mage::helper('amazonpayments')->__('Invalid request'), + self::EXCEPTION_INVALID_REQUEST + ); + } + + $request = $this->_addRequiredParameters($request); + $request = $this->_signRequest($request); + + $responseBody = $this->_call($this->_getServiceUrl(), $request->getData()); + return $this->_getResponse($request->getActionCode(), $responseBody); + } + + /** + * Return Amazon FPS service url + * + * @return string + */ + protected function _getServiceUrl() + { + if ($this->_isSandbox()) { + return $this->_getConfig('fps_service_url_sandbox'); + } + return $this->_getConfig('fps_service_url'); + } + + /** + * Add required params to FPS request + * + * @param object Mage_AmazonPayments_Model_Api_Asp_Fps_Request_Abstract + * @return Mage_AmazonPayments_Model_Api_Asp_Fps_Request_Abstract + */ + protected function _addRequiredParameters($request) + { + return $request->setData('AWSAccessKeyId', $this->_getConfig('access_key')) + ->setData('Timestamp', gmdate("Y-m-d\TH:i:s.\\0\\0\\0\\Z", time())) + ->setData('Version', self::SERVICE_VERSION) + ->setData('SignatureVersion', '1'); + } + + /** + * Add signature param to FPS request + * + * @param object Mage_AmazonPayments_Model_Api_Asp_Fps_Request_Abstract + * @return Mage_AmazonPayments_Model_Api_Asp_Fps_Request_Abstract + */ + protected function _signRequest($request) + { + $signature = $this->_getSignatureForArray($request->getData(), $this->_getConfig('secret_key')); + return $request->setData('Signature', $signature); + } + + /** + * Send request to Amazon FPS service and return response body + * + * @param string $serviceUrl + * @param array $params + * @return Varien_Simplexml_Element + */ + protected function _call($serviceUrl, $params) + { + $tmpArray = array(); + foreach ($params as $kay => $value) { + $tmpArray[] = $kay . '=' . urlencode($value); + } + $requestBody = implode('&', $tmpArray); + + $http = new Varien_Http_Adapter_Curl(); + $http->setConfig(array('timeout' => 30)); + $http->write(Zend_Http_Client::POST, $serviceUrl, '1.1', array(), $requestBody); + + $responseBody = $http->read(); + $responseBody = preg_split('/^\r?$/m', $responseBody, 2); + $responseBody = trim($responseBody[1]); + + $responseBody = new Varien_Simplexml_Element($responseBody); + + $http->close(); + return $responseBody; + } + + /** + * Throw exeption: Invalid action code + */ + protected function _throwExeptionInvalidActionCode() + { + throw new Exception( + Mage::helper('amazonpayments')->__('Invalid action code'), + self::EXCEPTION_INVALID_ACTION_CODE + ); + } +} diff --git a/app/code/core/Mage/AmazonPayments/Model/Api/Asp/Fps/Request/Abstract.php b/app/code/core/Mage/AmazonPayments/Model/Api/Asp/Fps/Request/Abstract.php new file mode 100644 index 0000000000..4b11f00438 --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/Model/Api/Asp/Fps/Request/Abstract.php @@ -0,0 +1,86 @@ + + */ +class Mage_AmazonPayments_Model_Api_Asp_Fps_Request_Abstract extends Varien_Object +{ + /* + * Request action code + */ + private $_actionCode; + + /** + * Init object + * + * @param string $actionCode + * @return Mage_AmazonPayments_Model_Api_Asp_Fps_Request_Abstract + */ + public function init($actionCode) + { + if (is_null($this->_actionCode)) { + $this->_actionCode = $actionCode; + $this->initDefaultParams(); + } + return $this; + } + + /** + * Init default request params + */ + protected function initDefaultParams() + { + $this->setData('Action', $this->getActionCode()); + } + + /** + * Return request action code + * + * @return string + */ + public function getActionCode() + { + return $this->_actionCode; + } + + /** + * Validation request params + * + * @return bool + */ + public function isValid() + { + if (!$this->getData('Action')) { + return false; + } + return true; + } +} diff --git a/app/code/core/Mage/AmazonPayments/Model/Api/Asp/Fps/Request/Cancel.php b/app/code/core/Mage/AmazonPayments/Model/Api/Asp/Fps/Request/Cancel.php new file mode 100644 index 0000000000..17773aa3ac --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/Model/Api/Asp/Fps/Request/Cancel.php @@ -0,0 +1,68 @@ + + */ +class Mage_AmazonPayments_Model_Api_Asp_Fps_Request_Cancel extends Mage_AmazonPayments_Model_Api_Asp_Fps_Request_Abstract +{ + /** + * rewrited for Mage_AmazonPayments_Model_Api_Asp_Fps_Request_Abstract + */ + public function isValid() + { + if (!$this->getData('TransactionId')) { + return false; + } + return parent::isValid(); + } + + /** + * Set request TransactionId + * + * @param string $transactionId + * @return Mage_AmazonPayments_Model_Api_Asp_Fps_Request_Cancel + */ + public function setTransactionId($transactionId) + { + return $this->setData('TransactionId', $transactionId); + } + + /** + * Set request Description + * + * @param string $description + * @return Mage_AmazonPayments_Model_Api_Asp_Fps_Request_Cancel + */ + public function setDescription($description) + { + return $this->setData('Description', $description); + } +} diff --git a/app/code/core/Mage/AmazonPayments/Model/Api/Asp/Fps/Request/Refund.php b/app/code/core/Mage/AmazonPayments/Model/Api/Asp/Fps/Request/Refund.php new file mode 100644 index 0000000000..0e4013ad32 --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/Model/Api/Asp/Fps/Request/Refund.php @@ -0,0 +1,92 @@ + + */ +class Mage_AmazonPayments_Model_Api_Asp_Fps_Request_Refund extends Mage_AmazonPayments_Model_Api_Asp_Fps_Request_Abstract +{ + /** + * rewrited for Mage_AmazonPayments_Model_Api_Asp_Fps_Request_Abstract + */ + public function isValid() + { + if (!$this->getData('TransactionId') || + !$this->getData('CallerReference')) { + return false; + } + return parent::isValid(); + } + + /** + * Set request transactionId + * + * @param string $transactionId + * @return Mage_AmazonPayments_Model_Api_Asp_Fps_Request_Refund + */ + public function setTransactionId($transactionId) + { + return $this->setData('TransactionId', $transactionId); + } + + /** + * Set request referenceId + * + * @param string $referenceId + * @return Mage_AmazonPayments_Model_Api_Asp_Fps_Request_Refund + */ + public function setReferenceId($referenceId) + { + return $this->setData('CallerReference', $referenceId); + } + + /** + * Set request description + * + * @param string $description + * @return Mage_AmazonPayments_Model_Api_Asp_Fps_Request_Refund + */ + public function setDescription($description) + { + return $this->setData('CallerDescription', $description); + } + + /** + * Set request amount + * + * @param Mage_AmazonPayments_Model_Api_Asp_Amount $amount + * @return Mage_AmazonPayments_Model_Api_Asp_Fps_Request_Refund + */ + public function setAmount($amount) + { + return $this->setData('RefundAmount.Value', $amount->getValue()) + ->setData('RefundAmount.CurrencyCode', $amount->getCurrencyCode()); + } +} diff --git a/app/code/core/Mage/AmazonPayments/Model/Api/Asp/Fps/Request/Settle.php b/app/code/core/Mage/AmazonPayments/Model/Api/Asp/Fps/Request/Settle.php new file mode 100644 index 0000000000..a58825fdcd --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/Model/Api/Asp/Fps/Request/Settle.php @@ -0,0 +1,69 @@ + + */ +class Mage_AmazonPayments_Model_Api_Asp_Fps_Request_Settle extends Mage_AmazonPayments_Model_Api_Asp_Fps_Request_Abstract +{ + /** + * rewrited for Mage_AmazonPayments_Model_Api_Asp_Fps_Request_Abstract + */ + public function isValid() + { + if (!$this->getData('ReserveTransactionId')) { + return false; + } + return parent::isValid(); + } + + /** + * Set request transactionId + * + * @param string $transactionId + * @return Mage_AmazonPayments_Model_Api_Asp_Fps_Request_Settle + */ + public function setTransactionId($transactionId) + { + return $this->setData('ReserveTransactionId', $transactionId); + } + + /** + * Set request amount + * + * @param Mage_AmazonPayments_Model_Api_Asp_Amount $amount + * @return Mage_AmazonPayments_Model_Api_Asp_Fps_Request_Settle + */ + public function setAmount($amount) + { + return $this->setData('TransactionAmount.Value', $amount->getValue()) + ->setData('TransactionAmount.CurrencyCode', $amount->getCurrencyCode()); + } +} diff --git a/app/code/core/Mage/AmazonPayments/Model/Api/Asp/Fps/Response/Abstract.php b/app/code/core/Mage/AmazonPayments/Model/Api/Asp/Fps/Response/Abstract.php new file mode 100644 index 0000000000..83db36eae7 --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/Model/Api/Asp/Fps/Response/Abstract.php @@ -0,0 +1,107 @@ + + */ +class Mage_AmazonPayments_Model_Api_Asp_Fps_Response_Abstract extends Varien_Object +{ + /* + * Response statuses + */ + const STATUS_CANCELLED = 'Cancelled'; + const STATUS_FAILURE = 'Failure'; + const STATUS_PENDING = 'Pending'; + const STATUS_RESERVED = 'Reserved'; + const STATUS_SUCCESS = 'Success'; + const STATUS_ERROR = 'Error'; + + /** + * Init object + * + * @param Varien_Simplexml_Element $responseBody + * @return Mage_AmazonPayments_Model_Api_Asp_Fps_Response_Abstract + */ + public function init($responseBody) + { + if (!$this->parse($responseBody)) { + return false; + } + return $this; + } + + /** + * Parse response body + * + * @param Varien_Simplexml_Element $responseBody + * @return bool + */ + protected function parse($responseBody) + { + $responseId = (string)$responseBody->ResponseMetadata->RequestId; + if($responseId == '') { + return false; + } + $this->setData('Id', $responseId); + + if (!$status = $this->getData('Status')) { + return false; + } + if ($status != self::STATUS_CANCELLED && + $status != self::STATUS_FAILURE && + $status != self::STATUS_PENDING && + $status != self::STATUS_RESERVED && + $status != self::STATUS_SUCCESS) { + return false; + } + return true; + } + + /** + * Return status of respons + * + * @return string + */ + public function getStatus() + { + return $this->getData('Status'); + } + + /** + * Return response ID + * + * @return string + */ + public function getId() + { + return $this->getData('Id'); + } + +} diff --git a/app/code/core/Mage/AmazonPayments/Model/Api/Asp/Fps/Response/Cancel.php b/app/code/core/Mage/AmazonPayments/Model/Api/Asp/Fps/Response/Cancel.php new file mode 100644 index 0000000000..fcdbb4190e --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/Model/Api/Asp/Fps/Response/Cancel.php @@ -0,0 +1,67 @@ + + */ +class Mage_AmazonPayments_Model_Api_Asp_Fps_Response_Cancel extends Mage_AmazonPayments_Model_Api_Asp_Fps_Response_Abstract +{ + /** + * rewrited for Mage_AmazonPayments_Model_Api_Asp_Fps_Response_Abstract + */ + protected function parse($responseBody) + { + if ($responseBody->getName() != 'CancelResponse') { + return false; + } + + $transactionId = (string)$responseBody->CancelResult->TransactionId; + $transactionStatus = (string)$responseBody->CancelResult->TransactionStatus; + + if($transactionId == '' || $transactionStatus == '') { + return false; + } + + $this->setData('TransactionId', $transactionId); + $this->setData('Status', $transactionStatus); + + return parent::parse($responseBody); + } + + /** + * Return response TransactionId + * + * @return string + */ + public function getTransactionId() + { + return $this->getData('TransactionId'); + } +} diff --git a/app/code/core/Mage/AmazonPayments/Model/Api/Asp/Fps/Response/Error.php b/app/code/core/Mage/AmazonPayments/Model/Api/Asp/Fps/Response/Error.php new file mode 100644 index 0000000000..3bf5895f86 --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/Model/Api/Asp/Fps/Response/Error.php @@ -0,0 +1,80 @@ + + */ +class Mage_AmazonPayments_Model_Api_Asp_Fps_Response_Error extends Mage_AmazonPayments_Model_Api_Asp_Fps_Response_Abstract +{ + /** + * rewrited for Mage_AmazonPayments_Model_Api_Asp_Fps_Response_Abstract + */ + protected function parse($responseBody) + { + if ($responseBody->getName() != 'Response') { + return false; + } + + $code = (string)$responseBody->Errors->Error->Code; + $message = (string)$responseBody->Errors->Error->Message; + $responseId = (string)$responseBody->RequestID; + + if($code == '' || $message == '' || $responseId == '') { + return false; + } + + $this->setData('Id', $responseId); + $this->setData('Status', parent::STATUS_ERROR); + $this->setData('Code', $code); + $this->setData('Message', $message); + return true; + } + + /** + * Return response Code + * + * @return string + */ + public function getCode() + { + return $this->getData('Code'); + } + + /** + * Return response Message + * + * @return string + */ + public function getMessage() + { + return $this->getData('Message'); + } + +} diff --git a/app/code/core/Mage/AmazonPayments/Model/Api/Asp/Fps/Response/Refund.php b/app/code/core/Mage/AmazonPayments/Model/Api/Asp/Fps/Response/Refund.php new file mode 100644 index 0000000000..e6fc48ae72 --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/Model/Api/Asp/Fps/Response/Refund.php @@ -0,0 +1,67 @@ + + */ +class Mage_AmazonPayments_Model_Api_Asp_Fps_Response_Refund extends Mage_AmazonPayments_Model_Api_Asp_Fps_Response_Abstract +{ + /** + * rewrited for Mage_AmazonPayments_Model_Api_Asp_Fps_Response_Abstract + */ + protected function parse($responseBody) + { + if ($responseBody->getName() != 'RefundResponse') { + return false; + } + + $transactionId = (string)$responseBody->RefundResult->TransactionId; + $transactionStatus = (string)$responseBody->RefundResult->TransactionStatus; + + if($transactionId == '' || $transactionStatus == '') { + return false; + } + + $this->setData('TransactionId', $transactionId); + $this->setData('Status', $transactionStatus); + + return parent::parse($responseBody); + } + + /** + * Return response TransactionId + * + * @return string + */ + public function getTransactionId() + { + return $this->getData('TransactionId'); + } +} diff --git a/app/code/core/Mage/AmazonPayments/Model/Api/Asp/Fps/Response/Settle.php b/app/code/core/Mage/AmazonPayments/Model/Api/Asp/Fps/Response/Settle.php new file mode 100644 index 0000000000..25b9085ebc --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/Model/Api/Asp/Fps/Response/Settle.php @@ -0,0 +1,68 @@ + + */ +class Mage_AmazonPayments_Model_Api_Asp_Fps_Response_Settle extends Mage_AmazonPayments_Model_Api_Asp_Fps_Response_Abstract +{ + /** + * rewrited for Mage_AmazonPayments_Model_Api_Asp_Fps_Response_Abstract + */ + protected function parse($responseBody) + { + if ($responseBody->getName() != 'SettleResponse') { + return false; + } + + $transactionId = (string)$responseBody->SettleResult->TransactionId; + $transactionStatus = (string)$responseBody->SettleResult->TransactionStatus; + + if($transactionId == '' || $transactionStatus == '') { + return false; + } + + $this->setData('TransactionId', $transactionId); + $this->setData('Status', $transactionStatus); + + return parent::parse($responseBody); + } + + /** + * Return response TransactionId + * + * @return string + */ + public function getTransactionId() + { + return $this->getData('TransactionId'); + } + +} diff --git a/app/code/core/Mage/AmazonPayments/Model/Api/Asp/Ipn/Request.php b/app/code/core/Mage/AmazonPayments/Model/Api/Asp/Ipn/Request.php new file mode 100644 index 0000000000..880846ca65 --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/Model/Api/Asp/Ipn/Request.php @@ -0,0 +1,215 @@ + + */ +class Mage_AmazonPayments_Model_Api_Asp_Ipn_Request extends Varien_Object +{ + /* + * Status request + */ + const STATUS_CANCEL_CUSTOMER = 'A'; + const STATUS_CANCEL_TRANSACTION = 'CANCELLED'; + const STATUS_RESERVE_SUCCESSFUL = 'PR'; + const STATUS_PAYMENT_INITIATED = 'PI'; + const STATUS_PAYMENT_SUCCESSFUL = 'PS'; + const STATUS_PAYMENT_FAILED = 'PF'; + const STATUS_REFUND_SUCCESSFUL = 'RS'; + const STATUS_REFUND_FAILED = 'RF'; + const STATUS_SYSTEM_ERROR = 'SE'; + + /* + * Request params + */ + private $requestParams; + + /** + * Init object + * + * @param array $requestParams + * @return Mage_AmazonPayments_Model_Api_Asp_Ipn_Request + */ + public function init($requestParams) + { + if (!$this->_validateRequestParams($requestParams)) { + return false; + } + $this->requestParams = $requestParams; + $this->_setRequestParamsToData($this->_convertRequestParams($requestParams)); + return $this; + } + + /** + * Validate request params + * + * @param array $requestParams + * @return bool + */ + private function _validateRequestParams($requestParams) + { + if (!isset($requestParams['transactionAmount']) || + !isset($requestParams['transactionDate']) || + !isset($requestParams['status'])) { + return false; + } + + $statusCode = $requestParams['status']; + if ($statusCode != self::STATUS_CANCEL_CUSTOMER && + $statusCode != self::STATUS_CANCEL_TRANSACTION && + $statusCode != self::STATUS_RESERVE_SUCCESSFUL && + $statusCode != self::STATUS_PAYMENT_INITIATED && + $statusCode != self::STATUS_PAYMENT_SUCCESSFUL && + $statusCode != self::STATUS_PAYMENT_FAILED && + $statusCode != self::STATUS_REFUND_SUCCESSFUL && + $statusCode != self::STATUS_REFUND_FAILED && + $statusCode != self::STATUS_SYSTEM_ERROR) { + return false; + } + + if ($statusCode != self::STATUS_CANCEL_TRANSACTION && + !isset($requestParams['referenceId'])){ + return false; + } + + if (($statusCode == self::STATUS_RESERVE_SUCCESSFUL || + $statusCode == self::STATUS_PAYMENT_SUCCESSFUL || + $statusCode == self::STATUS_REFUND_SUCCESSFUL) && + !isset($requestParams['transactionId'])) { + return false; + } + + if (!$this->_convertAmount($requestParams['transactionAmount'])) { + return false; + } + + if ($requestParams['status'] == self::STATUS_REFUND_SUCCESSFUL || + $requestParams['status'] == self::STATUS_REFUND_FAILED) { + if (!$this->_convertReferenceId($requestParams['referenceId'])) { + return false; + } + } + + return true; + } + + /** + * convert request params + * + * @param array $requestParams + * @return array + */ + private function _convertRequestParams($requestParams) + { + $_tmpResultArray = $this->_convertAmount($requestParams['transactionAmount']); + unset($requestParams['transactionAmount']); + $requestParams = array_merge($requestParams, $_tmpResultArray); + + if ($requestParams['status'] == self::STATUS_REFUND_SUCCESSFUL || + $requestParams['status'] == self::STATUS_REFUND_FAILED) { + $requestParams['referenceId'] = $this->_convertReferenceId($requestParams['referenceId']); + } + + $requestParams['transactionDate'] = $this->_convertTransactionDate($requestParams['transactionDate']); + + return $requestParams; + } + + + /** + * convert union amount string to amount value and amount currency code + * + * @param string $requestAmount + * @return Mage_AmazonPayments_Model_Api_Asp_Amount + */ + private function _convertAmount($requestAmount) + { + $amount = Mage::getSingleton('amazonpayments/api_asp_amount'); + if (!$amount->init($requestAmount)) { + return false; + } + + $resultArray = array(); + $resultArray['amount'] = $amount->getValue(); + $resultArray['currencyCode'] = $amount->getCurrencyCode(); + return $resultArray; + } + + /** + * convert peferenceId request param + * + * @param string $referenceId + * @return string + */ + private function _convertReferenceId($referenceId) + { + $tmpArr = array(); + if (!preg_match("/^Refund\sfor\s([0-9]{9})$/", $referenceId, $tmpArr)) { + return false; + } + return $tmpArr[1]; + } + + /** + * convert TransactionDate request param + * + * @param string $transactionDate + * @return string + */ + private function _convertTransactionDate($transactionDate) + { + return Mage::app()->getLocale()->date($transactionDate); + } + + /** + * set request params to object date + * + * @param array $requestParams + */ + private function _setRequestParamsToData($requestParams) + { + foreach ($requestParams as $kay => $value) { + $setMethodName = 'set' . ucfirst($kay); + $this->$setMethodName($value); + } + } + + /** + * rewrited for Varien_Object + */ + public function toString($format='') + { + $resultString = ''; + foreach($this->getData() as $kay => $value){ + $resultString .= "[$kay] = $value
"; + } + return $resultString; + } +} diff --git a/app/code/core/Mage/AmazonPayments/Model/Api/Cba.php b/app/code/core/Mage/AmazonPayments/Model/Api/Cba.php new file mode 100644 index 0000000000..98872eff78 --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/Model/Api/Cba.php @@ -0,0 +1,971 @@ + + */ +class Mage_AmazonPayments_Model_Api_Cba extends Mage_AmazonPayments_Model_Api_Abstract +{ + protected static $HMAC_SHA1_ALGORITHM = "sha1"; + protected $_paymentCode = 'amazonpayments_cba'; + + protected $_carriers; + protected $_address; + + const STANDARD_SHIPMENT_RATE = 'Standard'; + const EXPEDITED_SHIPMENT_RATE = 'Expedited'; + const ONEDAY_SHIPMENT_RATE = 'OneDay'; + const TWODAY_SHIPMENT_RATE = 'TwoDay'; + + protected $_shippingRates = array( + self::STANDARD_SHIPMENT_RATE, + self::EXPEDITED_SHIPMENT_RATE, + self::ONEDAY_SHIPMENT_RATE, + self::TWODAY_SHIPMENT_RATE, + ); + + protected $_configShippingRates = null; + + /** + * Return Merchant Id from config + * + * @return string + */ + public function getMerchantId() + { + return Mage::getStoreConfig('payment/amazonpayments_cba/merchant_id'); + } + + /** + * Return action url for CBA Cart form to Amazon + * + * @return unknown + */ + public function getAmazonRedirectUrl() + { + #$_url = $this->getCbaPaymentUrl(); + $_url = $this->getPayServiceUrl(); + $_merchantId = Mage::getStoreConfig('payment/amazonpayments_cba/merchant_id'); + return $_url.$_merchantId; + } + + public function getConfigShippingRates() + { + if (is_null($this->_configShippingRates)) { + $this->_configShippingRates = array(); + foreach ($this->_shippingRates as $_rate) { + $_carrier = unserialize(Mage::getStoreConfig('payment/amazonpayments_cba/' . strtolower($_rate) . '_rate')); + if ($_carrier['method'] && $_carrier['method'] != 'None') { + $_carrierInfo = explode('/', $_carrier['method']); + $this->_configShippingRates[$_rate] = array( + 'carrier' => $_carrierInfo[0], + 'method' => $_carrierInfo[1] + ); + } + } + } + return $this->_configShippingRates; + } + + /** + * Computes RFC 2104-compliant HMAC signature. + * + * @param data Array + * The data to be signed. + * @param key String + * The signing key, a.k.a. the AWS secret key. + * @return The base64-encoded RFC 2104-compliant HMAC signature. + */ + public function calculateSignature($data, $secretKey) + { + $stringData = ''; + if (is_array($data)) { + ksort($data); + foreach ($data as $key => $value) { + $stringData .= $key.'='.rawurlencode($value).'&'; + } + } elseif (is_string($data)) { + $stringData = $data; + } else { + $stringData = $data; + } + + // compute the hmac on input data bytes, make sure to set returning raw hmac to be true + $rawHmac = hash_hmac(self::$HMAC_SHA1_ALGORITHM, $stringData, $secretKey, true); + + // base64-encode the raw hmac + return base64_encode($rawHmac); + } + + /** + * Format amount value (2 digits after the decimal point) + * + * @param float $amount + * @return float + */ + public function formatAmount($amount) + { + return Mage::helper('amazonpayments')->formatAmount($amount); + } + + /** + * Build XML-based Cart for Checkout by Amazon + * + * @param Mage_Sales_Model_Quote + * @return string + */ + public function getXmlCart(Mage_Sales_Model_Quote $quote) + { + $_xml = ''."\n" + .''."\n"; + if (!$quote->hasItems()) { + return false; + } + $_xml .= " {$quote->getId()}\n"; // Returning parametr + # .""; + + $_xml .= " \n" + ." \n"; + + foreach ($quote->getAllVisibleItems() as $_item) { + $_xml .= " \n" + ." {$_item->getSku()}/{$_item->getId()}\n" + ." {$this->getMerchantId()}\n" + ." {$_item->getName()}\n" + ." \n" + ." {$this->formatAmount($_item->getPrice())}\n" + ." {$quote->getBaseCurrencyCode()}\n" + ." \n" + ." {$_item->getQty()}\n" + ." \n" + ." {$this->formatAmount($_item->getWeight())}\n" + ." lb\n" + ." \n"; + $_xml .= " \n"; + + } + $_xml .= " \n" + ." cart-total-discount\n" + ." \n"; + + $_xml .= " A2ZZYWSJ0WMID8MAGENTO\n" + ." Varien\n"; + $_xml .= " \n" + ." true\n" + ." true\n" + ." true\n" + ." ".Mage::getUrl('amazonpayments/cba/callback', array('_secure' => true))."\n" + ." true\n" + ." \n"; + + $_xml .= "\n"; + return $_xml; + + } + + /** + * Retreive checkout tax tables xml + * + * @param Mage_Sales_Model_Quote $quote + * @return string + */ + protected function _getCheckoutTaxXml(Mage_Sales_Model_Quote $quote) + { + $xml = ''; + + // shipping tax table (used as default, because we have no ability to specify shipping tax table) + //$xml .= $this->_getTaxTablesXml($quote, $this->_getShippingTaxRules($quote), true); + // removed because of no ability to use default tax table as shipping tax table + + $xml .= "\n"; + + // item tax tables + $xml .= $this->_getTaxTablesXml($quote, $this->_getTaxRules($quote)); + + // empty tax table for products without tax class + $xml .= " \n" + ." none\n" + ." \n" + ." \n" + ." 0\n" + ." WorldAll\n" + ." \n" + ." \n" + ." \n"; + + $xml .= "\n"; + + + return $xml; + } + + /** + * Retreive specified tax rates xml + * + * @param Mage_Sales_Model_Quote $quote + * @param array $rates + * @param string $type + * @param mixed $addTo + * @return string + */ + protected function _getTaxTablesXml($quote, $rules, $isShipping = false, $addTo = null) + { + $xml = ''; + if ($isShipping) { + $isShippingTaxed = 'true'; + $taxTableTag = 'DefaultTaxTable'; + } else { + $isShippingTaxed = 'false'; + $taxTableTag = 'TaxTable'; + } + + if (is_array($rules)) { + if ($addTo) { + $_tables = $addTo->addChild('TaxTables'); + } + + foreach ($rules as $group=>$taxRates) { + $isShippingTaxed = ($isShipping ? 'true' : 'false'); + if ($isShipping) { + $tableName = 'default-tax-table'; + } else { + $tableName = "tax_{$group}"; + if ($group == $this->_getShippingTaxClassId($quote)) { + $isShippingTaxed = 'true'; + } + } + if ($addTo) { + // $_tables = $addTo->addChild('TaxTables'); + $_table = $_tables->addChild($taxTableTag); + $_table->addChild('TaxTableId', $tableName); + $_rules = $_table->addChild('TaxRules'); + } else { + $xml .= " <{$taxTableTag}>\n"; + $xml .= " {$tableName}\n"; + $xml .= " \n"; + } + + if (is_array($taxRates)) { + foreach ($taxRates as $rate) { + if ($addTo) { + $_rule = $_rules->addChild('TaxRule'); + $_rule->addChild('Rate', $rate['value']); + $_rule->addChild('IsShippingTaxed', $isShippingTaxed); + } else { + $xml .= " \n"; + $xml .= " {$rate['value']}\n"; + $xml .= " {$isShippingTaxed}\n"; + } + + if ($rate['country']==='US') { + if (!empty($rate['postcode']) && $rate['postcode']!=='*') { + if ($addTo) { + $_rule->addChild('USZipRegion', $rate['postcode']); + } else { + $xml .= " {$rate['postcode']}\n"; + } + } else if (!empty($rate['state']) && $rate['state']!=='*') { + if ($addTo) { + $_rule->addChild('USStateRegion', $rate['state']); + } else { + $xml .= " {$rate['state']}\n"; + } + } else { + if ($addTo) { + $_rule->addChild('PredefinedRegion', 'USAll'); + } else { + $xml .= " USAll\n"; + } + } + } else { + if ($addTo) { + $_region = $_rule->addChild('WorldRegion'); + $_region->addChild('CountryCode', $rate['country']); + if (!empty($rate['postcode']) && $rate['postcode']!=='*') { + $_region->addChild('PostalRegion', $rate['postcode']); + } + } else { + $xml .= " \n"; + $xml .= " {$rate['country']}\n"; + if (!empty($rate['postcode']) && $rate['postcode']!=='*') { + $xml .= " {$rate['postcode']}\n"; + } + $xml .= " \n"; + } + } + + $xml .= " \n"; + } + } else { + $taxRate = $taxRates/100; + if ($addTo) { + $_rule = $_rules->addChild('TaxRule'); + $_rule->addChild('Rate', $taxRate); + $_rule->addChild('IsShippingTaxed', $isShippingTaxed); + $_rule->addChild('PredefinedRegion', 'WorldAll'); + } else { + $xml .= " \n"; + $xml .= " {$taxRate}\n"; + $xml .= " {$isShippingTaxed}\n"; + $xml .= " WorldAll\n"; + $xml .= " \n"; + } + } + + $xml .= " \n"; + $xml .= " \n"; + } + + } else { + if (is_numeric($rules)) { + $taxRate = $rules/100; + + if ($addTo) { + $_table = $addTo->addChild($taxTableTag); + $_rules = $_table->addChild('TaxRules'); + $_rule = $_rules->addChild('TaxRule'); + $_rule->addChild('Rate', $taxRate); + $_rule->addChild('IsShippingTaxed', $isShippingTaxed); + $_rule->addChild('PredefinedRegion', 'WorldAll'); + } else { + $xml .= " <{$taxTableTag}>\n"; + $xml .= " \n"; + $xml .= " \n"; + $xml .= " {$taxRate}\n"; + $xml .= " {$isShippingTaxed}\n"; + $xml .= " WorldAll\n"; + $xml .= " \n"; + $xml .= " \n"; + $xml .= " \n"; + } + } + } + + if ($addTo) { + return $addTo; + } + + return $xml; + } + + + /** + * Retreive tax rules applicable to quote items + * + * @param Mage_Sales_Model_Quote $quote + * @return array + */ + protected function _getTaxRules(Mage_Sales_Model_Quote $quote) + { + $customerTaxClass = $this->_getCustomerTaxClass($quote); + if (Mage::helper('tax')->getTaxBasedOn() == 'origin') { + $request = Mage::getSingleton('tax/calculation')->getRateRequest(); + return Mage::getSingleton('tax/calculation')->getRatesForAllProductTaxClasses($request->setCustomerClassId($customerTaxClass)); + } else { + $customerRules = Mage::getSingleton('tax/calculation')->getRatesByCustomerTaxClass($customerTaxClass); + $rules = array(); + foreach ($customerRules as $rule) { + $rules[$rule['product_class']][] = $rule; + } + return $rules; + } + } + + /** + * Retreive tax rules applicable to shipping + * + * @param Mage_Sales_Model_Quote $quote + * @return array + */ + protected function _getShippingTaxRules(Mage_Sales_Model_Quote $quote) + { + $customerTaxClass = $this->_getCustomerTaxClass($quote); + if ($shippingTaxClass = $this->_getShippingTaxClassId($quote)) { + if (Mage::helper('tax')->getTaxBasedOn() == 'origin') { + $request = Mage::getSingleton('tax/calculation')->getRateRequest(); + $request + ->setCustomerClassId($customerTaxClass) + ->setProductClassId($shippingTaxClass); + + return Mage::getSingleton('tax/calculation')->getRate($request); + } + $customerRules = Mage::getSingleton('tax/calculation')->getRatesByCustomerAndProductTaxClasses($customerTaxClass, $shippingTaxClass); + $rules = array(); + foreach ($customerRules as $rule) { + $rules[$rule['product_class']][] = $rule; + } + return $rules; + } else { + return array(); + } + } + + /** + * Retreive shipping tax class + * + * @param Mage_Sales_Model_Quote $quote + * @return int + */ + protected function _getShippingTaxClassId(Mage_Sales_Model_Quote $quote) + { + return Mage::getStoreConfig(Mage_Tax_Model_Config::CONFIG_XML_PATH_SHIPPING_TAX_CLASS, $quote->getStoreId()); + } + + /** + * Retreive customer tax class from quote + * + * @param Mage_Sales_Model_Quote $quote + * @return int + */ + protected function _getCustomerTaxClass(Mage_Sales_Model_Quote $quote) + { + $customerGroup = $quote->getCustomerGroupId(); + if (!$customerGroup) { + $customerGroup = Mage::getStoreConfig('customer/create_account/default_group', $quote->getStoreId()); + } + return Mage::getModel('customer/group')->load($customerGroup)->getTaxClassId(); + } + + /** + * Handle Callback from CBA and calculate Shipping, Taxes in case XML-based shopping cart + * + */ + public function handleXmlCallback($xmlRequest, $session) + { + $_address = $this->_parseRequestAddress($xmlRequest); + + #$quoteId = $session->getAmazonQuoteId(); + $quoteId = $_address['ClientRequestId']; + $quote = Mage::getModel('sales/quote')->load($quoteId); + + $baseCurrency = $session->getQuote()->getBaseCurrencyCode(); + $currency = Mage::app()->getStore($session->getQuote()->getStoreId())->getBaseCurrency(); + + $billingAddress = $quote->getBillingAddress(); + $address = $quote->getShippingAddress(); + + $this->_address = $_address; + + $regionModel = Mage::getModel('directory/region')->loadByCode($_address['regionCode'], $_address['countryCode']); + $_regionId = $regionModel->getId(); + + $address->setCountryId($_address['countryCode']) + ->setRegion($_address['regionCode']) + ->setRegionId($_regionId) + ->setCity($_address['city']) + ->setStreet($_address['street']) + ->setPostcode($_address['postCode']); + $billingAddress->setCountryId($_address['countryCode']) + ->setRegion($_address['regionCode']) + ->setRegionId($_regionId) + ->setCity($_address['city']) + ->setStreet($_address['street']) + ->setPostcode($_address['postCode']); + + $quote->setBillingAddress($billingAddress); + $quote->setShippingAddress($address); + $quote->save(); + + $address->setCollectShippingRates(true)->collectShippingRates(); + + $errors = array(); + $_carriers = array(); + foreach ($this->getConfigShippingRates() as $_cfgRate) { + if ($carrier = Mage::getStoreConfig('carriers/' . $_cfgRate['carrier'], $this->getStoreId())) { + if (isset($carrier['title']) && $carrier['active'] && !in_array($_cfgRate['carrier'], $_carriers)) { + $_carriers[] = $_cfgRate['carrier']; + } + } + } + + $result = Mage::getModel('shipping/shipping') + ->collectRatesByAddress($address, $_carriers) + ->getResult(); + $rateCodes = array(); + foreach ($this->getConfigShippingRates() as $_cfgRateLevel => $_cfgRate) { + if ($rates = $result->getRatesByCarrier($_cfgRate['carrier'])) { + foreach ($rates as $rate) { + if (!$rate instanceof Mage_Shipping_Model_Rate_Result_Error && $rate->getMethod() == $_cfgRate['method']) { + if ($address->getFreeShipping()) { + $price = 0; + } else { + $price = $rate->getPrice(); + } + if ($price) { + $price = Mage::helper('tax')->getShippingPrice($price, true, $address); + } + $this->_carriers[] = array( + 'service_level' => $_cfgRateLevel, + 'code' => $rate->getCarrier() . '_' . $rate->getMethod(), + 'price' => $price, + 'currency' => $currency['currency_code'], + 'description' => $rate->getCarrierTitle() . ' - ' . $rate->getMethodTitle() . ' (Amazon ' . $_cfgRateLevel . ' Service Level)' + ); + } + } + } + } + + if ($_extShippingInfo = unserialize($quote->getExtShippingInfo())) { + $_extShippingInfo = array_merge($_extShippingInfo, array('amazon_service_level' => $this->_carriers)); + } else { + $_extShippingInfo = array('amazon_service_level' => $this->_carriers); + } + $quote->setExtShippingInfo(serialize($_extShippingInfo)); + + $_items = $this->_parseRequestItems($xmlRequest); + $xml = $this->_generateXmlResponse($quote, $_items); + + $session->getQuote() + ->setForcedCurrency($currency) + ->collectTotals() + ->save(); + $quote->save(); + return $xml; + } + + /** + * Parse request from Amazon and return order details + * + * @param string xml + */ + public function parseOrder($xmlData) + { + $parsedOrder = array(); + if (strlen(trim($xmlData)) > 0) { + $xml = simplexml_load_string($xmlData, 'Varien_Simplexml_Element'); + $parsedOrder = array( + 'NotificationReferenceId' => (string) $xml->descend("NotificationReferenceId"), + 'amazonOrderID' => (string) $xml->descend("ProcessedOrder/AmazonOrderID"), + 'orderDate' => (string) $xml->descend("ProcessedOrder/OrderDate"), + 'orderChannel' => (string) $xml->descend("ProcessedOrder/OrderChannel"), + 'buyerName' => (string) $xml->descend("ProcessedOrder/BuyerInfo/BuyerName"), + 'buyerEmailAddress' => (string) $xml->descend("ProcessedOrder/BuyerInfo/BuyerEmailAddress"), + 'ShippingLevel' => (string) $xml->descend("ProcessedOrder/ShippingServiceLevel"), + 'shippingAddress' => array( + 'name' => (string) $xml->descend("ProcessedOrder/ShippingAddress/Name"), + 'street' => (string) $xml->descend("ProcessedOrder/ShippingAddress/AddressFieldOne"), + 'city' => (string) $xml->descend("ProcessedOrder/ShippingAddress/City"), + 'regionCode' => (string) $xml->descend("ProcessedOrder/ShippingAddress/State"), + 'postCode' => (string) $xml->descend("ProcessedOrder/ShippingAddress/PostalCode"), + 'countryCode' => (string) $xml->descend("ProcessedOrder/ShippingAddress/CountryCode"), + ), + 'items' => array(), + ); + + $_total = $_shipping = $_tax = $_shippingTax = $_subtotalPromo = $_shippingPromo = $_subtotal = 0; + $_itemsCount = $_itemsQty = 0; + foreach ($xml->descend("ProcessedOrder/ProcessedOrderItems/ProcessedOrderItem") as $_item) { + $parsedOrder['ClientRequestId'] = (string) $_item->ClientRequestId; + $_compositeSku = explode('/', (string) $_item->SKU); + $_sku = ''; + if (isset($_compositeSku[0])) { + $_sku = $_compositeSku[0]; + } + $_itemId = ''; + if ($_compositeSku[1]) { + $_itemId = $_compositeSku[1]; + } + $_itemQty = (string) $_item->Quantity; + $_itemsQty += $_itemQty; + $_itemsCount++; + $parsedOrder['items'][$_itemId] = array( + 'AmazonOrderItemCode' => (string) $_item->AmazonOrderItemCode, + 'sku' => $_sku, + 'title' => (string) $_item->Title, + 'price' => array( + 'amount' => (string) $_item->Price->Amount, + 'currencyCode' => (string) $_item->Price->CurrencyCode, + ), + 'qty' => $_itemQty, + 'weight' => array( + 'amount' => (string) $_item->Weight->Amount, + 'unit' => (string) $_item->Weight->Unit, + ), + ); + $_itemSubtotal = 0; + foreach ($_item->ItemCharges->Component as $_component) { + switch ((string) $_component->Type) { + case 'Principal': + $_itemSubtotal += (string) $_component->Charge->Amount; + $parsedOrder['items'][$_itemId]['subtotal'] = $_itemSubtotal; + break; + case 'Shipping': + $_shipping += (string) $_component->Charge->Amount; + $parsedOrder['items'][$_itemId]['shipping'] = (string) $_component->Charge->Amount; + break; + case 'Tax': + $_tax += (string) $_component->Charge->Amount; + $parsedOrder['items'][$_itemId]['tax'] = (string) $_component->Charge->Amount; + break; + case 'ShippingTax': + $_shippingTax += (string) $_component->Charge->Amount; + $parsedOrder['items'][$_itemId]['shipping_tax'] = (string) $_component->Charge->Amount; + break; + case 'PrincipalPromo': + $_subtotalPromo += (string) $_component->Charge->Amount; + $parsedOrder['items'][$_itemId]['principal_promo'] = (string) $_component->Charge->Amount; + break; + case 'ShippingPromo': + $_shippingPromo += (string) $_component->Charge->Amount; + $parsedOrder['items'][$_itemId]['shipping_promo'] = (string) $_component->Charge->Amount; + break; + } + } + $_subtotal += $_itemSubtotal; + } + + $parsedOrder['itemsCount'] = $_itemsCount; + $parsedOrder['itemsQty'] = $_itemsQty; + + $parsedOrder['subtotal'] = $_subtotal; + $parsedOrder['shippingAmount'] = $_shipping; + $parsedOrder['tax'] = $_tax + $_shippingTax; + $parsedOrder['shippingTax'] = $_shippingTax; + $parsedOrder['discount'] = $_subtotalPromo + $_shippingPromo; + $parsedOrder['discountShipping'] = $_shippingPromo; + + $parsedOrder['total'] = $_subtotal + $_shipping + $_tax + $_shippingTax - abs($_subtotalPromo) - abs($_shippingPromo); + } + return $parsedOrder; + } + + public function parseCancelNotification($xmlData) + { + $cancelData = array(); + if (strlen(trim($xmlData))) { + $xml = simplexml_load_string($xmlData, 'Varien_Simplexml_Element'); + $aOrderId = (string) $xml->descend('ProcessedOrder/AmazonOrderID'); + $cancelData['amazon_order_id'] = $aOrderId; + } + return $cancelData; + } + + /** + * Return address from Amazon request + * + * @param array $responseArr + */ + protected function _parseRequestAddress($xmlResponse) + { + $address = array(); + if (strlen(trim($xmlResponse)) > 0) { + $xml = simplexml_load_string($xmlResponse, 'Varien_Simplexml_Element'); + + $address = array( + 'addressId' => (string) $xml->descend("CallbackOrders/CallbackOrder/Address/AddressId"), + 'regionCode' => (string) $xml->descend("CallbackOrders/CallbackOrder/Address/State"), + 'countryCode' => (string) $xml->descend("CallbackOrders/CallbackOrder/Address/CountryCode"), + 'city' => (string) $xml->descend("CallbackOrders/CallbackOrder/Address/City"), + 'street' => (string) $xml->descend("CallbackOrders/CallbackOrder/Address/AddressFieldOne"), + 'postCode' => (string) $xml->descend("CallbackOrders/CallbackOrder/Address/PostalCode"), + 'ClientRequestId' => (string) $xml->descend("ClientRequestId"), + ); + } else { + $address = array( + 'addressId' => '', + 'regionCode' => '', + 'countryCode' => '', + 'city' => '', + 'street' => '', + 'postCode' => '', + ); + } + return $address; + } + + /** + * Return items SKUs from Amazon request + * + * @param array $responseArr + */ + protected function _parseRequestItems($xmlResponse) + { + $items = array(); + if (strlen(trim($xmlResponse)) > 0) { + $xml = simplexml_load_string($xmlResponse, 'Varien_Simplexml_Element'); + $itemsXml = $xml->descend("Cart/Items"); + foreach ($itemsXml->Item as $_item) { + $sku = ''; + $compositeSku = explode('/', (string)$_item->SKU); + if (isset($compositeSku[0])) { + $sku = $compositeSku[0]; + } + $items[(string)$_item->SKU] = $sku; + } + } else { + return false; + } + return $items; + } + + /** + * Generate XML Responce for Amazon with Shipping, Taxes, Promotions + * + * @return string xml + */ + protected function _generateXmlResponse($quote, $items = array()) + { + + $_xmlString = << + + +XML; + + $xml = new SimpleXMLElement($_xmlString); + + if (count($this->_carriers) > 0) { + $_xmlResponse = $xml->addChild('Response'); + $_xmlCallbackOrders = $_xmlResponse->addChild('CallbackOrders'); + $_xmlCallbackOrder = $_xmlCallbackOrders->addChild('CallbackOrder'); + + $_xmlAddress = $_xmlCallbackOrder->addChild('Address'); + $_xmlAddressId = $_xmlAddress->addChild('AddressId', $this->_address['addressId']); + + $_xmlCallbackOrderItems = $_xmlCallbackOrder->addChild('CallbackOrderItems'); + foreach ($items as $_itemSku) { + $_quoteItem = null; + foreach ($quote->getAllItems() as $_item) { + if ($_item->getSku() == $_itemSku) { + $_quoteItem = $_item; + break; + } + } + if (is_null($_quoteItem)) { + Mage::throwException($this->__('Item specified in callback request XML was not found in quote.')); + } + + $_xmlCallbackOrderItem = $_xmlCallbackOrderItems->addChild('CallbackOrderItem'); + $_xmlCallbackOrderItem->addChild('SKU', $_itemSku . '/' . $_quoteItem->getId()); + $_xmlCallbackOrderItem->addChild('TaxTableId', 'tax_'.$_quoteItem->getTaxClassId()); + + + + $_xmlShippingMethodIds = $_xmlCallbackOrderItem->addChild('ShippingMethodIds'); + foreach ($this->_carriers as $_carrier) { + $_xmlShippingMethodIds->addChild('ShippingMethodId', $_carrier['code']); + } + } + + $this->_appendTaxTables($xml, $quote, $this->_getTaxRules($quote)); + $this->_appendDiscounts($xml, $quote); + + $_xmlShippingMethods = $xml->addChild('ShippingMethods'); + foreach ($this->_carriers as $_carrier) { + $_xmlShippingMethod = $_xmlShippingMethods->addChild('ShippingMethod'); + + $_xmlShippingMethod->addChild('ShippingMethodId', $_carrier['code']); + $_xmlShippingMethod->addChild('ServiceLevel', $_carrier['service_level']); + + $_xmlShippingMethodRate = $_xmlShippingMethod->addChild('Rate'); + // Posible values: ShipmentBased | WeightBased | ItemQuantityBased + $_xmlShippingMethodRateItem = $_xmlShippingMethodRate->addChild('ShipmentBased'); + $_xmlShippingMethodRateItem->addChild('Amount', $_carrier['price']); + $_xmlShippingMethodRateItem->addChild('CurrencyCode', $_carrier['currency']); + + $_xmlShippingMethodIncludedRegions = $_xmlShippingMethod->addChild('IncludedRegions'); + $_xmlShippingMethodIncludedRegions->addChild('PredefinedRegion', 'WorldAll'); + } + $xml->addChild('CartPromotionId', 'cart-total-discount'); + } + + return $xml; + } + + protected function _appendTaxTables($xml, $quote, $rules, $isShipping = false) + { + return $this->_getTaxTablesXml($quote, $rules, $isShipping, $xml); + } + + protected function _appendDiscounts($xml, $quote) + { + $totalDiscount = $quote->getShippingAddress()->getBaseDiscountAmount() + $quote->getBillingAddress()->getBaseDiscountAmount(); + $discountAmount = ($totalDiscount ? $totalDiscount : 0); + + $_promotions = $xml->addChild('Promotions'); + $_promotion = $_promotions->addChild('Promotion'); + $_promotion->addChild('PromotionId', 'cart-total-discount'); + $_promotion->addChild('Description', 'Discount'); + $_benefit = $_promotion->addChild('Benefit'); + $_fad = $_benefit->addChild('FixedAmountDiscount'); + $_fad->addChild('Amount', $discountAmount); + $_fad->addChild('CurrencyCode', $quote->getBaseCurrencyCode()); + + return $xml; + } + + /** + * Generate XML with error message in case Calculation Callbacks error + * + * @param Exception $e + */ + public function callbackXmlError(Exception $e) + { + // Posible error codes: INVALID_SHIPPING_ADDRESS | INTERNAL_SERVER_ERROR | SERVICE_UNAVAILABLE + $xmlErrorString = ''."\n" + .''."\n" + .' '."\n" + .' '."\n" + .' INTERNAL_SERVER_ERROR'."\n" + .' [MESSAGE]'."\n" + .' '."\n" + .' '."\n" + .''; + $_errorMsg = $e->getMessage(); + $_errorMessage = "{$_errorMsg}\n\n" + ."code: {$e->getCode()}\n\n" + ."file: {$e->getFile()}\n\n" + ."line: {$e->getLine()}\n\n" + ."trac: {$e->getTraceAsString()}\n\n"; + if ($this->getDebug()) { + $debug = Mage::getModel('amazonpayments/api_debug') + ->setResponseBody($_errorMessage) + ->setRequestBody(time() .' - error callback response') + ->save(); + } + + if ($_errorMsg = $e->getMessage() && 0) { + $xmlErrorString = str_replace('[MESSAGE]', $_errorMsg, $xmlErrorString); + } else { + $xmlErrorString = str_replace('[MESSAGE]', 'Error', $xmlErrorString); + } + $xml = new SimpleXMLElement($xmlErrorString); + return $xml; + } + + /** + * Get order amazon api + * + * @return Mage_AmazonPayments_Model_Api_Cba_Document + */ + public function getDocumentApi() + { + if (is_null($this->getData('document_api'))) { + $_documentApi = Mage::getModel('amazonpayments/api_cba_document') + ->setWsdlUri('https://merchant-api.amazon.com/gateway/merchant-interface-mime/') + ->setMerchantInfo(array( + 'merchantIdentifier' => Mage::getStoreConfig('payment/amazonpayments_cba/merchant_tocken'), + 'merchantName' => Mage::getStoreConfig('payment/amazonpayments_cba/merchant_name'), + )) + ->init( + Mage::getStoreConfig('payment/amazonpayments_cba/merchant_login'), + Mage::getStoreConfig('payment/amazonpayments_cba/merchant_pass') + ); + $this->setData('document_api', $_documentApi); + } + return $this->getData('document_api'); + } + + /** + * Cancel order + * + * @param Mage_Sales_Model_Order $order + * @return Mage_AmazonPayments_Model_Api_Cba + */ + public function cancel($order) + { + $this->getDocumentApi()->cancel($order); + return $this; + } + + /** + * Refund order + * + * @param Mage_Sales_Model_Order_Payment $payment + * @param float $amount + * @return Mage_AmazonPayments_Model_Api_Cba + */ + public function refund($payment, $amount) + { + $this->getDocumentApi()->refund($payment, $amount); + return $this; + } + + /** + * Confirm crating of shipment + * + * @param Mage_Sales_Model_Order_Shipment $shipment + * @return Mage_AmazonPayments_Model_Api_Cba + */ + public function confirmShipment($shipment) + { + $items = array(); + foreach ($shipment->getAllItems() as $item) { + /* @var $item Mage_Sales_Model_Order_Shipment_Item */ + if ($item->getOrderItem()->getParentItemId()) { + continue; + } + $items[] = array( + 'id' => $item->getOrderItem()->getExtOrderItemId(), + 'qty' => $item->getQty() + ); + } + $carrier = $shipment->getOrder()->getShippingCarrier(); + + $carrierCode = ''; + $carrierMethod = ''; + $trackNumber = ''; + /** + * Magento track numbers is not connected with items. + * Get only first track number + */ + foreach ($shipment->getAllTracks() as $track) { + $trackNumber = $track->getNumber(); + break; + } + $_shipping = explode('_', $shipment->getOrder()->getShippingMethod()); + if ($_shipping && count($_shipping) >= 2) { + $carrierCode = $_shipping[0]; + $carrierMethod = $carrier->getCode('method', $_shipping[1]); + } + + $this->getDocumentApi()->confirmShipment( + $shipment->getOrder()->getExtOrderId(), + $carrierCode, + $carrierMethod, + $items, + $trackNumber + ); + return $this; + } + + /** + * Send shipping track number + * + * @param Mage_Sales_Model_Order $order + * @param Mage_Sales_Model_Order_Shipment_Track $track + * @param Mage_AmazonPayments_Model_Api_Cba + */ + public function sendTrackingNumber($order, $track) + { +// $this->getDocumentApi()->sendTrackNumber($order, $carrierCode, $carrierMethod, $trackNumber); + return $this; + } + +} diff --git a/app/code/core/Mage/AmazonPayments/Model/Api/Cba/Document.php b/app/code/core/Mage/AmazonPayments/Model/Api/Cba/Document.php new file mode 100644 index 0000000000..40c9659106 --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/Model/Api/Cba/Document.php @@ -0,0 +1,438 @@ + + */ +class Mage_AmazonPayments_Model_Api_Cba_Document extends Varien_Object +{ + const MESSAGE_TYPE_ADJUSTMENT = '_POST_PAYMENT_ADJUSTMENT_DATA_'; + const MESSAGE_TYPE_FULFILLMENT = '_POST_ORDER_FULFILLMENT_DATA_'; + const MESSAGE_TYPE_ACKNOWLEDGEMENT = '_POST_ORDER_ACKNOWLEDGEMENT_DATA_'; + + protected $_wsdlUri = null; + protected $_merchantInfo = array(); + protected $_client = null; + protected $_result = null; + protected $_options = array( + 'trace' => true, + 'timeout' => '20', + ); + + protected function _construct() + { + parent::_construct(); + } + + /** + * Set Wsdl uri + * + * @param string $wsdlUri + * @return Mage_AmazonPayments_Model_Api_Cba_Document + */ + public function setWsdlUri($wsdlUri) + { + $this->_wsdlUri = $wsdlUri; + return $this; + } + + /** + * Return Wsdl Uri + * + * @return string + */ + public function getWsdlUri() + { + return $this->_wsdlUri; + } + + /** + * Set merchant info + * + * @param array $merchantInfo + * @return Mage_AmazonPayments_Model_Api_Cba_Document + */ + public function setMerchantInfo(array $merchantInfo = array()) + { + $this->_merchantInfo = $merchantInfo; + return $this; + } + + /** + * Return merchant info + * + * @return array + */ + public function getMerchantInfo() + { + return $this->_merchantInfo; + } + + /** + * Return merchant identifier + * + * @return string + */ + public function getMerchantIdentifier() + { + if (array_key_exists('merchantIdentifier', $this->_merchantInfo)) { + return $this->_merchantInfo['merchantIdentifier']; + } + return null; + } + + /** + * Return Soap object + * + * @return SOAP_Client + */ + public function getClient() + { + return $this->_client; + } + + /** + * Initialize Soap Client object and authorize + * + * @param string $login + * @param string $password + * @return Mage_AmazonPayments_Model_Api_Cba_Document + */ + public function init($login, $password) + { + if ($this->getWsdlUri()) { + $this->_client = null; + $auth = array('user' => $login, 'pass' => $password); + try { + set_include_path( + BP . DS . 'lib' . DS . 'PEAR' . PS . get_include_path() + ); + require_once 'SOAP/Client.php'; + $this->_client = new SOAP_Client($this->getWsdlUri(), true, false, $auth, false); + } catch (Exception $e) { + Zend_Debug::dump($e->getMessage()); + } + } + return $this; + } + + /** + * Create soap attachment (MIME encoding) + * + * @param string $document + * @return string + */ + protected function _createAttachment($document) + { + require_once 'SOAP/Value.php'; + $attachment = new SOAP_Attachment('doc', 'application/binary', null, $document); + $attachment->options['attachment']['encoding'] = '8bit'; + $this->_options['attachments'] = 'Mime'; + return $attachment; + } + + /** + * Proccess request and setting result + * + * @param string $method + * @param array $params + * @return Mage_AmazonPayments_Model_Api_Cba_Document + */ + protected function _proccessRequest($method, $params) + { + if ($this->getClient()) { + $this->_result = null; + try { + $this->_result = $this->getClient() + ->call($method, $params, $this->_options); + } catch (Exception $e) { + Zend_Debug::dump($e->getMessage()); + } + } + return $this; + } + + /** + * Format amount value (2 digits after the decimal point) + * + * @param float $amount + * @return float + */ + public function formatAmount($amount) + { + return Mage::helper('amazonpayments')->formatAmount($amount); + } + + /** + * Get order info + * + * @param string $aOrderId Amazon order id + * @return Varien_Simplexml_Element + */ + public function getDocument($aOrderId) + { + $params = array( + 'merchant' => $this->getMerchantInfo(), + 'documentIdentifier' => $aOrderId + ); + $this->_proccessRequest('getDocument', $params); + + require_once 'Mail/mimeDecode.php'; + $decoder = new Mail_mimeDecode($this->getClient()->xml); + $decoder->decode(array( + 'include_bodies' => true, + 'decode_bodies' => true, + 'decode_headers' => true, + )); + $xml = $decoder->_body; + + // remove the ending mime boundary + $boundaryIndex = strripos($xml, '--xxx-WASP-CPP-MIME-Boundary-xxx'); + if (!($boundaryIndex === false)) { + $xml = substr($xml, 0, $boundaryIndex); + } + + return simplexml_load_string($xml, 'Varien_Simplexml_Element'); + } + + /** + * Get pending orders + * + * @return array + */ + public function getPendingDocuments() + { + $params = array( + 'merchant' => $this->getMerchantInfo(), + 'messageType' => '_GET_ORDERS_DATA_' + ); + $this->_proccessRequest('getAllPendingDocumentInfo', $params); + if (!is_array($this->_result)) { + $this->_result = array($this->_result); + } + return $this->_result; + } + + /** + * Cancel order + * + * @param Mage_Sales_Model_Order $order + * @return string Amazon Transaction Id + */ + public function cancel($order) + { + $_document = ' + +
+ 1.01 + ' . $this->getMerchantIdentifier() . ' +
+ OrderAcknowledgement + + 1 + Update + + ' . $order->getExtOrderId() . ' + Failure + + +
'; + + $params = array( + 'merchant' => $this->getMerchantInfo(), + 'messageType' => self::MESSAGE_TYPE_ACKNOWLEDGEMENT, + 'doc' => $this->_createAttachment($_document) + ); + + $this->_proccessRequest('postDocument', $params); + return $this->_result; + } + + /** + * Refund order + * + * @param Mage_Sales_Model_Order_Payment $payment + * @param float $amount + * @return string Amazon Transaction Id + */ + public function refund($payment, $amount) + { + $_document = ' + +
+ 1.01 + ' . $this->getMerchantIdentifier() . ' +
+ OrderAdjustment'; + + $_shippingAmount = $payment->getCreditmemo()->getShippingAmount(); + $_messageId = 1; + foreach ($payment->getCreditmemo()->getAllItems() as $item) { + /* @var $item Mage_Sales_Model_Order_Creditmemo_Item */ + if ($item->getOrderItem()->getParentItemId()) { + continue; + } + + $shipping = 0; + $amazon_amounts = unserialize($item->getOrderItem()->getProductOptionByCode('amazon_amounts')); + if ($amazon_amounts['shipping'] > $_shippingAmount) { + $shipping = $_shippingAmount; + } else { + $shipping = $amazon_amounts['shipping']; + } + $_shippingAmount -= $shipping; + + $_document .= ' + ' . $_messageId . ' + + ' . $payment->getOrder()->getExtOrderId() . ' + + '. $item->getOrderItem()->getExtOrderItemId() . ' + GeneralAdjustment + + + Principal + ' . $this->formatAmount($item->getBaseRowTotal()) . ' + + + Tax + ' . $this->formatAmount($item->getBaseTaxAmount()) . ' + ' + .' + Shipping + ' . $this->formatAmount($shipping) . ' + ' + .''; + $_document .= ' + + '; + $_messageId++; + } + + $_document .= '
'; + $params = array( + 'merchant' => $this->getMerchantInfo(), + 'messageType' => self::MESSAGE_TYPE_ADJUSTMENT, + 'doc' => $this->_createAttachment($_document) + ); + $this->_proccessRequest('postDocument', $params); + return $this->_result; + } + + /** + * Confirm creating of shipment + * + * @param string $aOrderId + * @param string $carrierCode + * @param string $carrierMethod + * @param array $items + * @param string $trackNumber + * @return string Amazon Transaction Id + */ + public function confirmShipment($aOrderId, $carrierCode, $carrierMethod, $items, $trackNumber = '') + { + $fulfillmentDate = gmdate('Y-m-d\TH:i:s'); + $_document = ' + +
+ 1.01 + ' . $this->getMerchantIdentifier() . ' +
+ OrderFulfillment + + 1 + + ' . $aOrderId . ' + ' . $fulfillmentDate . ' + + ' . strtoupper($carrierCode) . ' + ' . $carrierMethod . ' + ' . $trackNumber .' + '; + foreach ($items as $item) { + $_document .= ' + ' . $item['id'] . ' + ' . $item['qty'] . ' + '; + } + $_document .= ' + +
'; + $params = array( + 'merchant' => $this->getMerchantInfo(), + 'messageType' => self::MESSAGE_TYPE_FULFILLMENT, + 'doc' => $this->_createAttachment($_document) + ); + $this->_proccessRequest('postDocument', $params); + return $this->_result; + } + + /** + * Send Tracking Number + * + * @param Mage_Sales_Model_Order $order + * @param string $carrierCode + * @param string $carrierMethod + * @param string $trackNumber + * @return string Amazon Transaction Id + */ + public function sendTrackNumber($order, $carrierCode, $carrierMethod, $trackNumber) + { + $fulfillmentDate = gmdate('Y-m-d\TH:i:s'); + $_document = ' + +
+ 1.01 + ' . $this->getMerchantIdentifier() . ' +
+ OrderFulfillment'; + $_document .= ' + 1 + + ' . $order->getExtOrderId() . ' + ' . $fulfillmentDate . ' + + ' . $carrierCode . ' + ' . $carrierMethod . ' + ' . $trackNumber .' + + + '; + $_document .= '
'; + $params = array( + 'merchant' => $this->getMerchantInfo(), + 'messageType' => self::MESSAGE_TYPE_FULFILLMENT, + 'doc' => $this->_createAttachment($_document) + ); + $this->_proccessRequest('postDocument', $params); + return $this->_result; + } +} diff --git a/app/code/core/Mage/AmazonPayments/Model/Api/Debug.php b/app/code/core/Mage/AmazonPayments/Model/Api/Debug.php new file mode 100644 index 0000000000..d33e4fb194 --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/Model/Api/Debug.php @@ -0,0 +1,44 @@ + + */ +class Mage_AmazonPayments_Model_Api_Debug extends Mage_Core_Model_Abstract +{ + /** + * Model initialization + * + */ + protected function _construct() + { + $this->_init('amazonpayments/api_debug'); + } +} \ No newline at end of file diff --git a/app/code/core/Mage/AmazonPayments/Model/Mysql4/Api/Debug.php b/app/code/core/Mage/AmazonPayments/Model/Mysql4/Api/Debug.php new file mode 100644 index 0000000000..3bdb339c40 --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/Model/Mysql4/Api/Debug.php @@ -0,0 +1,45 @@ + + */ + +class Mage_AmazonPayments_Model_Mysql4_Api_Debug extends Mage_Core_Model_Mysql4_Abstract +{ + /** + * Resource model initialization + */ + protected function _construct() + { + $this->_init('amazonpayments/api_debug', 'transaction_id'); + } +} \ No newline at end of file diff --git a/app/code/core/Mage/AmazonPayments/Model/Mysql4/Api/Debug/Collection.php b/app/code/core/Mage/AmazonPayments/Model/Mysql4/Api/Debug/Collection.php new file mode 100644 index 0000000000..3238fe6529 --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/Model/Mysql4/Api/Debug/Collection.php @@ -0,0 +1,45 @@ + + */ +class Mage_AmazonPayments_Model_Mysql4_Api_Debug_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract +{ + /** + * Collection constructor + * + */ + protected function _construct() + { + $this->_init('amazonpayments/api_debug'); + } +} \ No newline at end of file diff --git a/app/code/core/Mage/AmazonPayments/Model/Mysql4/Setup.php b/app/code/core/Mage/AmazonPayments/Model/Mysql4/Setup.php new file mode 100644 index 0000000000..50bdc92471 --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/Model/Mysql4/Setup.php @@ -0,0 +1,31 @@ +getEvent()->getShipment(); + if ($shipment->getOrder()->getPayment()->getMethod() != 'amazonpayments_cba') { + return; + } + + Mage::getModel('amazonpayments/api_cba') + ->confirmShipment($shipment); + } + + public function salesOrderShipmentTrackSaveAfter(Varien_Event_Observer $observer) + { + $track = $observer->getEvent()->getTrack(); + $order = $track->getShipment()->getOrder(); + /* @var $order Mage_Sales_Model_Order */ + if ($order->getPayment()->getMethod() != 'amazonpayments_cba') { + return; + } + Mage::getModel('amazonpayments/api_cba') + ->sendTrackingNumber($order, $track); + } +} diff --git a/app/code/core/Mage/AmazonPayments/Model/Payment.php b/app/code/core/Mage/AmazonPayments/Model/Payment.php new file mode 100644 index 0000000000..29416b0265 --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/Model/Payment.php @@ -0,0 +1,54 @@ +getRedirectUrl(); + } + + public function getRedirectUrl() + { + #Mage::exception($this, 'worldpay'); + #throw new Exception('qwe') + die('test2'); + $_url = Mage::getUrl('amazonepayments/redirect'); + echo "url: {$_url}
"; + return $_url; + } +} \ No newline at end of file diff --git a/app/code/core/Mage/AmazonPayments/Model/Payment/Asp.php b/app/code/core/Mage/AmazonPayments/Model/Payment/Asp.php new file mode 100644 index 0000000000..d39021b870 --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/Model/Payment/Asp.php @@ -0,0 +1,415 @@ + + */ +class Mage_AmazonPayments_Model_Payment_Asp extends Mage_Payment_Model_Method_Abstract +{ + /** + * rewrited for Mage_Payment_Model_Method_Abstract + */ + protected $_isGateway = false; + protected $_canAuthorize = false; + protected $_canCapture = true; + protected $_canCapturePartial = false; + protected $_canRefund = true; + protected $_canVoid = true; + protected $_canUseInternal = false; + protected $_canUseCheckout = true; + protected $_canUseForMultishipping = false; + protected $_isInitializeNeeded = true; + + /** + * rewrited for Mage_Payment_Model_Method_Abstract + */ + protected $_formBlockType = 'amazonpayments/asp_form'; + + /** + * rewrited for Mage_Payment_Model_Method_Abstract + */ + protected $_code = 'amazonpayments_asp'; + + /** + * current order + */ + protected $_order; + + /** + * Get value from the module config + * + * @param string $path + * @return string + */ + public function getConfig($path) + { + return Mage::getStoreConfig('payment/' . $this->_code . '/' . $path); + } + + /** + * rewrited for Mage_Payment_Model_Method_Abstract + */ + public function isAvailable($quote=null) + { + return $this->getConfig('active'); + } + + /** + * Get singleton with AmazonPayments ASP API Model + * + * @return Mage_AmazonPayments_Model_Api_Asp + */ + public function getApi() + { + return Mage::getSingleton('amazonpayments/api_asp'); + } + + /** + * Get singleton with AmazonPayments ASP Notification Model + * + * @return Mage_AmazonPayments_Model_Payment_Asp_Notification + */ + public function getNotification() + { + return Mage::getSingleton('amazonpayments/payment_asp_notification'); + } + + /** + * Set model of current order + * + * @param Mage_Sales_Model_Order $order + * @return Mage_AmazonPayments_Model_Payment_Asp + */ + public function setOrder($order) + { + $this->_order = $order; + return $this; + } + + /** + * Get model of current order + * + * @return Mage_Sales_Model_Order + */ + public function getOrder() + { + if (!$this->_order) { + $paymentInfo = $this->getInfoInstance(); + $this->_order = Mage::getModel('sales/order')->loadByIncrementId( + $paymentInfo->getOrder()->getRealOrderId() + ); + } + return $this->_order; + } + + /** + * Add item in to log storage + * + * @param string $request + * @param string $response + * @return Mage_AmazonPayments_Model_Payment_Asp + */ + protected function _log($request, $response = '') + { + $debug = Mage::getModel('amazonpayments/api_debug') + ->setRequestBody($request) + ->setResponseBody($response) + ->save(); + return $this; + } + + /** + * Send mail + * + * @param string $template + * @param array $variables + * @return Mage_AmazonPayments_Model_Payment_Asp + */ + protected function _mail($template, array $variables = array()) + { + $mailTemplate = Mage::getModel('core/email_template'); + $mailTemplate->setDesignConfig(array('area' => 'frontend')) + ->sendTransactional( + $this->getConfig($template), + $this->getConfig('email_sender_identity'), + $this->getConfig('report_email'), + null, + $variables + ); + return $this; + } + + /** + * rewrited for Mage_Payment_Model_Method_Abstract + */ + public function getOrderPlaceRedirectUrl() + { + return Mage::getUrl('amazonpayments/asp/pay'); + } + + /** + * Return Amazon Simple Pay payment url + * + * @return string + */ + public function getPayRedirectUrl() + { + return $this->getApi()->getPayUrl(); + } + + /** + * Return pay params for current order + * + * @return array + */ + public function getPayRedirectParams() + { + $orderId = $this->getOrder()->getRealOrderId(); + $amount = Mage::app()->getStore()->roundPrice($this->getOrder()->getBaseGrandTotal()); + $currencyCode = $this->getOrder()->getBaseCurrency(); + return $this->getApi()->getPayParams( + $orderId, + $amount, + $currencyCode, + Mage::getUrl('amazonpayments/asp/returnCancel'), + Mage::getUrl('amazonpayments/asp/returnSuccess'), + Mage::getUrl('amazonpayments/asp/notification') + ); + } + + /** + * When a customer redirect to Amazon Simple Pay site + * + * @return Mage_AmazonPayments_Model_Payment_Asp + */ + public function processEventRedirect() + { + $this->getOrder()->addStatusToHistory( + $this->getOrder()->getStatus(), + Mage::helper('amazonpayments')->__('Customer was redirected to Amazon Simple Pay site') + )->save(); + return $this; + } + + /** + * When a customer successfully returned from Amazon Simple Pay site + * + * @return Mage_AmazonPayments_Model_Payment_Asp + */ + public function processEventReturnSuccess() + { + $this->getOrder()->addStatusToHistory( + $this->getOrder()->getStatus(), + Mage::helper('amazonpayments')->__('Customer successfully returned from Amazon Simple Pay site') + )->save(); + return $this; + } + + /** + * Customer canceled payment and successfully returned from Amazon Simple Pay site + * + * @return Mage_AmazonPayments_Model_Payment_Asp + */ + public function processEventReturnCancel() + { + $this->getOrder()->addStatusToHistory( + $this->getOrder()->getStatus(), + Mage::helper('amazonpayments')->__('Customer canceled payment and successfully returned from Amazon Simple Pay site') + )->save(); + return $this; + } + + /** + * rewrited for Mage_Payment_Model_Method_Abstract + */ + public function initialize($paymentAction, $stateObject) + { + $state = Mage_Sales_Model_Order::STATE_NEW; + $stateObject->setState($state); + $stateObject->setStatus(Mage::getSingleton('sales/order_config')->getStateDefaultStatus($state)); + $stateObject->setIsNotified(false); + return $this; + } + + /** + * process Amazon Simple Pay notification request + * + * @param array $requestParams + * @return Mage_AmazonPayments_Model_Payment_Asp + */ + public function processNotification($requestParams) + { + if ($this->getConfig('debug_log')) { + $this->_log('DEBUG ASP notification: ' . print_r($requestParams, 1)); + } + + try { + $this->getNotification()->setPayment($this)->process($requestParams); + } catch(Exception $e) { + if ($this->getConfig('error_log')) { + $this->_log('ERROR ASP notification: ' . print_r($requestParams, 1), $e->getMessage()); + } + + if ($this->getConfig('report_error_to_email')) { + $variables = array(); + $variables['request'] = print_r($requestParams, 1); + $variables['error'] = $e->getMessage(); + $this->_mail('email_template_notofication_error', $variables); + } + } + + return $this; + } + + /** + * rewrited for Mage_Payment_Model_Method_Abstract + */ + public function capture(Varien_Object $payment, $amount) + { + if (is_null($payment->getCcTransId())) { + Mage::throwException( + Mage::helper('amazonpayments')->__('Order was not captured online. Authorization confirmation is required.') + ); + } + return $this; + } + + /** + * rewrited for Mage_Payment_Model_Method_Abstract + */ + public function processInvoice($invoice, $payment) + { + if (!is_null($payment->getCcTransId()) && + is_null($payment->getLastTransId()) && + is_null($invoice->getTransactionId())) { + + $amount = Mage::app()->getStore()->roundPrice($invoice->getBaseGrandTotal()); + $currencyCode = $payment->getOrder()->getBaseCurrency(); + $transactionId = $payment->getCcTransId(); + $response = $this->getApi() + ->setStoreId($payment->getOrder()->getStoreId()) + ->capture($transactionId, $amount, $currencyCode); + + if ($response->getStatus() == Mage_AmazonPayments_Model_Api_Asp_Fps_Response_Abstract::STATUS_ERROR) { + Mage::throwException( + Mage::helper('amazonpayments')->__('Order was not captured. Amazon Simple Pay service error: [%s] %s', $response->getCode(), $response->getMessage()) + ); + } + + if ($response->getStatus() == Mage_AmazonPayments_Model_Api_Asp_Fps_Response_Abstract::STATUS_SUCCESS || + $response->getStatus() == Mage_AmazonPayments_Model_Api_Asp_Fps_Response_Abstract::STATUS_PENDING) { + + $payment->setForcedState(Mage_Sales_Model_Order_Invoice::STATE_OPEN); + $payment->setLastTransId($response->getTransactionId()); + + $invoice->setTransactionId($response->getTransactionId()); + $invoice->addComment(Mage::helper('amazonpayments')->__('Invoice was created (online capture). Waiting for capture confirmation from Amazon Simple Pay service.')); + + $payment->getOrder()->addStatusToHistory( + $payment->getOrder()->getStatus(), + Mage::helper('amazonpayments')->__('Payment was captured online with Amazon Simple Pay service. Invoice was created. Waiting for capture confirmation from payment service.') + )->save(); + + } + } + return $this; + } + + /** + * rewrited for Mage_Payment_Model_Method_Abstract + */ + public function processCreditmemo($creditmemo, $payment) + { + + $transactionId = $creditmemo->getInvoice()->getTransactionId(); + + if (!is_null($transactionId) && + is_null($creditmemo->getTransactionId())) { + + $amount = Mage::app()->getStore()->roundPrice($creditmemo->getBaseGrandTotal()); + $currencyCode = $payment->getOrder()->getBaseCurrency(); + $referenseID = $creditmemo->getInvoice()->getIncrementId(); + $response = $this->getApi() + ->setStoreId($payment->getOrder()->getStoreId()) + ->refund($transactionId, $amount, $currencyCode, $referenseID); + + if ($response->getStatus() == Mage_AmazonPayments_Model_Api_Asp_Fps_Response_Abstract::STATUS_ERROR) { + Mage::throwException( + Mage::helper('amazonpayments')->__('Invoice was not refunded. Amazon Simple Pay service error: [%s] %s', $response->getCode(), $response->getMessage()) + ); + } + + if ($response->getStatus() == Mage_AmazonPayments_Model_Api_Asp_Fps_Response_Abstract::STATUS_SUCCESS || + $response->getStatus() == Mage_AmazonPayments_Model_Api_Asp_Fps_Response_Abstract::STATUS_PENDING) { + + $creditmemo->setTransactionId($response->getTransactionId()); + $creditmemo->addComment(Mage::helper('amazonpayments')->__('Payment refunded online. Waiting for refund confirmation from Amazon Simple Pay service.')); + $creditmemo->setState(Mage_Sales_Model_Order_Creditmemo::STATE_OPEN); + + $payment->getOrder()->addStatusToHistory( + $payment->getOrder()->getStatus(), + Mage::helper('amazonpayments')->__('Payment refunded online with Amazon Simple Pay service. Creditmemo was created. Waiting for refund confirmation from Amazon Simple Pay service.') + )->save(); + } + } + return $this; + } + + /** + * rewrited for Mage_Payment_Model_Method_Abstract + */ + public function cancel(Varien_Object $payment) + { + if (!is_null($payment->getCcTransId()) && + is_null($payment->getLastTransId())) { + + $transactionId = $payment->getCcTransId(); + $response = $this->getApi() + ->setStoreId($payment->getOrder()->getStoreId()) + ->cancel($transactionId); + + if ($response->getStatus() == Mage_AmazonPayments_Model_Api_Asp_Fps_Response_Abstract::STATUS_ERROR) { + Mage::throwException( + Mage::helper('amazonpayments')->__('Order was not cancelled. Amazon Simple Pay service error: [%s] %s', $response->getCode(), $response->getMessage()) + ); + } + + if ($response->getStatus() == Mage_AmazonPayments_Model_Api_Asp_Fps_Response_Abstract::STATUS_CANCELLED) { + $payment->getOrder()->setState( + Mage_Sales_Model_Order::STATE_CANCELED, + true, + Mage::helper('amazonpayments')->__('Payment authorization cancelled with Amazon Simple Pay service.'), + $notified = false + )->save(); + } + } + return $this; + } +} diff --git a/app/code/core/Mage/AmazonPayments/Model/Payment/Asp/Abstract.php b/app/code/core/Mage/AmazonPayments/Model/Payment/Asp/Abstract.php new file mode 100644 index 0000000000..808e397b69 --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/Model/Payment/Asp/Abstract.php @@ -0,0 +1,30 @@ +getApi()->getFps()->refund($reserveTransactionId, $callerReference); + } + +} diff --git a/app/code/core/Mage/AmazonPayments/Model/Payment/Asp/Notification.php b/app/code/core/Mage/AmazonPayments/Model/Payment/Asp/Notification.php new file mode 100644 index 0000000000..3a6e42ecba --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/Model/Payment/Asp/Notification.php @@ -0,0 +1,525 @@ + + */ +class Mage_AmazonPayments_Model_Payment_Asp_Notification extends Varien_Object +{ + /* + * payment Model + */ + protected $_payment; + + /** + * Set payment Model + * + * @param Mage_AmazonPayments_Model_Payment_Asp $payment + * @return Mage_AmazonPayments_Model_Payment_Asp_Notification + */ + public function setPayment($payment) + { + $this->_payment = $payment; + return $this; + } + + /** + * Get payment Model + * + * @return Mage_AmazonPayments_Model_Payment_Asp + */ + public function getPayment() + { + return $this->_payment; + } + + /** + * process notification request + * + * @param array $requestParams + */ + public function process($requestParams) + { + $request = $this->getPayment()->getApi()->processNotification($requestParams); + + if ($request->getStatus() == Mage_AmazonPayments_Model_Api_Asp_Ipn_Request::STATUS_CANCEL_TRANSACTION) { + return true; + } + + $order = $this->_getRequestOrder($request); + switch ($request->getStatus()) { + case Mage_AmazonPayments_Model_Api_Asp_Ipn_Request::STATUS_CANCEL_CUSTOMER: + $this->_processCancel($request, $order); + break; + case Mage_AmazonPayments_Model_Api_Asp_Ipn_Request::STATUS_RESERVE_SUCCESSFUL: + $this->_processReserveSuccess($request, $order); + break; + case Mage_AmazonPayments_Model_Api_Asp_Ipn_Request::STATUS_PAYMENT_INITIATED: + $this->_processPaymetInitiated($request, $order); + break; + case Mage_AmazonPayments_Model_Api_Asp_Ipn_Request::STATUS_PAYMENT_SUCCESSFUL: + $this->_processPaymentSuccessful($request, $order); + break; + case Mage_AmazonPayments_Model_Api_Asp_Ipn_Request::STATUS_PAYMENT_FAILED: + $this->_processPaymentFailed($request, $order); + break; + case Mage_AmazonPayments_Model_Api_Asp_Ipn_Request::STATUS_REFUND_SUCCESSFUL: + $this->_processRefundSuccessful($request, $order); + break; + case Mage_AmazonPayments_Model_Api_Asp_Ipn_Request::STATUS_REFUND_FAILED: + $this->_processRefundFailed($request, $order); + break; + case Mage_AmazonPayments_Model_Api_Asp_Ipn_Request::STATUS_SYSTEM_ERROR: + $this->_processSystemError($request, $order); + break; + } + $order->save(); + } + + /** + * When a customer cancel payment + */ + protected function _processCancel($request, $order) + { + if ($order->getState() == Mage_Sales_Model_Order::STATE_CANCELED) { + $order->addStatusToHistory( + $order->getStatus(), + Mage::helper('amazonpayments')->__('Amazon Simple Pay service confirmed cancelation.') + ); + return true; + } + + if ($order->getState() == Mage_Sales_Model_Order::STATE_NEW) { + $order->addStatusToHistory( + $order->getStatus(), + Mage::helper('amazonpayments')->__('Amazon Simple Pay service confirmed cancelation.') + )->cancel(); + return true; + } + + $this->_errorViolationSequenceStates($request, $order); + } + + /** + * When a authorize payment + */ + protected function _processReserveSuccess($request, $order) + { + if ($order->getState() != Mage_Sales_Model_Order::STATE_NEW) { + $this->_errorViolationSequenceStates($request, $order); + } + + $order->getPayment()->setCcTransId($request->getTransactionId()); + + $order->setState( + Mage_Sales_Model_Order::STATE_PENDING_PAYMENT, + 'pending_amazon_asp', + Mage::helper('amazonpayments')->__('Amazon Simple Pay service confirmed amount authorization.'), + $notified = false + ); + + return true; + } + + /** + * When a initiation capture payment + */ + protected function _processPaymetInitiated($request, $order) + { + if ($order->getState() != Mage_Sales_Model_Order::STATE_NEW && + $order->getState() != Mage_Sales_Model_Order::STATE_PENDING_PAYMENT) { + $this->_errorViolationSequenceStates($request, $order); + } + + $order->setState( + Mage_Sales_Model_Order::STATE_PENDING_PAYMENT, + 'pending_amazon_asp', + Mage::helper('amazonpayments')->__('Amazon Simple Pay service confirmed capture initiation.'), + $notified = false + ); + + return true; + } + + /** + * When a capture payment + */ + protected function _processPaymentSuccessful($request, $order) + { + if ($order->getState() != Mage_Sales_Model_Order::STATE_NEW && + $order->getState() != Mage_Sales_Model_Order::STATE_PENDING_PAYMENT && + $order->getState() != Mage_Sales_Model_Order::STATE_PROCESSING) { + $this->_errorViolationSequenceStates($request, $order); + } + + $msg = ''; + + if (!$invoice = $this->_getOrderInvoice($order)) { + + $orderAmount = Mage::app()->getStore()->roundPrice($order->getBaseGrandTotal()); + $requestAmount = Mage::app()->getStore()->roundPrice($request->getAmount()); + if ($orderAmount != $requestAmount) { + $this->_error( + Mage::helper('amazonpayments')->__('Amazon Simple Pay service capture confirmation error: confirmation request amount not equal to the amount of order.'), + $request, + $order + ); + } + + $invoice = $order->prepareInvoice(); + $invoice->register()->pay(); + $invoice->addComment(Mage::helper('amazonpayments')->__('Amazon Simple Pay service confirmed payment capture. Invoice created automatically.')); + $invoice->setTransactionId($request->getTransactionId()); + + $transactionSave = Mage::getModel('core/resource_transaction') + ->addObject($invoice) + ->addObject($invoice->getOrder()) + ->save(); + + $msg = $msg . Mage::helper('amazonpayments')->__('Amazon Simple Pay service confirmed payment capture. Invoice %s was automatically created after confirmation.', $invoice->getIncrementId()); + + } else { + + if ($invoice->getTransactionId() != $request->getTransactionId()) { + $this->_error( + Mage::helper('amazonpayments')->__('Amazon Simple Pay service capture confirmation error: existing transaction ID doesn\'t match transaction ID in the confirmation request.'), + $request, + $order + ); + } + + $invoiceAmount = Mage::app()->getStore()->roundPrice($invoice->getGrandTotal()); + $requestAmount = Mage::app()->getStore()->roundPrice($request->getAmount()); + if ($invoiceAmount != $requestAmount) { + $this->_error( + Mage::helper('amazonpayments')->__('Amazon Simple Pay service capture confirmation error: amount in the existing invoice doensn\'t match the amount in confirmation request.'), + $request, + $order + ); + } + + switch ($invoice->getState()) + { + case Mage_Sales_Model_Order_Invoice::STATE_OPEN: + $invoice->addComment(Mage::helper('amazonpayments')->__('Amazon Simple Pay service capture confirmation. Invoice was captured automatically.')); + $invoice->setState(Mage_Sales_Model_Order_Invoice::STATE_PAID)->save(); + $msg = $msg . Mage::helper('amazonpayments')->__('Amazon Simple Pay service confirmed capture for invoice %s. Invoice automatically captured.', $invoice->getIncrementId()); + break; + + case Mage_Sales_Model_Order_Invoice::STATE_PAID: + $invoice->addComment(Mage::helper('amazonpayments')->__('Amazon Simple Pay service confirmed capture')); + $msg = $msg . Mage::helper('amazonpayments')->__('Amazon Simple Pay service confirmed capture for invoice %s', $invoice->getIncrementId()); + break; + } + + } + + $order->getPayment()->getLastTransId($request->getTransactionId()); + $order->addStatusToHistory($order->getStatus(), $msg); + $order->setState( + Mage_Sales_Model_Order::STATE_PROCESSING, + true, + Mage::helper('amazonpayments')->__('Payment was authorized and captured successfully'), + $notified = true + ); + + return true; + } + + /** + * When a failed capture payment + */ + protected function _processPaymentFailed($request, $order) + { + if ($order->getState() != Mage_Sales_Model_Order::STATE_NEW && + $order->getState() != Mage_Sales_Model_Order::STATE_PENDING_PAYMENT) { + $this->_errorViolationSequenceStates($request, $order); + } + + $order->setState( + Mage_Sales_Model_Order::STATE_CANCELED, + true, + Mage::helper('amazonpayments')->__('Amazon Simple Pay service payment confirmation failed'), + $notified = false + ); + + return true; + } + + /** + * When a refund payment + */ + protected function _processRefundSuccessful($request, $order) + { + if ($order->getState() != Mage_Sales_Model_Order::STATE_PROCESSING && + $order->getState() != Mage_Sales_Model_Order::STATE_CLOSED && + $order->getState() != Mage_Sales_Model_Order::STATE_COMPLETE) { + $this->_errorViolationSequenceStates($request, $order); + } + + $msg = ''; + + if (!$creditmemo = $this->_getOrderCreditmemo($order)) { + + $orderAmount = Mage::app()->getStore()->roundPrice($order->getBaseGrandTotal()); + $requestAmount = Mage::app()->getStore()->roundPrice($request->getAmount()); + if ($orderAmount != $requestAmount || $order->getBaseTotalRefunded() > 0) { + $this->_error( + Mage::helper('amazonpayments')->__('Amazon Simple Pay service refund confirmation error: confirmation request amount doesn\'t match the order amount.'), + $request, + $order + ); + } + + if ($creditmemo = $this->_initCreditmemo($order)) { + $creditmemo->setTransactionId($request->getTransactionId()); + $creditmemo->addComment(Mage::helper('amazonpayments')->__('Amazon Simple Pay service confirmed payment refund. Creditmemo %s was automatically created after confirmation.', $creditmemo->getIncrementId())); + $creditmemo->register(); + + $transactionSave = Mage::getModel('core/resource_transaction') + ->addObject($creditmemo) + ->addObject($creditmemo->getOrder()); + if ($creditmemo->getInvoice()) { + $transactionSave->addObject($creditmemo->getInvoice()); + } + $transactionSave->save(); + + $msg = $msg . Mage::helper('amazonpayments')->__('Amazon Simple Pay service confirmed payment refund. Credit memo created automatically.', $creditmemo->getIncrementId()); + } + + } else { + + if ($creditmemo->getTransactionId() != $request->getTransactionId()) { + $this->_error( + Mage::helper('amazonpayments')->__('Amazon Simple Pay service refund confirmation error: transaction ID in the existing creditmemo doesn\'t match the transaction ID in the confirmation request.'), + $request, + $order + ); + } + + $creditmemoAmount = Mage::app()->getStore()->roundPrice($creditmemo->getBaseGrandTotal()); + $requestAmount = Mage::app()->getStore()->roundPrice($request->getAmount()); + if ($creditmemoAmount != $requestAmount) { + $this->_error( + Mage::helper('amazonpayments')->__('Amazon Simple Pay service refund confirmation error: amount in the existing creditmemo doensn\'t match the amount in confirmation request.'), + $request, + $order + ); + } + + switch ($creditmemo->getState()) + { + case Mage_Sales_Model_Order_Creditmemo::STATE_OPEN: + $creditmemo->addComment(Mage::helper('amazonpayments')->__('Amazon Simple Pay service confirmed refund. Creditmemo processed automatically.')); + $creditmemo->setState(Mage_Sales_Model_Order_Creditmemo::STATE_REFUNDED)->save(); + $msg = $msg . Mage::helper('amazonpayments')->__('Amazon Simple Pay service confirmed refunded creditmemo %s. Creditmemo processed automatically.', $creditmemo->getIncrementId()); + break; + + case Mage_Sales_Model_Order_Creditmemo::STATE_REFUNDED: + $creditmemo->addComment(Mage::helper('amazonpayments')->__('Amazon Simple Pay service confirmed refund.')); + $msg = $msg . Mage::helper('amazonpayments')->__('Amazon Simple Pay service confirmed refund for creditmemo %s.', $creditmemo->getIncrementId()); + break; + } + + } + + $order->addStatusToHistory($order->getStatus(), $msg); + + return true; + } + + /** + * When a failed refund payment + */ + protected function _processRefundFailed($request, $order) + { + $order->setState( + $order->getState(), + true, + Mage::helper('amazonpayments')->__('Amazon Simple Pay service payment confirmation failed'), + $notified = false + ); + + return true; + } + + /** + * When a Amazon Simple Pay system error + */ + protected function _processSystemError($request, $order) + { + $order->setState( + Mage_Sales_Model_Order::STATE_CANCELED, + true, + Mage::helper('amazonpayments')->__('Amazon Simple Pay service is not available. Payment was not processed.'), + $notified = false + ); + + return true; + } + + /** + * Return order model for current request + * + * @param array $request + * @return Mage_Sales_Model_Order + */ + protected function _getRequestOrder($request) + { + $order = Mage::getModel('sales/order'); + $order->loadByIncrementId($request->getReferenceId()); + + if ($order->isEmpty()) { + $this->_error( + Mage::helper('amazonpayments')->__('Amazon Simple Pay service confirmation error: order specified in the IPN request can not be found'), + $request + ); + } + + if ($order->getPayment()->getMethodInstance()->getCode() != $this->getPayment()->getCode()) { + $this->_error( + Mage::helper('amazonpayments')->__('Amazon Simple Pay service confirmation error: payment method in the order is not Amazon Simple Pay'), + $request + ); + } + + if ($order->getBaseCurrency()->getCurrencyCode() != $request->getCurrencyCode()) { + $this->_error( + Mage::helper('amazonpayments')->__('Amazon Simple Pay service confirmation error: order currency does not match the currency of the IPN request'), + $request + ); + } + + return $order; + } + + /** + * Return invoice model for current order + * + * @param Mage_Sales_Model_Order $order + * @return Mage_Sales_Model_Order_Invoice + */ + protected function _getOrderInvoice($order) + { + foreach ($order->getInvoiceCollection() as $orderInvoice) { + if ($orderInvoice->getState() == Mage_Sales_Model_Order_Invoice::STATE_PAID || + $orderInvoice->getState() == Mage_Sales_Model_Order_Invoice::STATE_OPEN) { + return $orderInvoice; + } + } + + return false; + } + + /** + * Create and return creditmemo for current order + * + * @param Mage_Sales_Model_Order $order + * @return Mage_Sales_Model_Order_Creditmemo + */ + protected function _initCreditMemo($order) + { + $invoice = $this->_getOrderInvoice($order); + + if (!$invoice) { + return false; + } + + $convertor = Mage::getModel('sales/convert_order'); + $creditmemo = $convertor->toCreditmemo($order)->setInvoice($invoice); + + foreach ($invoice->getAllItems() as $invoiceItem) { + $orderItem = $invoiceItem->getOrderItem(); + $item = $convertor->itemToCreditmemoItem($orderItem); + $item->setQty($orderItem->getQtyToRefund()); + $creditmemo->addItem($item); + } + + $creditmemo->setShippingAmount($invoice->getShippingAmount()); + $creditmemo->collectTotals(); + Mage::register('current_creditmemo', $creditmemo); + + return $creditmemo; + } + + /** + * Return creditmemo model for current order + * + * @param Mage_Sales_Model_Order $order + * @return Mage_Sales_Model_Order_Creditmemo + */ + protected function _getOrderCreditmemo($order) + { + foreach ($order->getCreditmemosCollection() as $orderCreditmemo) { + if ($orderCreditmemo->getState() == Mage_Sales_Model_Order_Creditmemo::STATE_REFUNDED || + $orderCreditmemo->getState() == Mage_Sales_Model_Order_Creditmemo::STATE_OPEN) { + return $orderCreditmemo; + } + } + + return false; + } + + /** + * Process error: order states sequence violation + * + * @param array $request + * @param Mage_Sales_Model_Order $order + */ + protected function _errorViolationSequenceStates($request, $order) + { + $this->_error( + Mage::helper('amazonpayments')->__('Amazon Simple Pay service confirmation error: order states sequence violation'), + $request, + $order + ); + } + + /** + * Process error + * + * @param string $comment + * @param array $request + * @param Mage_Sales_Model_Order $order + */ + protected function _error($comment, $request, $order = null) + { + $message = $comment . Mage::helper('amazonpayments')->__('
Trace confirmation request:
%s', $request->toString()); + + if (!is_null($order)) { + $order->addStatusToHistory( + $order->getStatus(), + $message + )->save(); + } + + Mage::throwException($comment); + } +} diff --git a/app/code/core/Mage/AmazonPayments/Model/Payment/Asp/Source/PaymentAction.php b/app/code/core/Mage/AmazonPayments/Model/Payment/Asp/Source/PaymentAction.php new file mode 100644 index 0000000000..f30fff4267 --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/Model/Payment/Asp/Source/PaymentAction.php @@ -0,0 +1,49 @@ + + */ +class Mage_AmazonPayments_Model_Payment_Asp_Source_PaymentAction +{ + public function toOptionArray() + { + return array( + array( + 'value' => Mage_AmazonPayments_Model_Api_Asp_Abstract::PAY_ACTION_SETTLE, + 'label' => Mage::helper('amazonpayments')->__('Authorize Only') + ), + array( + 'value' => Mage_AmazonPayments_Model_Api_Asp_Abstract::PAY_ACTION_SETTLE_CAPTURE, + 'label' => Mage::helper('amazonpayments')->__('Authorize and Capture') + ), + ); + } +} \ No newline at end of file diff --git a/app/code/core/Mage/AmazonPayments/Model/Payment/Cba.php b/app/code/core/Mage/AmazonPayments/Model/Payment/Cba.php new file mode 100644 index 0000000000..33faab2be0 --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/Model/Payment/Cba.php @@ -0,0 +1,560 @@ +_api) { + $this->_api = Mage::getSingleton('amazonpayments/api_cba'); + $this->_api->setPaymentCode($this->getCode()); + } + return $this->_api; + } + + /** + * Get AmazonPayments session namespace + * + * @return Mage_AmazonPayments_Model_Session + */ + public function getSession() + { + return Mage::getSingleton('amazonpayments/session'); + } + + /** + * Retrieve redirect url + * + * @return string + */ + public function getRedirectUrl() + { + return Mage::getUrl('amazonpayments/cba/redirect'); + } + + /** + * Retrieve redirect to Amazon CBA url + * + * @return string + */ + public function getAmazonRedirectUrl() + { + return $this->getApi()->getAmazonRedirectUrl(); + } + + /** + * Authorize + * + * @param Varien_Object $orderPayment + * @return Mage_Payment_Model_Abstract + */ + public function authorize(Varien_Object $payment, $amount) + { + parent::authorize($payment, $amount); + return $this; + } + + /** + * Capture payment + * + * @param Varien_Object $orderPayment + * @return Mage_Payment_Model_Abstract + */ + public function capture(Varien_Object $payment, $amount) + { + parent::capture($payment, $amount); + return $this; + } + + public function cancel(Varien_Object $payment) + { + if ($this->_skipProccessDocument) { + return $this; + } + $this->getApi()->cancel($payment->getOrder()); + return $this; + } + + /** + * Refund order + * + * @param Varien_Object $payment + * @return Mage_AmazonPayments_Model_Payment_Cba + */ + public function refund(Varien_Object $payment, $amount) + { + if ($this->_skipProccessDocument) { + return $this; + } + $this->getApi()->refund($payment, $amount); + return $this; + } + + /** + * Handle Callback from CBA and calculate Shipping, Taxes in case XML-based shopping cart + * + */ + public function handleCallback($_request) + { + $response = ''; + + if (!empty($_request['order-calculations-request'])) { + $xmlRequest = urldecode($_request['order-calculations-request']); + + $session = $this->getCheckout(); + $xml = $this->getApi()->handleXmlCallback($xmlRequest, $session); + + if ($this->getDebug()) { + $debug = Mage::getModel('amazonpayments/api_debug') + ->setRequestBody(print_r($_request, 1)) + ->setResponseBody(time().' - request callback') + ->save(); + } + + if ($xml) { + $xmlText = $xml->asXML(); + $response .= 'order-calculations-response='.urlencode($xmlText); + #$response .= 'order-calculations-response='.base64_encode($xmlText); + + $secretKeyID = Mage::getStoreConfig('payment/amazonpayments_cba/secretkey_id'); + + $_signature = $this->getApi()->calculateSignature($xmlText, $secretKeyID); + + if ($_signature) { + $response .= '&Signature='.urlencode($_signature); + #$response .= '&Signature='.$_signature; + } + $response .= '&aws-access-key-id='.urlencode(Mage::getStoreConfig('payment/amazonpayments_cba/accesskey_id')); + + if ($this->getDebug()) { + $debug = Mage::getModel('amazonpayments/api_debug') + ->setResponseBody($response) + ->setRequestBody(time() .' - response calllback') + ->save(); + } + } + } else { + if ($this->getDebug()) { + $debug = Mage::getModel('amazonpayments/api_debug') + ->setRequestBody(print_r($_request, 1)) + ->setResponseBody(time().' - error request callback') + ->save(); + } + } + return $response; + } + + public function handleNotification($_request) + { + if (!empty($_request) && !empty($_request['NotificationData']) && !empty($_request['NotificationType'])) { + /** + * Debug + */ + if ($this->getDebug()) { + $debug = Mage::getModel('amazonpayments/api_debug') + ->setRequestBody(print_r($_request, 1)) + ->setResponseBody(time().' - Notification: '. $_request['NotificationType']) + ->save(); + } + switch ($_request['NotificationType']) { + case 'NewOrderNotification': + $newOrderDetails = $this->getApi()->parseOrder($_request['NotificationData']); + $this->_createNewOrder($newOrderDetails); + break; + case 'OrderReadyToShipNotification': + $amazonOrderDetails = $this->getApi()->parseOrder($_request['NotificationData']); + $this->_proccessOrder($amazonOrderDetails); + break; + case 'OrderCancelledNotification': + $cancelDetails = $this->getApi()->parseCancelNotification($_request['NotificationData']); + $this->_skipProccessDocument = true; + $this->_cancelOrder($cancelDetails); + $this->_skipProccessDocument = false; + break; + default: + // Unknown notification type + } + } else { + if ($this->getDebug()) { + $debug = Mage::getModel('amazonpayments/api_debug') + ->setRequestBody(print_r($_request, 1)) + ->setResponseBody(time().' - error request callback') + ->save(); + } + } + return $this; + } + + /** + * Create new order by data from Amazon NewOrderNotification + * + * @param array $newOrderDetails + */ + protected function _createNewOrder(array $newOrderDetails) + { + if (array_key_exists('amazonOrderID', $newOrderDetails)) { + $_order = Mage::getModel('sales/order') + ->loadByAttribute('ext_order_id', $newOrderDetails['amazonOrderID']); + if ($_order->getId()) { + $_order = null; + return $this; + } + $_order = null; + } + $session = $this->getCheckout(); + + #$quoteId = $session->getAmazonQuoteId(); + + $quoteId = $newOrderDetails['ClientRequestId']; + $quote = Mage::getModel('sales/quote')->load($quoteId); + + $baseCurrency = $session->getQuote()->getBaseCurrencyCode(); + $currency = Mage::app()->getStore($session->getQuote()->getStoreId())->getBaseCurrency(); + + $shipping = $quote->getShippingAddress(); + $billing = $quote->getBillingAddress(); + + $_address = $newOrderDetails['shippingAddress']; + $this->_address = $_address; + + $regionModel = Mage::getModel('directory/region')->loadByCode($_address['regionCode'], $_address['countryCode']); + $_regionId = $regionModel->getId(); + + $sName = explode(' ', $newOrderDetails['shippingAddress']['name']); + $sFirstname = isset($sName[0])?$sName[0]:''; + $sLastname = isset($sName[1])?$sName[1]:''; + + $bName = explode(' ', $newOrderDetails['buyerName']); + $bFirstname = isset($bName[0])?$bName[0]:''; + $bLastname = isset($bName[1])?$bName[1]:''; + + $shipping->setCountryId($_address['countryCode']) + ->setRegion($_address['regionCode']) + ->setRegionId($_regionId) + ->setCity($_address['city']) + ->setStreet($_address['street']) + ->setPostcode($_address['postCode']) + ->setTaxAmount($newOrderDetails['tax']) + ->setBaseTaxAmount($newOrderDetails['tax']) + ->setShippingAmount($newOrderDetails['shippingAmount']) + ->setBaseShippingAmount($newOrderDetails['shippingAmount']) + ->setShippingTaxAmount($newOrderDetails['shippingTax']) + ->setBaseShippingTaxAmount($newOrderDetails['shippingTax']) + ->setDiscountAmount($newOrderDetails['discount']) + ->setBaseDiscountAmount($newOrderDetails['discount']) + ->setSubtotal($newOrderDetails['subtotal']) + ->setBaseSubtotal($newOrderDetails['subtotal']) + ->setGrandTotal($newOrderDetails['total']) + ->setBaseGrandTotal($newOrderDetails['total']) + ->setFirstname($sFirstname) + ->setLastname($sLastname); + + $_shippingDesc = ''; + $_shippingServices = unserialize($quote->getExtShippingInfo()); + if (is_array($_shippingServices) && array_key_exists('amazon_service_level', $_shippingServices)) { + foreach ($_shippingServices['amazon_service_level'] as $_level) { + if ($_level['service_level'] == $newOrderDetails['ShippingLevel']) { + $shipping->setShippingMethod($_level['code']); + $_shippingDesc = $_level['description']; + } + } + } + /** @todo save shipping method */ +// $this->getQuote()->getShippingAddress()->setShippingMethod($shippingMethod); + + $billing->setCountryId($_address['countryCode']) + ->setRegion($_address['regionCode']) + ->setRegionId($_regionId) + ->setCity($_address['city']) + ->setStreet($_address['street']) + ->setPostcode($_address['postCode']) + ->setTaxAmount($newOrderDetails['tax']) + ->setBaseTaxAmount($newOrderDetails['tax']) + ->setShippingAmount($newOrderDetails['shippingAmount']) + ->setBaseShippingAmount($newOrderDetails['shippingAmount']) + ->setShippingTaxAmount($newOrderDetails['shippingTax']) + ->setBaseShippingTaxAmount($newOrderDetails['shippingTax']) + ->setDiscountAmount($newOrderDetails['discount']) + ->setBaseDiscountAmount($newOrderDetails['discount']) + ->setSubtotal($newOrderDetails['subtotal']) + ->setBaseSubtotal($newOrderDetails['subtotal']) + ->setGrandTotal($newOrderDetails['total']) + ->setBaseGrandTotal($newOrderDetails['total']) + ->setFirstname($bFirstname) + ->setLastname($bLastname); + + $quote->setBillingAddress($billing); + $quote->setShippingAddress($shipping); + + $billing = $quote->getBillingAddress(); + $shipping = $quote->getShippingAddress(); + + $convertQuote = Mage::getModel('sales/convert_quote'); + /* @var $convertQuote Mage_Sales_Model_Convert_Quote */ + $order = Mage::getModel('sales/order'); + /* @var $order Mage_Sales_Model_Order */ + + $order = $convertQuote->addressToOrder($billing); + + // add payment information to order + $order->setBillingAddress($convertQuote->addressToOrderAddress($billing)) + ->setShippingAddress($convertQuote->addressToOrderAddress($shipping)); + + $order->setShippingMethod($shipping->getShippingMethod()); + $order->setShippingDescription($_shippingDesc); + + $order->setPayment($convertQuote->paymentToOrderPayment($quote->getPayment())); + + /** + * Amazon Order Id + */ + $order->setExtOrderId($newOrderDetails['amazonOrderID']); + + // add items to order + foreach ($quote->getAllItems() as $item) { + /* @var $item Mage_Sales_Model_Quote_Item */ + $order->addItem($convertQuote->itemToOrderItem($item)); + $orderItem = $order->getItemByQuoteItemId($item->getId()); + /* @var $orderItem Mage_Sales_Model_Order_Item */ + $orderItem->setExtOrderItemId($newOrderDetails['items'][$item->getId()]['AmazonOrderItemCode']); + $orderItemOptions = $orderItem->getProductOptions(); + $orderItemOptions['amazon_amounts'] = serialize(array( + 'shipping' => $newOrderDetails['items'][$item->getId()]['shipping'], + 'tax' => $newOrderDetails['items'][$item->getId()]['tax'], + 'shipping_tax' => $newOrderDetails['items'][$item->getId()]['shipping_tax'], + 'principal_promo' => $newOrderDetails['items'][$item->getId()]['principal_promo'], + 'shipping_promo' => $newOrderDetails['items'][$item->getId()]['shipping_promo'] + )); + $orderItem->setProductOptions($orderItemOptions); + } + + $order->place(); + + $customer = $quote->getCustomer(); + if (isset($customer) && $customer) { // && $quote->getCheckoutMethod()==Mage_Sales_Model_Quote::CHECKOUT_METHOD_REGISTER) { + $order->setCustomerId($customer->getId()) + ->setCustomerEmail($customer->getEmail()) + ->setCustomerPrefix($customer->getPrefix()) + ->setCustomerFirstname($customer->getFirstname()) + ->setCustomerMiddlename($customer->getMiddlename()) + ->setCustomerLastname($customer->getLastname()) + ->setCustomerSuffix($customer->getSuffix()) + ->setCustomerGroupId($customer->getGroupId()) + ->setCustomerTaxClassId($customer->getTaxClassId()); + } + + $order->save(); + + $quote->setIsActive(false); + $quote->save(); + + $orderId = $order->getIncrementId(); + $this->getCheckout()->setLastQuoteId($quote->getId()); + $this->getCheckout()->setLastSuccessQuoteId($quote->getId()); + $this->getCheckout()->setLastOrderId($order->getId()); + $this->getCheckout()->setLastRealOrderId($order->getIncrementId()); + + $order->sendNewOrderEmail(); + return $this; + } + + /** + * Proccess existing order + * + * @param array $amazonOrderDetails + * @return boolean + */ + protected function _proccessOrder($amazonOrderDetails) + { + if ($quoteId = $newOrderDetails['ClientRequestId']) { + if ($order = Mage::getModel('sales/order')->loadByAttribute('quote_id', $quoteId)) { + /* @var $order Mage_Sales_Model_Order */ + + $order->setState(Mage_Sales_Model_Order::STATE_PROCESSING); + $order->setStatus('Processing'); + $order->setIsNotified(false); + $order->save(); + } + } + return true; + } + + /** + * Cancel the order + * + * @param array $amazonOrderDetails + * @return boolean + */ + protected function _cancelOrder($cancelDetails) + { + if (array_key_exists('amazon_order_id', $cancelDetails)) { + $order = Mage::getModel('sales/order') + ->loadByAttribute('ext_order_id', $cancelDetails['amazon_order_id']); + /* @var $order Mage_Sales_Model_Order */ + if ($order->getId()) { + try { + $order->cancel()->save(); + } catch (Exception $e) { + return false; + } + } + } + return true; + } + + /** + * Return xml with error + * + * @param Exception $e + * @return string + */ + + public function callbackXmlError(Exception $e) + { + $_xml = $this->getApi()->callbackXmlError($e); + $secretKeyID = Mage::getStoreConfig('payment/amazonpayments_cba/secretkey_id'); + $_signature = $this->getApi()->calculateSignature($_xml->asXml(), $secretKeyID); + + $response = 'order-calculations-response='.urlencode($_xml->asXML()) + .'&Signature='.urlencode($_signature) + .'&aws-access-key-id='.urlencode(Mage::getStoreConfig('payment/amazonpayments_cba/accesskey_id')); + return $response; + } + + /** + * Prepare fields for XML-based signed cart form for CBA + * + * @return array + */ + public function getCheckoutXmlFormFields() + { + $secretKeyID = Mage::getStoreConfig('payment/amazonpayments_cba/secretkey_id'); + $_quote = $this->getCheckout()->getQuote(); + + $xml = $this->getApi()->getXmlCart($_quote); + + $xmlCart = array('order-input' => + "type:merchant-signed-order/aws-accesskey/1;" + ."order:".base64_encode($xml).";" + ."signature:{$this->getApi()->calculateSignature($xml, $secretKeyID)};" + ."aws-access-key-id:".Mage::getStoreConfig('payment/amazonpayments_cba/accesskey_id') + ); + if ($this->getDebug()) { + $debug = Mage::getModel('amazonpayments/api_debug') + ->setResponseBody(print_r($xmlCart, 1)."\norder:".$xml) + ->setRequestBody(time() .' - xml cart') + ->save(); + } + return $xmlCart; + } + + /** + * Return CBA order details in case Html-based shopping cart commited to Amazon + * + */ + public function returnAmazon() + { + $_request = Mage::app()->getRequest()->getParams(); + #$_amazonOrderId = Mage::app()->getRequest()->getParam('amznPmtsOrderIds'); + #$_quoteId = Mage::app()->getRequest()->getParam('amznPmtsReqId'); + + if ($this->getDebug()) { + $debug = Mage::getModel('amazonpayments/api_debug') + ->setRequestBody(print_r($_request, 1)) + ->setResponseBody(time().' - success') + ->save(); + } + } + + /** + * Rewrite standard logic + * + * @return bool + */ + public function isInitializeNeeded() + { + return false; + } + + /** + * Get debug flag + * + * @return string + */ + public function getDebug() + { + return Mage::getStoreConfig('payment/' . $this->getCode() . '/debug_flag'); + } +} \ No newline at end of file diff --git a/app/code/core/Mage/AmazonPayments/Model/Session.php b/app/code/core/Mage/AmazonPayments/Model/Session.php new file mode 100644 index 0000000000..90c8cd1967 --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/Model/Session.php @@ -0,0 +1,39 @@ + + */ +class Mage_AmazonPayments_Model_Session extends Mage_Core_Model_Session_Abstract +{ + public function __construct() + { + $this->init('amazonpayments'); + } +} \ No newline at end of file diff --git a/app/code/core/Mage/AmazonPayments/controllers/AspController.php b/app/code/core/Mage/AmazonPayments/controllers/AspController.php new file mode 100644 index 0000000000..3d49be1a06 --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/controllers/AspController.php @@ -0,0 +1,141 @@ + + */ +class Mage_AmazonPayments_AspController extends Mage_Core_Controller_Front_Action +{ + /** + * Get singleton with payment model AmazonPayments ASP + * + * @return Mage_AmazonPayments_Model_Payment_Asp + */ + public function getPayment() + { + return Mage::getSingleton('amazonpayments/payment_asp'); + } + + /** + * Get singleton with model checkout session + * + * @return Mage_Checkout_Model_Session + */ + public function getSession() + { + return Mage::getSingleton('checkout/session'); + } + + /** + * When a customer press "Place Order" button on Checkout/Review page + * Redirect customer to Amazon Simple Pay payment interface + * + */ + public function payAction() + { + $session = $this->getSession(); + $session->setAmazonAspQuoteId($session->getQuoteId()); + $session->setAmazonAspLastRealOrderId($session->getLastRealOrderId()); + + $order = Mage::getModel('sales/order'); + $order->loadByIncrementId($session->getLastRealOrderId()); + + $payment = $this->getPayment(); + $payment->setOrder($order); + + $payment->processEventRedirect(); + Mage::register('amazonpayments_payment_asp', $payment); + $this->loadLayout(); + $this->renderLayout(); + + $session->unsQuoteId(); + $session->unsLastRealOrderId(); + } + + /** + * When a customer successfully returned from Amazon Simple Pay site + * Redirect customer to Checkout/Success page + * + */ + public function returnSuccessAction() + { + $session = $this->getSession(); + + $order = Mage::getModel('sales/order'); + $order->loadByIncrementId($session->getAmazonAspLastRealOrderId()); + + if ($order->isEmpty()) { + return false; + } + + $payment = $this->getPayment(); + $payment->setOrder($order); + $payment->processEventReturnSuccess(); + + $session->setQuoteId($session->getAmazonAspQuoteId(true)); + $session->getQuote()->setIsActive(false)->save(); + $session->setLastRealOrderId($session->getAmazonAspLastRealOrderId(true)); + + $this->_redirect('checkout/onepage/success'); + } + + /** + * Customer canceled payment and successfully returned from Amazon Simple Pay site + * Redirect customer to Shopping Cart page + * + */ + public function returnCancelAction() + { + $session = $this->getSession(); + $session->setQuoteId($session->getAmazonAspQuoteId(true)); + + $order = Mage::getModel('sales/order'); + $order->loadByIncrementId($session->getAmazonAspLastRealOrderId()); + + if ($order->isEmpty()) { + return false; + } + + $payment = $this->getPayment(); + $payment->setOrder($order); + $payment->processEventReturnCancel(); + + $this->_redirect('checkout/cart/'); + } + + /** + * Amazon Simple Pay service send notification + * + */ + public function notificationAction() + { + $this->getPayment()->processNotification($this->getRequest()->getParams()); + } +} diff --git a/app/code/core/Mage/AmazonPayments/controllers/CbaController.php b/app/code/core/Mage/AmazonPayments/controllers/CbaController.php new file mode 100644 index 0000000000..8103f9c2b1 --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/controllers/CbaController.php @@ -0,0 +1,167 @@ +getCba()->isAvailable()) { + $this->_redirect('checkout/cart/'); + } + $session = $this->getCheckout(); + if ($quoteId = $this->getCheckout()->getQuoteId()) { + $quote = Mage::getModel('sales/quote')->load($quoteId); + + /** @var $quote Mage_Sales_Model_Quote */ + if ($quote->hasItems()) { + $session->setAmazonQuoteId($quoteId); + + $quote->getPayment()->setMethod($this->getCba()->getCode()); + $quote->setIsActive(false); + $quote->save(); + + $this->getResponse()->setBody($this->getLayout()->createBlock('amazonpayments/cba_redirect')->toHtml()); + + if ($session->hasData('quote_id_'.Mage::app()->getStore()->getWebsiteId())) { + $session->unsetData('quote_id_'.Mage::app()->getStore()->getWebsiteId()); + } + } else { + $this->_redirect('checkout/cart/'); + } + } else { + $this->_redirect('checkout/cart/'); + } + } + + /** + * When a customer has checkout on Amazon and return with Successful payment + * + */ + public function successAction() + { + #$amazonOrderID = Mage::app()->getRequest()->getParam('amznPmtsOrderIds'); + #$referenceId = Mage::app()->getRequest()->getParam('amznPmtsOrderIds'); + + $this->getCba()->returnAmazon(); + + $this->loadLayout(); + #$this->_initLayoutMessages('amazonpayments/session'); + $this->renderLayout(); + } + + /** + * When Amazon return callback request for calculation shipping, taxes and etc. + * + */ + public function callbackAction() + { + $response = ''; + $session = $this->getCheckout(); + + $_request = Mage::app()->getRequest()->getParams(); + + try { + if ($_request) { + $response = $this->getCba()->handleCallback($_request); + } else { + $e = new Exception('Inavlid Shipping Address'); + } + } + catch (Exception $e) { + // Return Xml with Error + $response = $this->getCba()->callbackXmlError($e); + } + echo $response; + exit(0); + } + + public function notificationAction() + { + $response = ''; + $session = $this->getCheckout(); + + $_request = Mage::app()->getRequest()->getParams(); + + try { + $this->getCba() + ->handleNotification($_request); + } + catch (Exception $e) { + // Return Xml with Error + $response = $this->getCba()->callbackXmlError($e); + } + $this->getResponse() + ->setHttpResponseCode(200); + } + + /** + * When a customer has checkout on Amazon and return with Cancel + * + */ + public function cancelAction() + { + $session = $this->getCheckout(); + if ($quoteId = $session->getAmazonQuoteId()) { + $quote = Mage::getModel('sales/quote')->load($quoteId); + $quote->setIsActive(true); + $quote->save(); + $session->setQuoteId($quoteId); + } + if ($this->getCba()->getDebug()) { + $_request = Mage::app()->getRequest()->getParams(); + $debug = Mage::getModel('amazonpayments/api_debug') + ->setResponseBody(print_r($_request, 1)) + ->setRequestBody(time() .' - cancel') + ->save(); + } + $this->_redirect('checkout/cart/'); + } + +} \ No newline at end of file diff --git a/app/code/core/Mage/AmazonPayments/etc/config.xml b/app/code/core/Mage/AmazonPayments/etc/config.xml new file mode 100644 index 0000000000..f31edee80a --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/etc/config.xml @@ -0,0 +1,238 @@ + + + + + + 0.1.2 + + + + + + Mage_AmazonPayments_Model + amazonpayments_mysql4 + + + Mage_AmazonPayments_Model_Mysql4 + + amazonpayments_api_debug
+
+
+
+ + + Mage_AmazonPayments_Helper + + + + + + Mage_AmazonPayments + Mage_AmazonPayments_Model_Mysql4_Setup + + + core_setup + + + + + core_write + + + + + core_read + + + + + Mage_AmazonPayments_Block + + + + + + + + + + + + + + + + + +
+ + + + + + + + Mage_AmazonPayments.csv + + + + + + + standard + + Mage_AmazonPayments + amazonpayments + + + + + + + amazonpayments.xml + + + + + + + + + + + Mage_AmazonPayments.csv + + + + + + + + + model + amazonpayments/observer + confirmShipment + + + + + + + model + amazonpayments/observer + salesOrderShipmentTrackSaveAfter + + + + + + + + + + + 0 + amazonpayments/payment_cba + Checkout by Amazon + pending + 0 + Authorization + + + 1 + + + https://payments.amazon.com/checkout/ + https://payments-sandbox.amazon.com/checkout/ + http://g-ecx.images-amazon.com/images/G/01/cba/images/buttons/btn_Chkout-orange-medium.gif + https://merchant-api.amazon.com/gateway/merchant-interface-mime/ + 1 + 0 + + + + + amazonpayments/payment_asp + Amazon Simple Pay + 0 + + 1 + + + + https://authorize.payments.amazon.com/pba/paypipeline + https://authorize.payments-sandbox.amazon.com/pba/paypipeline + + MAGENTO ASP PAYMENT + MAGENTO ASP REFUND + MAGENTO ASP CANCEL + + 1 + 0 + + 1 + + https://fps.amazonaws.com + https://fps.sandbox.amazonaws.com/ + + general + payment_amazonpayments_asp + 0 + + 0 + 0 + + + + + + + +
diff --git a/app/code/core/Mage/AmazonPayments/etc/system.xml b/app/code/core/Mage/AmazonPayments/etc/system.xml new file mode 100644 index 0000000000..d34fce6834 --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/etc/system.xml @@ -0,0 +1,427 @@ + + + + + + + + + +
+
+


Signing up with Checkout by Amazon

+

To configure Checkout by Amazon™ you will need to enter your Checkout by Amazon™ Merchant ID, Access Key ID, and Secret Access Key.

+

If you do not already have a Checkout by Amazon™ account, click here to create one now. Sign-Up

+

To locate your Merchant ID, sign in to your Seller Central Checkout by Amazon™ account and click Settings > Checkout Pipeline Settings.

+

To locate your Access Key ID and Secret Access Key, sign in to your Seller Central Checkout by Amazon™ account and click Integration > AWS Key. Click the link to read the Amazon Web Services Customer Agreement, and then click the check box, if you are setting up a new Access Key ID.

+

To enable XML Order Reports click Settings > Checkout Pipeline Settings, and then clicking Edit under the Order Report Settings section. Select Order Report Type as XML to get XML Order Reports using SOAP APIs. Configure your downloads for hourly.

+

For additional information on setting up your Checkout by Amazon account, Click Here - FAQ.

+


Signup for Checkout by Amazon

+
+
+]]>
+ text + 601 + 1 + 1 + 0 + + + <label>Title</label> + <frontend_type>text</frontend_type> + <sort_order>2</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_yesno + 10 + 1 + 1 + 0 + + + + select + adminhtml/system_config_source_yesno + 12 + 1 + 1 + 0 + + + + + select + adminhtml/system_config_source_yesno + 14 + 1 + 1 + 0 + + + + text + 20 + 1 + 1 + 0 + + + + text + 21 + 1 + 1 + 0 + + + + text + 30 + 1 + 1 + 0 + + + + text + 40 + 1 + 1 + 0 + + + + text + 45 + 1 + 1 + 0 + + + + text + 50 + 1 + 1 + 0 + + + + text + 55 + 1 + 1 + 0 + + + + + select + adminhtml/system_config_source_order_status_new + 70 + 1 + 1 + 0 + + + + select + adminhtml/system_config_source_yesno + 91 + 0 + 0 + 0 + + + + select + amazonpayments/adminhtml_shipping_methods + adminhtml/system_config_backend_serialized + 100 + 1 + 1 + 0 + + + + select + amazonpayments/adminhtml_shipping_methods + adminhtml/system_config_backend_serialized + 110 + 1 + 1 + 0 + + + + select + amazonpayments/adminhtml_shipping_methods + adminhtml/system_config_backend_serialized + 120 + 1 + 1 + 0 + + + + select + amazonpayments/adminhtml_shipping_methods + adminhtml/system_config_backend_serialized + 130 + 1 + 1 + 0 + + +
+ + + +
+
+


Signing up with Amazon Simple Pay

+

To configure your Amazon Alternate Payment Method (Amazon Simple Pay), you will need to enter your Amazon Web Services Access Key ID, and Secret Access Key.

+

If you do not already have an Amazon Payments Business account, click here to create one now. Sign-Up.

+

To locate your Amazon Web Services Access Key ID and Secret Key ID, sign in to your AWS account and click Your Account > Access Identifiers. Don't have an AWS account? Log in to aws.amazon.com and setup an AWS account.

+

For additional information on setting up your Amazon Simple Pay, Click Here - FAQ.

+


Signup for Amazon Simple Pay

+
+
+]]>
+ text + 601 + 1 + 1 + 0 + + + <label>Title</label> + <frontend_type>text</frontend_type> + <sort_order>2</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_yesno + 9 + 1 + 1 + 0 + + + + select + adminhtml/system_config_source_yesno + 10 + 1 + 1 + 0 + + + + select + + amazonpayments/payment_asp_source_paymentAction + 11 + 1 + 1 + 0 + + + + text + 12 + 1 + 1 + 0 + + + + text + 13 + 1 + 1 + 0 + + + + text + 15 + 0 + 0 + 0 + + + + text + 16 + 0 + 0 + 0 + + + + select + adminhtml/system_config_source_yesno + 19 + 1 + 1 + 0 + Automatically redirect after payment to success page + + + + text + 21 + 0 + 0 + 0 + + + + text + 22 + 0 + 0 + 0 + + + + + text + 31 + 1 + 1 + 0 + + + + text + 32 + 1 + 1 + 0 + + + + text + 31 + 1 + 1 + 0 + + + + text + 33 + 1 + 1 + 0 + + + + text + 34 + 1 + 1 + 0 + + + + select + adminhtml/system_config_source_email_identity + 35 + 1 + 1 + 0 + + + + select + adminhtml/system_config_source_email_template + 36 + 1 + 1 + 0 + + + + select + adminhtml/system_config_source_yesno + 37 + 1 + 1 + 0 + + + + select + adminhtml/system_config_source_yesno + 38 + 1 + 1 + 0 + + + + select + adminhtml/system_config_source_yesno + 39 + 1 + 1 + 0 + + +
+
+
+
+
diff --git a/app/code/core/Mage/AmazonPayments/sql/amazonpayments_setup/mysql4-install-0.1.2.php b/app/code/core/Mage/AmazonPayments/sql/amazonpayments_setup/mysql4-install-0.1.2.php new file mode 100644 index 0000000000..b6bbf16309 --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/sql/amazonpayments_setup/mysql4-install-0.1.2.php @@ -0,0 +1,45 @@ +startSetup(); + +$installer->run(" +CREATE TABLE `{$this->getTable('amazonpayments_api_debug')}` ( + `debug_id` int(10) unsigned NOT NULL auto_increment, + `transaction_id` varchar(255) NOT NULL default '', + `debug_at` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, + `request_body` text, + `response_body` text, + PRIMARY KEY (`debug_id`), + KEY `debug_at` (`debug_at`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + "); + +$installer->endSetup(); diff --git a/app/code/core/Mage/AmazonPayments/sql/amazonpayments_setup/mysql4-upgrade-0.1.0-0.1.1.php b/app/code/core/Mage/AmazonPayments/sql/amazonpayments_setup/mysql4-upgrade-0.1.0-0.1.1.php new file mode 100644 index 0000000000..b6bbf16309 --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/sql/amazonpayments_setup/mysql4-upgrade-0.1.0-0.1.1.php @@ -0,0 +1,45 @@ +startSetup(); + +$installer->run(" +CREATE TABLE `{$this->getTable('amazonpayments_api_debug')}` ( + `debug_id` int(10) unsigned NOT NULL auto_increment, + `transaction_id` varchar(255) NOT NULL default '', + `debug_at` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, + `request_body` text, + `response_body` text, + PRIMARY KEY (`debug_id`), + KEY `debug_at` (`debug_at`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + "); + +$installer->endSetup(); diff --git a/app/code/core/Mage/AmazonPayments/sql/amazonpayments_setup/mysql4-upgrade-0.1.1-0.1.2.php b/app/code/core/Mage/AmazonPayments/sql/amazonpayments_setup/mysql4-upgrade-0.1.1-0.1.2.php new file mode 100644 index 0000000000..4a933b57aa --- /dev/null +++ b/app/code/core/Mage/AmazonPayments/sql/amazonpayments_setup/mysql4-upgrade-0.1.1-0.1.2.php @@ -0,0 +1,45 @@ +startSetup(); + +$installer->run(" +CREATE TABLE IF NOT EXISTS `{$this->getTable('amazonpayments_api_debug')}` ( + `debug_id` int(10) unsigned NOT NULL auto_increment, + `transaction_id` varchar(255) NOT NULL default '', + `debug_at` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, + `request_body` text, + `response_body` text, + PRIMARY KEY (`debug_id`), + KEY `debug_at` (`debug_at`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + "); + +$installer->endSetup(); 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 17b1a517f7..1e15c5f821 100644 --- a/app/code/core/Mage/Api/Model/Server/Adapter/Soap.php +++ b/app/code/core/Mage/Api/Model/Server/Adapter/Soap.php @@ -96,10 +96,11 @@ public function run() { $urlModel = Mage::getModel('core/url') ->setUseSession(false); - if ($this->getController()->getRequest()->getParam('wsdl')) { + if ($this->getController()->getRequest()->getParam('wsdl') !== null) { // Generating wsdl content from template $io = new Varien_Io_File(); $io->open(array('path'=>Mage::getModuleDir('etc', 'Mage_Api'))); + $wsdlContent = $io->read('wsdl.xml'); $template = Mage::getModel('core/email_template_filter'); diff --git a/app/code/core/Mage/Api/Model/User.php b/app/code/core/Mage/Api/Model/User.php index 7fdacd890c..f8269fbc98 100644 --- a/app/code/core/Mage/Api/Model/User.php +++ b/app/code/core/Mage/Api/Model/User.php @@ -31,8 +31,9 @@ protected function _construct() $this->_init('api/user'); } - public function save() { - + public function save() + { + $this->_beforeSave(); $data = array( 'firstname' => $this->getFirstname(), 'lastname' => $this->getLastname(), @@ -62,6 +63,7 @@ public function save() { $this->setData($data); $this->_getResource()->save($this); + $this->_afterSave(); return $this; } diff --git a/app/code/core/Mage/Api/Model/Wsdl/Config.php b/app/code/core/Mage/Api/Model/Wsdl/Config.php index e49dca712d..0969038ea0 100644 --- a/app/code/core/Mage/Api/Model/Wsdl/Config.php +++ b/app/code/core/Mage/Api/Model/Wsdl/Config.php @@ -33,8 +33,7 @@ */ class Mage_Api_Model_Wsdl_Config extends Mage_Api_Model_Wsdl_Config_Base { - - protected $_wsdlContent = null; + protected static $_namespacesPrefix = null; public function __construct($sourceData=null) { @@ -43,25 +42,30 @@ public function __construct($sourceData=null) } /** - * Set content of wsdl file as string + * Return wsdl content * - * @param string $wsdlContent - * @return Mage_Api_Model_Wsdl_Config + * @return string */ - public function setWsdlContent($wsdlContent) + public function getWsdlContent() { - $this->_wsdlContent = $wsdlContent; - return $this; + return $this->_xml->asXML(); } /** - * Return wsdl content + * Return namespaces with their prefix * - * @return string + * @return array */ - public function getWsdlContent() + public static function getNamespacesPrefix() { - return $this->_wsdlContent; + if (is_null(self::$_namespacesPrefix)) { + self::$_namespacesPrefix = array(); + $config = Mage::getConfig()->getNode('api/v2/wsdl/prefix')->children(); + foreach ($config as $prefix => $namespace) { + self::$_namespacesPrefix[$namespace->asArray()] = $prefix; + } + } + return self::$_namespacesPrefix; } public function getCache() @@ -105,12 +109,6 @@ public function init() BP . DS . 'app' . DS . 'code' . DS . 'community' . PS . BP . DS . 'app' . DS . 'code' . DS . 'core' . PS . BP . DS . 'lib' . PS . - /** - * Problem with concatenate BP . $codeDir - */ - /*BP . $codeDir . DS .'community' . PS . - BP . $codeDir . DS .'core' . PS . - BP . $libDir . PS .*/ Mage::registry('original_include_path') ); } @@ -133,7 +131,6 @@ public function init() $this->loadFile($baseWsdlFile); foreach ($modules as $modName=>$module) { -// if ($module->is('active') && $modName == 'Mage_Customer') { if ($module->is('active') && $modName != 'Mage_Api') { if ($disableLocalModules && ('local' === (string)$module->codePool)) { continue; @@ -144,7 +141,6 @@ public function init() } } } - $this->setWsdlContent($this->_xml->asXML()); if (Mage::app()->useCache('config')) { $this->saveCache(array('config')); @@ -152,4 +148,14 @@ public function init() return $this; } + + /** + * Return Xml of node as string + * + * @return string + */ + public function getXmlString() + { + return $this->getNode()->asXML(); + } } diff --git a/app/code/core/Mage/Api/Model/Wsdl/Config/Element.php b/app/code/core/Mage/Api/Model/Wsdl/Config/Element.php index 52b159aa48..a390e571c9 100644 --- a/app/code/core/Mage/Api/Model/Wsdl/Config/Element.php +++ b/app/code/core/Mage/Api/Model/Wsdl/Config/Element.php @@ -90,9 +90,13 @@ public function extendChild($source, $overwrite=false, $elmNamespace = '') $targetChild->setParent($this); foreach ($this->getAttributes($source) as $namespace => $attributes) { foreach ($attributes as $key => $value) { + $_namespacesPrefix = Mage_Api_Model_Wsdl_Config::getNamespacesPrefix(); if ($namespace == '') { $namespace = null; + } elseif (array_key_exists($namespace, $_namespacesPrefix)) { + $key = $_namespacesPrefix[$namespace] . ':' . $key; } + $targetChild->addAttribute($key, $this->xmlentities($value), $namespace); } } @@ -109,8 +113,11 @@ public function extendChild($source, $overwrite=false, $elmNamespace = '') $targetChild->setParent($this); foreach ($this->getAttributes($source) as $namespace => $attributes) { foreach ($attributes as $key => $value) { + $_namespacesPrefix = Mage_Api_Model_Wsdl_Config::getNamespacesPrefix(); if ($namespace == '') { $namespace = null; + } elseif (array_key_exists($namespace, $_namespacesPrefix)) { + $key = $_namespacesPrefix[$namespace] . ':' . $key; } $targetChild->addAttribute($key, $this->xmlentities($value), $namespace); } @@ -166,14 +173,13 @@ public function getChildren($source) { $children = array(); $namespaces = $source->getNamespaces(true); - $children[''] = $source->children(''); foreach ($namespaces as $key => $value) { if ($key == '' || $key == 'wsdl') { - $value = ''; continue; } $children[$value] = $source->children($value); } + $children[''] = $source->children(''); return $children; } diff --git a/app/code/core/Mage/Api/etc/api.xml b/app/code/core/Mage/Api/etc/api.xml index 3206e5832a..11e0ddfe68 100644 --- a/app/code/core/Mage/Api/etc/api.xml +++ b/app/code/core/Mage/Api/etc/api.xml @@ -69,6 +69,13 @@ + + + + http://schemas.xmlsoap.org/wsdl/ + + + 0 diff --git a/app/code/core/Mage/Bundle/Model/Product/Type.php b/app/code/core/Mage/Bundle/Model/Product/Type.php index ee45cc8161..985fa184fc 100644 --- a/app/code/core/Mage/Bundle/Model/Product/Type.php +++ b/app/code/core/Mage/Bundle/Model/Product/Type.php @@ -692,7 +692,7 @@ public function getOptionsByIds($optionIds, $product = null) ->setPositionOrder() ->joinValues(Mage::app()->getStore()->getId()) ->setIdFilter($optionIds); - $this->getProduct($product)->setData($this->_keyUsedOptions, $usedOptionsIds); + $this->getProduct($product)->setData($this->_keyUsedOptions, $usedOptions); $this->getProduct($product)->setData($this->_keyUsedOptionsIds, $optionIds); } return $usedOptions; 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 336245725e..c239582049 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 @@ -134,7 +134,8 @@ public function draw() } $this->_setFontRegular(); if ($option['value']) { - $values = explode(', ', 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) { $page->drawText($_value, $leftBound + 5, $pdf->y-$shift[1], 'UTF-8'); 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 69c10aa204..a8b593fb75 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 @@ -125,7 +125,8 @@ public function draw() } $this->_setFontRegular(); if ($option['value']) { - $values = explode(', ', 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, 70, true, true) as $_value) { $page->drawText($_value, 40, $pdf->y-$shift[1], 'UTF-8'); 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 2d3d714608..51c90c2c35 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 @@ -115,7 +115,8 @@ public function draw() $this->_setFontRegular(); if ($option['value']) { - $values = explode(', ', 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, 70,true,true) as $_value) { $page->drawText($_value, 65, $pdf->y-$shift[1], 'UTF-8'); diff --git a/app/code/core/Mage/Catalog/Block/Product/List.php b/app/code/core/Mage/Catalog/Block/Product/List.php index 1114cac4b3..d659498bb8 100644 --- a/app/code/core/Mage/Catalog/Block/Product/List.php +++ b/app/code/core/Mage/Catalog/Block/Product/List.php @@ -1,251 +1,251 @@ - - */ -class Mage_Catalog_Block_Product_List extends Mage_Catalog_Block_Product_Abstract -{ - /** - * Default toolbar block name - * - * @var string - */ - protected $_defaultToolbarBlock = 'catalog/product_list_toolbar'; - - /** - * Product Collection - * - * @var Mage_Eav_Model_Entity_Collection_Abstract - */ - protected $_productCollection; - - /** - * Retrieve loaded category collection - * - * @return Mage_Eav_Model_Entity_Collection_Abstract - */ - protected function _getProductCollection() - { - if (is_null($this->_productCollection)) { - $layer = Mage::getSingleton('catalog/layer'); - /* @var $layer Mage_Catalog_Model_Layer */ - if ($this->getShowRootCategory()) { - $this->setCategoryId(Mage::app()->getStore()->getRootCategoryId()); - } - - // if this is a product view page - if (Mage::registry('product')) { - // get collection of categories this product is associated with - $categories = Mage::registry('product')->getCategoryCollection() - ->setPage(1, 1) - ->load(); - // if the product is associated with any category - if ($categories->count()) { - // show products from this category - $this->setCategoryId(current($categories->getIterator())); - } - } - - $origCategory = null; - if ($this->getCategoryId()) { - $category = Mage::getModel('catalog/category')->load($this->getCategoryId()); - if ($category->getId()) { - $origCategory = $layer->getCurrentCategory(); - $layer->setCurrentCategory($category); - } - } - $this->_productCollection = $layer->getProductCollection(); - - $this->prepareSortableFieldsByCategory($layer->getCurrentCategory()); - - if ($origCategory) { - $layer->setCurrentCategory($origCategory); - } - } - return $this->_productCollection; - } - - /** - * Retrieve loaded category collection - * - * @return Mage_Eav_Model_Entity_Collection_Abstract - */ - public function getLoadedProductCollection() - { - return $this->_getProductCollection(); - } - - /** - * Retrieve current view mode - * - * @return string - */ - public function getMode() - { - return $this->getChild('toolbar')->getCurrentMode(); - } - - /** - * Need use as _prepareLayout - but problem in declaring collection from - * another block (was problem with search result) - */ - protected function _beforeToHtml() - { - /*$toolbar = $this->getLayout()->createBlock('catalog/product_list_toolbar', microtime()); - if ($toolbarTemplate = $this->getToolbarTemplate()) { - $toolbar->setTemplate($toolbarTemplate); - }*/ - $toolbar = $this->getToolbarBlock(); - - // called prepare sortable parameters - $collection = $this->_getProductCollection(); - - // use sortable parameters - if ($orders = $this->getAvailableOrders()) { - $toolbar->setAvailableOrders($orders); - } - if ($sort = $this->getSortBy()) { - $toolbar->setDefaultOrder($sort); - } - if ($modes = $this->getModes()) { - $toolbar->setModes($modes); - } - - // set collection to tollbar and apply sort - $toolbar->setCollection($collection); - - $this->setChild('toolbar', $toolbar); - Mage::dispatchEvent('catalog_block_product_list_collection', array( - 'collection'=>$this->_getProductCollection(), - )); - - $this->_getProductCollection()->load(); - Mage::getModel('review/review')->appendSummary($this->_getProductCollection()); - return parent::_beforeToHtml(); - } - - /** - * Retrieve Toolbar block - * - * @return Mage_Catalog_Block_Product_List_Toolbar - */ - public function getToolbarBlock() - { - if ($blockName = $this->getToolbarBlockName()) { - if ($block = $this->getLayout()->getBlock($blockName)) { - return $block; - } - } - $block = $this->getLayout()->createBlock($this->_defaultToolbarBlock, microtime()); - return $block; - } - - /** - * Retrieve list toolbar HTML - * - * @return string - */ - public function getToolbarHtml() - { - return $this->getChildHtml('toolbar'); - } - - public function setCollection($collection) - { - $this->_productCollection = $collection; - return $this; - } - - public function addAttribute($code) - { - $this->_getProductCollection()->addAttributeToSelect($code); - return $this; - } - - public function getPriceBlockTemplate() - { - return $this->_getData('price_block_template'); - } - - /** - * Retrieve Catalog Config object - * - * @return Mage_Catalog_Model_Config - */ - protected function _getConfig() - { - return Mage::getSingleton('catalog/config'); - } - - /** - * Prepare Sort By fields from Category Data - * - * @param Mage_Catalog_Model_Category $category - * @return Mage_Catalog_Block_Product_List - */ - public function prepareSortableFieldsByCategory($category) { - if (!$this->getAvailableOrders()) { - $this->setAvailableOrders($category->getAvailableSortByOptions()); - } - $availableOrders = $this->getAvailableOrders(); - if (!$this->getSortBy()) { - if ($categorySortBy = $category->getDefaultSortBy()) { - if (!$availableOrders) { - $availableOrders = $this->_getConfig()->getAttributeUsedForSortByArray(); - } - if (isset($availableOrders[$categorySortBy])) { - $this->setSortBy($categorySortBy); - } - } - } - - return $this; - } - - /** - * Retrieve url for add product to cart - * Rewrited for Product List and has required options products - * - * @param Mage_Catalog_Model_Product $product - * @param array $additional - * @return string - */ - public function getAddToCartUrl($product, $additional = array()) - { - if ($product->hasRequiredOptions()) { - $url = $product->getProductUrl(); - $link = (strpos($url, '?') !== false) ? '&' : '?'; - return $url . $link . 'options=cart'; - } - return parent::getAddToCartUrl($product, $additional); - } -} + + */ +class Mage_Catalog_Block_Product_List extends Mage_Catalog_Block_Product_Abstract +{ + /** + * Default toolbar block name + * + * @var string + */ + protected $_defaultToolbarBlock = 'catalog/product_list_toolbar'; + + /** + * Product Collection + * + * @var Mage_Eav_Model_Entity_Collection_Abstract + */ + protected $_productCollection; + + /** + * Retrieve loaded category collection + * + * @return Mage_Eav_Model_Entity_Collection_Abstract + */ + protected function _getProductCollection() + { + if (is_null($this->_productCollection)) { + $layer = Mage::getSingleton('catalog/layer'); + /* @var $layer Mage_Catalog_Model_Layer */ + if ($this->getShowRootCategory()) { + $this->setCategoryId(Mage::app()->getStore()->getRootCategoryId()); + } + + // if this is a product view page + if (Mage::registry('product')) { + // get collection of categories this product is associated with + $categories = Mage::registry('product')->getCategoryCollection() + ->setPage(1, 1) + ->load(); + // if the product is associated with any category + if ($categories->count()) { + // show products from this category + $this->setCategoryId(current($categories->getIterator())); + } + } + + $origCategory = null; + if ($this->getCategoryId()) { + $category = Mage::getModel('catalog/category')->load($this->getCategoryId()); + if ($category->getId()) { + $origCategory = $layer->getCurrentCategory(); + $layer->setCurrentCategory($category); + } + } + $this->_productCollection = $layer->getProductCollection(); + + $this->prepareSortableFieldsByCategory($layer->getCurrentCategory()); + + if ($origCategory) { + $layer->setCurrentCategory($origCategory); + } + } + return $this->_productCollection; + } + + /** + * Retrieve loaded category collection + * + * @return Mage_Eav_Model_Entity_Collection_Abstract + */ + public function getLoadedProductCollection() + { + return $this->_getProductCollection(); + } + + /** + * Retrieve current view mode + * + * @return string + */ + public function getMode() + { + return $this->getChild('toolbar')->getCurrentMode(); + } + + /** + * Need use as _prepareLayout - but problem in declaring collection from + * another block (was problem with search result) + */ + protected function _beforeToHtml() + { + /*$toolbar = $this->getLayout()->createBlock('catalog/product_list_toolbar', microtime()); + if ($toolbarTemplate = $this->getToolbarTemplate()) { + $toolbar->setTemplate($toolbarTemplate); + }*/ + $toolbar = $this->getToolbarBlock(); + + // called prepare sortable parameters + $collection = $this->_getProductCollection(); + + // use sortable parameters + if ($orders = $this->getAvailableOrders()) { + $toolbar->setAvailableOrders($orders); + } + if ($sort = $this->getSortBy()) { + $toolbar->setDefaultOrder($sort); + } + if ($modes = $this->getModes()) { + $toolbar->setModes($modes); + } + + // set collection to tollbar and apply sort + $toolbar->setCollection($collection); + + $this->setChild('toolbar', $toolbar); + Mage::dispatchEvent('catalog_block_product_list_collection', array( + 'collection'=>$this->_getProductCollection(), + )); + + $this->_getProductCollection()->load(); + Mage::getModel('review/review')->appendSummary($this->_getProductCollection()); + return parent::_beforeToHtml(); + } + + /** + * Retrieve Toolbar block + * + * @return Mage_Catalog_Block_Product_List_Toolbar + */ + public function getToolbarBlock() + { + if ($blockName = $this->getToolbarBlockName()) { + if ($block = $this->getLayout()->getBlock($blockName)) { + return $block; + } + } + $block = $this->getLayout()->createBlock($this->_defaultToolbarBlock, microtime()); + return $block; + } + + /** + * Retrieve list toolbar HTML + * + * @return string + */ + public function getToolbarHtml() + { + return $this->getChildHtml('toolbar'); + } + + public function setCollection($collection) + { + $this->_productCollection = $collection; + return $this; + } + + public function addAttribute($code) + { + $this->_getProductCollection()->addAttributeToSelect($code); + return $this; + } + + public function getPriceBlockTemplate() + { + return $this->_getData('price_block_template'); + } + + /** + * Retrieve Catalog Config object + * + * @return Mage_Catalog_Model_Config + */ + protected function _getConfig() + { + return Mage::getSingleton('catalog/config'); + } + + /** + * Prepare Sort By fields from Category Data + * + * @param Mage_Catalog_Model_Category $category + * @return Mage_Catalog_Block_Product_List + */ + public function prepareSortableFieldsByCategory($category) { + if (!$this->getAvailableOrders()) { + $this->setAvailableOrders($category->getAvailableSortByOptions()); + } + $availableOrders = $this->getAvailableOrders(); + if (!$this->getSortBy()) { + if ($categorySortBy = $category->getDefaultSortBy()) { + if (!$availableOrders) { + $availableOrders = $this->_getConfig()->getAttributeUsedForSortByArray(); + } + if (isset($availableOrders[$categorySortBy])) { + $this->setSortBy($categorySortBy); + } + } + } + + return $this; + } + + /** + * Retrieve url for add product to cart + * Rewrited for Product List and has required options products + * + * @param Mage_Catalog_Model_Product $product + * @param array $additional + * @return string + */ + public function getAddToCartUrl($product, $additional = array()) + { + if ($product->hasRequiredOptions()) { + $url = $product->getProductUrl(); + $link = (strpos($url, '?') !== false) ? '&' : '?'; + return $url . $link . 'options=cart'; + } + return parent::getAddToCartUrl($product, $additional); + } +} diff --git a/app/code/core/Mage/Catalog/Block/Product/List/Crosssell.php b/app/code/core/Mage/Catalog/Block/Product/List/Crosssell.php index aa5f2e0402..39e43d12a9 100644 --- a/app/code/core/Mage/Catalog/Block/Product/List/Crosssell.php +++ b/app/code/core/Mage/Catalog/Block/Product/List/Crosssell.php @@ -24,18 +24,29 @@ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** * Catalog product related items block * * @category Mage * @package Mage_Catalog - * @author Magento Core Team + * @author Magento Core Team */ class Mage_Catalog_Block_Product_List_Crosssell extends Mage_Catalog_Block_Product_Abstract { + /** + * Crosssell item collection + * + * @var Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Link_Product_Collection + */ protected $_itemCollection; + /** + * Prepare crosssell items data + * + * @return Mage_Catalog_Block_Product_List_Crosssell + */ protected function _prepareData() { $product = Mage::registry('product'); @@ -58,14 +69,43 @@ protected function _prepareData() return $this; } - protected function _beforeToHtml() + /** + * Before rendering html process + * Prepare items collection + * + * @return Mage_Catalog_Block_Product_List_Crosssell + */ + protected function _beforeToHtml() { $this->_prepareData(); return parent::_beforeToHtml(); } + /** + * Retrieve crosssell items collection + * + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Link_Product_Collection + */ public function getItems() { return $this->_itemCollection; } -} \ No newline at end of file + + /** + * Retrieve url for add product to cart + * Rewrited for Product List and has required options products + * + * @param Mage_Catalog_Model_Product $product + * @param array $additional + * @return string + */ + public function getAddToCartUrl($product, $additional = array()) + { + if ($product->hasRequiredOptions()) { + $url = $product->getProductUrl(); + $link = (strpos($url, '?') !== false) ? '&' : '?'; + return $url . $link . 'options=cart'; + } + return parent::getAddToCartUrl($product, $additional); + } +} diff --git a/app/code/core/Mage/Catalog/Block/Product/New.php b/app/code/core/Mage/Catalog/Block/Product/New.php index a602aa93fa..9e4a295215 100644 --- a/app/code/core/Mage/Catalog/Block/Product/New.php +++ b/app/code/core/Mage/Catalog/Block/Product/New.php @@ -40,7 +40,12 @@ class Mage_Catalog_Block_Product_New extends Mage_Catalog_Block_Product_Abstract protected function _beforeToHtml() { $todayDate = Mage::app()->getLocale()->date()->toString(Varien_Date::DATETIME_INTERNAL_FORMAT); - $collection = $this->_addProductAttributesAndPrices(Mage::getResourceModel('catalog/product_collection')) + + $collection = Mage::getResourceModel('catalog/product_collection'); + Mage::getSingleton('catalog/product_status')->addVisibleFilterToCollection($collection); + Mage::getSingleton('catalog/product_visibility')->addVisibleInCatalogFilterToCollection($collection); + + $collection = $this->_addProductAttributesAndPrices($collection) ->addStoreFilter() ->addAttributeToFilter('news_from_date', array('date' => true, 'to' => $todayDate)) ->addAttributeToFilter('news_to_date', array('or'=> array( @@ -69,4 +74,4 @@ public function getProductsCount() } return $this->_productsCount; } -} \ No newline at end of file +} diff --git a/app/code/core/Mage/Catalog/Block/Product/View.php b/app/code/core/Mage/Catalog/Block/Product/View.php index 17da386d41..1e4a633780 100644 --- a/app/code/core/Mage/Catalog/Block/Product/View.php +++ b/app/code/core/Mage/Catalog/Block/Product/View.php @@ -53,7 +53,14 @@ protected function _prepareLayout() } else { $headBlock->setDescription( $this->getProduct()->getDescription() ); } - } + } + + if ($layout = $this->getProduct()->getPageLayout()) { + if ($template = (string)Mage::getConfig()->getNode('global/cms/layouts/'.$layout.'/template')) { + $this->getLayout()->getBlock('root')->setTemplate($template); + } + } + return parent::_prepareLayout(); } diff --git a/app/code/core/Mage/Catalog/Block/Product/View/Options/Type/Date.php b/app/code/core/Mage/Catalog/Block/Product/View/Options/Type/Date.php index cefeebcc09..d9b212661e 100644 --- a/app/code/core/Mage/Catalog/Block/Product/View/Options/Type/Date.php +++ b/app/code/core/Mage/Catalog/Block/Product/View/Options/Type/Date.php @@ -50,6 +50,16 @@ protected function _prepareLayout() return parent::_prepareLayout(); } + /** + * Use JS calendar settings + * + * @return boolean + */ + public function useCalendar() + { + return Mage::getSingleton('catalog/product_option_type_date')->useCalendar(); + } + /** * Date input * @@ -57,7 +67,7 @@ protected function _prepareLayout() */ public function getDateHtml() { - if (Mage::getSingleton('catalog/product_option_type_date')->useCalendar()) { + if ($this->useCalendar()) { return $this->getCalendarDateHtml(); } else { return $this->getDropDownsDateHtml(); @@ -118,12 +128,13 @@ public function getDropDownsDateHtml() */ public function getTimeHtml() { - $hourStart = 0; if (Mage::getSingleton('catalog/product_option_type_date')->is24hTimeFormat()) { + $hourStart = 0; $hourEnd = 23; $dayPartHtml = ''; } else { - $hourEnd = 11; + $hourStart = 1; + $hourEnd = 12; $dayPartHtml = $this->_getHtmlSelect('day_part') ->setOptions(array( 'am' => Mage::helper('catalog')->__('AM'), diff --git a/app/code/core/Mage/Catalog/Helper/Data.php b/app/code/core/Mage/Catalog/Helper/Data.php index e0bae9ca95..e4ca699f79 100644 --- a/app/code/core/Mage/Catalog/Helper/Data.php +++ b/app/code/core/Mage/Catalog/Helper/Data.php @@ -20,17 +20,29 @@ * * @category Mage * @package Mage_Catalog - * @copyright Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com) + * @copyright Copyright (c) 2009 Irubin Consulting Inc. DBA Varien (http://www.varien.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** * Catalog data helper * - * @author Magento Core Team + * @category Mage + * @package Mage_Catalog + * @author Magento Core Team */ class Mage_Catalog_Helper_Data extends Mage_Core_Helper_Abstract { + const PRICE_SCOPE_GLOBAL = 0; + const PRICE_SCOPE_WEBSITE = 1; + const XML_PATH_PRICE_SCOPE = 'catalog/price/scope'; + + /** + * Breadcrumb Path cache + * + * @var string + */ protected $_categoryPath; /** @@ -70,6 +82,12 @@ public function getBreadcrumbPath() return $this->_categoryPath; } + /** + * Check is category link + * + * @param int $categoryId + * @return bool + */ protected function _isCategoryLink($categoryId) { if ($this->getProduct()) { @@ -82,20 +100,30 @@ protected function _isCategoryLink($categoryId) } /** - * Return current category + * Return current category object * - * @return Mage_Catalog_Model_Category + * @return Mage_Catalog_Model_Category|null */ public function getCategory() { return Mage::registry('current_category'); } + /** + * Retrieve current Product object + * + * @return Mage_Catalog_Model_Product|null + */ public function getProduct() { return Mage::registry('current_product'); } + /** + * Retrieve Visitor/Customer Last Viewed URL + * + * @return string + */ public function getLastViewedUrl() { if ($productId = Mage::getSingleton('catalog/session')->getLastViewedProductId()) { @@ -129,6 +157,11 @@ public function splitSku($sku, $length = 30) return Mage::helper('core/string')->str_split($sku, $length, true, false, '[\-\s]'); } + /** + * Retrieve attribute hidden fields + * + * @return array + */ public function getAttributeHiddenFields() { if (Mage::registry('attribute_type_hidden_fields')) { @@ -138,6 +171,11 @@ public function getAttributeHiddenFields() } } + /** + * Retrieve attribute disabled types + * + * @return array + */ public function getAttributeDisabledTypes() { if (Mage::registry('attribute_type_disabled_types')) { @@ -146,4 +184,24 @@ public function getAttributeDisabledTypes() return array(); } } + + /** + * Retrieve Catalog Price Scope + * + * @return int + */ + public function getPriceScope() + { + return Mage::getStoreConfig(self::XML_PATH_PRICE_SCOPE); + } + + /** + * Is Global Price + * + * @return bool + */ + public function isPriceGlobal() + { + return $this->getPriceScope() == self::PRICE_SCOPE_GLOBAL; + } } diff --git a/app/code/core/Mage/Catalog/Helper/Product/Flat.php b/app/code/core/Mage/Catalog/Helper/Product/Flat.php index e96a4ee2de..24d7d3a8ed 100644 --- a/app/code/core/Mage/Catalog/Helper/Product/Flat.php +++ b/app/code/core/Mage/Catalog/Helper/Product/Flat.php @@ -34,7 +34,9 @@ */ class Mage_Catalog_Helper_Product_Flat extends Mage_Core_Helper_Abstract { - const XML_PATH_USE_PRODUCT_FLAT = 'catalog/frontend/flat_catalog_product'; + const XML_PATH_USE_PRODUCT_FLAT = 'catalog/frontend/flat_catalog_product'; + const XML_NODE_ADD_FILTERABLE_ATTRIBUTES = 'global/catalog/product/flat/add_filterable_attributes'; + const XML_NODE_ADD_CHILD_DATA = 'global/catalog/product/flat/add_child_data'; /** * Catalog Product Flat Flag object @@ -80,4 +82,24 @@ public function isEnabled($store = null) } return Mage::getStoreConfigFlag(self::XML_PATH_USE_PRODUCT_FLAT, $store); } + + /** + * Is add filterable attributes to Flat table + * + * @return int + */ + public function isAddFilterableAttributes() + { + return intval(Mage::getConfig()->getNode(self::XML_NODE_ADD_FILTERABLE_ATTRIBUTES)); + } + + /** + * Is add child data to Flat + * + * @return int + */ + public function isAddChildData() + { + return intval(Mage::getConfig()->getNode(self::XML_NODE_ADD_CHILD_DATA)); + } } diff --git a/app/code/core/Mage/Catalog/Helper/Product/Options.php b/app/code/core/Mage/Catalog/Helper/Product/Options.php index 9ea5538159..c5d0ec9151 100644 --- a/app/code/core/Mage/Catalog/Helper/Product/Options.php +++ b/app/code/core/Mage/Catalog/Helper/Product/Options.php @@ -34,17 +34,4 @@ */ class Mage_Catalog_Helper_Product_Options extends Mage_Core_Helper_Abstract { - /** - * Simplest way to check if we format our option value using HTML - * - * @param string $optionValue - * @return boolean - */ - public function isHtmlFormattedOptionValue($optionValue) - { - if (is_string($optionValue) && strlen($optionValue) != strlen(strip_tags($optionValue))) { - return true; - } - return false; - } } \ No newline at end of file diff --git a/app/code/core/Mage/Catalog/Model/Category.php b/app/code/core/Mage/Catalog/Model/Category.php index d9596b968b..e72853d508 100644 --- a/app/code/core/Mage/Catalog/Model/Category.php +++ b/app/code/core/Mage/Catalog/Model/Category.php @@ -20,37 +20,71 @@ * * @category Mage * @package Mage_Catalog - * @copyright Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com) + * @copyright Copyright (c) 2009 Irubin Consulting Inc. DBA Varien (http://www.varien.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** * Catalog category * * @category Mage * @package Mage_Catalog - * @author Magento Core Team + * @author Magento Core Team */ class Mage_Catalog_Model_Category extends Mage_Catalog_Model_Abstract { /** * Category display modes */ - const DM_PRODUCT = 'PRODUCTS'; - const DM_PAGE = 'PAGE'; - const DM_MIXED = 'PRODUCTS_AND_PAGE'; - const TREE_ROOT_ID = 1; + const DM_PRODUCT = 'PRODUCTS'; + const DM_PAGE = 'PAGE'; + const DM_MIXED = 'PRODUCTS_AND_PAGE'; + const TREE_ROOT_ID = 1; + + const CACHE_TAG = 'catalog_category'; - const CACHE_TAG = 'catalog_category'; - protected $_cacheTag = 'catalog_category'; + /** + * Prefix of model events names + * + * @var string + */ + protected $_eventPrefix = 'catalog_category'; - protected $_eventPrefix = 'catalog_category'; - protected $_eventObject = 'category'; + /** + * Parameter name in event + * + * @var string + */ + protected $_eventObject = 'category'; + /** + * URL Model instance + * + * @var Mage_Core_Model_Url + */ protected static $_url; + + /** + * URL rewrite model + * + * @var Mage_Core_Model_Url_Rewrite + */ protected static $_urlRewrite; - private $_designAttributes = array( + /** + * Use flat resource model flag + * + * @var bool + */ + protected $_useFlatResource = false; + + /** + * Category design attributes + * + * @var array + */ + private $_designAttributes = array( 'custom_design', 'custom_design_apply', 'custom_design_from', @@ -60,16 +94,21 @@ class Mage_Catalog_Model_Category extends Mage_Catalog_Model_Abstract ); /** - * Enter description here... + * Category tree model * * @var Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Tree */ protected $_treeModel = null; + /** + * Initialize resource mode + * + */ protected function _construct() { if (Mage::helper('catalog/category_flat')->isEnabled()) { $this->_init('catalog/category_flat'); + $this->_useFlatResource = true; } else { $this->_init('catalog/category'); } @@ -162,6 +201,7 @@ public function getProductCollection() /** * Retrieve all customer attributes * + * @todo Use with Flat Resource * @return array */ public function getAttributes($noDesignAttributes = false) @@ -194,12 +234,12 @@ public function getProductsPosition() return array(); } - $arr = $this->getData('products_position'); - if (is_null($arr)) { - $arr = $this->getResource()->getProductsPosition($this); - $this->setData('products_position', $arr); + $array = $this->getData('products_position'); + if (is_null($array)) { + $array = $this->getResource()->getProductsPosition($this); + $this->setData('products_position', $array); } - return $arr; + return $array; } /** @@ -221,15 +261,19 @@ public function getStoreIds() return $storeIds; } - + /** + * Retrieve Layout Update Handle name + * + * @return string + */ public function getLayoutUpdateHandle() { $layout = 'catalog_category_'; if ($this->getIsAnchor()) { - $layout.= 'layered'; + $layout .= 'layered'; } else { - $layout.= 'default'; + $layout .= 'default'; } return $layout; } @@ -304,6 +348,11 @@ public function getUrl() return $url; } + /** + * Retrieve category id URL + * + * @return string + */ public function getCategoryIdUrl() { Varien_Profiler::start('REGULAR: '.__METHOD__); @@ -316,6 +365,12 @@ public function getCategoryIdUrl() return $url; } + /** + * Format URL key from name or defined key + * + * @param string $str + * @return string + */ public function formatUrlKey($str) { $str = Mage::helper('core')->removeAccents($str); @@ -325,6 +380,11 @@ public function formatUrlKey($str) return $urlKey; } + /** + * Retrieve image URL + * + * @return string + */ public function getImageUrl() { $url = false; @@ -334,6 +394,11 @@ public function getImageUrl() return $url; } + /** + * Retrieve URL path + * + * @return string + */ public function getUrlPath() { if ($path = $this->getData('url_path')) { @@ -383,6 +448,11 @@ public function getParentIds() return array_diff($this->getPathIds(), array($this->getId())); } + /** + * Retrieve dates for custom design (from & to) + * + * @return array + */ public function getCustomDesignDate() { $result = array(); @@ -392,6 +462,11 @@ public function getCustomDesignDate() return $result; } + /** + * Retrieve design attributes array + * + * @return array + */ public function getDesignAttributes() { $result = array(); @@ -401,10 +476,22 @@ public function getDesignAttributes() return $result; } + /** + * Retrieve attribute by code + * + * @param string $attributeCode + * @return Mage_Eav_Model_Entity_Attribute_Abstract + */ private function _getAttribute($attributeCode) { - return $this->getResource() - ->getAttribute($attributeCode); + if (!$this->_useFlatResource) { + $attribute = $this->getResource()->getAttribute($attributeCode); + } + else { + $attribute = Mage::getSingleton('catalog/config') + ->getAttribute('catalog_category', $attributeCode); + } + return $attribute; } /** @@ -418,41 +505,54 @@ public function getAllChildren($asArray = false) $children = $this->getResource()->getAllChildren($this); if ($asArray) { return $children; - } else { - return implode(',', $children); - } - - - - $this->getTreeModelInstance()->load(); - $children = $this->getTreeModelInstance()->getChildren($this->getId()); - - $myId = array($this->getId()); - if (is_array($children)) { - $children = array_merge($myId, $children); - } else { - $children = $myId; } - if ($asArray) { - return $children; - } else { + else { return implode(',', $children); } + +// $this->getTreeModelInstance()->load(); +// $children = $this->getTreeModelInstance()->getChildren($this->getId()); +// +// $myId = array($this->getId()); +// if (is_array($children)) { +// $children = array_merge($myId, $children); +// } +// else { +// $children = $myId; +// } +// if ($asArray) { +// return $children; +// } +// else { +// return implode(',', $children); +// } } + /** + * Retrieve children ids comma separated + * + * @return string + */ public function getChildren() { return implode(',', $this->getResource()->getChildren($this, false)); } + /** + * Retrieve Stores where isset category Path + * Return comma separated string + * + * @return string + */ public function getPathInStore() { $result = array(); //$path = $this->getTreeModelInstance()->getPath($this->getId()); $path = array_reverse($this->getPathIds()); foreach ($path as $itemId) { - if ($itemId == Mage::app()->getStore()->getRootCategoryId()) + if ($itemId == Mage::app()->getStore()->getRootCategoryId()) { break; + } $result[] = $itemId; } return implode(',', $result); @@ -485,6 +585,11 @@ public function getPathIds() return $ids; } + /** + * Retrieve level + * + * @return int + */ public function getLevel() { if (!$this->hasLevel()) { @@ -493,32 +598,63 @@ public function getLevel() return $this->getData('level'); } + /** + * Verify category ids + * + * @param array $ids + * @return bool + */ public function verifyIds(array $ids) { return $this->getResource()->verifyIds($ids); } + /** + * Retrieve Is Category has children flag + * + * @return bool + */ public function hasChildren() { return $this->_getResource()->getChildrenAmount($this) > 0; } + /** + * Retrieve Request Path + * + * @return string + */ public function getRequestPath() { return $this->_getData('request_path'); } + /** + * Retrieve Name data wraper + * + * @return string + */ public function getName() { return $this->_getData('name'); } + /** + * Before delete process + * + * @return Mage_Catalog_Model_Category + */ protected function _beforeDelete() { $this->_protectFromNonAdmin(); return parent::_beforeDelete(); } + /** + * Retrieve anchors above + * + * @return array + */ public function getAnchorsAbove() { $anchors = array(); @@ -528,17 +664,27 @@ public function getAnchorsAbove() unset($path[array_search($this->getId(), $path)]); } - if (!Mage::registry('_category_is_anchor_attribute')) { - $model = $this->getResource()->getAttribute('is_anchor'); - Mage::register('_category_is_anchor_attribute', $model); + if ($this->_useFlatResource) { + $anchors = $this->_getResource()->getAnchorsAbove($path, $this->getStoreId()); } + else { + if (!Mage::registry('_category_is_anchor_attribute')) { + $model = $this->_getAttribute('is_anchor'); + Mage::register('_category_is_anchor_attribute', $model); + } - if ($isAnchorAttribute = Mage::registry('_category_is_anchor_attribute')) { - $anchors = $this->getResource()->findWhereAttributeIs($path, $isAnchorAttribute, 1); + if ($isAnchorAttribute = Mage::registry('_category_is_anchor_attribute')) { + $anchors = $this->getResource()->findWhereAttributeIs($path, $isAnchorAttribute, 1); + } } return $anchors; } + /** + * Retrieve count products of category + * + * @return int + */ public function getProductCount() { if (!$this->hasProductCount()) { @@ -549,12 +695,14 @@ public function getProductCount() } /** - * Enter description here... + * Retrieve categories by parent * - * @param unknown_type $sorted - * @param unknown_type $asCollection - * @param unknown_type $toLoad - * @return unknown_type + * @param int $parent + * @param int $recursionLevel + * @param bool $sorted + * @param bool $asCollection + * @param bool $toLoad + * @return mixed */ public function getCategories($parent, $recursionLevel = 0, $sorted=false, $asCollection=false, $toLoad=true) { @@ -583,6 +731,11 @@ public function getChildrenCategories() return $this->getResource()->getChildrenCategories($this); } + /** + * Check category is in Root Category list + * + * @return bool + */ public function isInRootCategoryList() { return $this->getResource()->isInRootCategoryList($this); @@ -596,6 +749,9 @@ public function isInRootCategoryList() public function getAvailableSortBy() { $available = $this->getData('available_sort_by'); + if (empty($available)) { + return array(); + } if ($available && !is_array($available)) { $available = split(',', $available); } @@ -645,5 +801,4 @@ public function getDefaultSortBy() { return $sortBy; } - -} \ No newline at end of file +} 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 9f28b45751..7f7f8cc48d 100644 --- a/app/code/core/Mage/Catalog/Model/Category/Api/V2.php +++ b/app/code/core/Mage/Catalog/Model/Category/Api/V2.php @@ -55,7 +55,7 @@ public function info($categoryId, $store = null, $attributes = null) foreach ($category->getAttributes() as $attribute) { if ($this->_isAllowedAttribute($attribute, $attributes)) { - $result[$attribute->getAttributeCode()] = $category->getData($attribute->getAttributeCode()); + $result[$attribute->getAttributeCode()] = $category->getDataUsingMethod($attribute->getAttributeCode()); } } $result['parent_id'] = $category->getParentId(); @@ -135,5 +135,4 @@ public function update($categoryId, $categoryData, $store = null) return true; } - -} \ No newline at end of file +} diff --git a/app/code/core/Mage/Catalog/Model/Design.php b/app/code/core/Mage/Catalog/Model/Design.php index 7432113cc4..60daecd099 100644 --- a/app/code/core/Mage/Catalog/Model/Design.php +++ b/app/code/core/Mage/Catalog/Model/Design.php @@ -20,27 +20,45 @@ * * @category Mage * @package Mage_Catalog - * @copyright Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com) + * @copyright Copyright (c) 2009 Irubin Consulting Inc. DBA Varien (http://www.varien.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ +/** + * Catalog Custom Category design Model + * + * @category Mage + * @package Mage_Catalog + * @author Magento Core Team + */ class Mage_Catalog_Model_Design extends Mage_Core_Model_Abstract { const APPLY_FOR_PRODUCT = 1; const APPLY_FOR_CATEGORY = 2; + /** + * Category / Custom Design / Apply To constants + * + */ + const CATEGORY_APPLY_CATEGORY_AND_PRODUCT_RECURSIVE = 1; + const CATEGORY_APPLY_CATEGORY_ONLY = 2; + const CATEGORY_APPLY_CATEGORY_AND_PRODUCT_ONLY = 3; + const CATEGORY_APPLY_CATEGORY_RECURSIVE = 4; + + /** + * Apply design from catalog object + * + * @param array|Mage_Catalog_Model_Category|Mage_Catalog_Model_Product $object + * @param int $calledFrom + * @return Mage_Catalog_Model_Design + */ public function applyDesign($object, $calledFrom = 0) { - switch ($calledFrom) { - case self::APPLY_FOR_PRODUCT: - $calledFrom = 3; - break; - - case self::APPLY_FOR_CATEGORY: - $calledFrom = 4; - break; + if ($calledFrom != self::APPLY_FOR_CATEGORY && $calledFrom != self::APPLY_FOR_PRODUCT) { + return $this; } + if (Mage::helper('catalog/category_flat')->isEnabled()) { $this->_applyDesign($object, $calledFrom); } else { @@ -50,7 +68,13 @@ public function applyDesign($object, $calledFrom = 0) return $this; } - private function _apply($package, $theme) + /** + * Apply package and theme + * + * @param string $package + * @param string $theme + */ + protected function _apply($package, $theme) { Mage::getSingleton('core/design_package') ->setPackageName($package) @@ -58,84 +82,144 @@ private function _apply($package, $theme) } /** - * Apply design recursively + * Check is allow apply for * - * @param Varien_Object $object - * @param integer $calledFrom + * @param int $applyForObject + * @param int $applyTo + * @param int $pass + * @return bool */ - protected function _applyDesignRecursively($object, $calledFrom = 0) + protected function _isApplyFor($applyForObject, $applyTo, $pass = 0) { - $error = 0; - $package = ''; - $theme = ''; - $design = $object->getCustomDesign(); - $date = $object->getCustomDesignDate(); - $application= $object->getCustomDesignApply(); - - $designInfo = explode("/", $design); - if (count($designInfo) > 1){ - $package= $designInfo[0]; - $theme = $designInfo[1]; + $hasError = false; + if ($pass == 0) { + switch ($applyForObject) { + case self::APPLY_FOR_CATEGORY: + break; + case self::APPLY_FOR_PRODUCT: + $validApplyTo = array( + self::CATEGORY_APPLY_CATEGORY_AND_PRODUCT_RECURSIVE, + self::CATEGORY_APPLY_CATEGORY_AND_PRODUCT_ONLY + ); + if ($applyTo && !in_array($applyTo, $validApplyTo)) { + $hasError = true; + } + break; + default: + $hasError = true; + break; + } } + else { + switch ($applyForObject) { + case self::APPLY_FOR_CATEGORY: + $validApplyTo = array( + self::CATEGORY_APPLY_CATEGORY_AND_PRODUCT_RECURSIVE, + self::CATEGORY_APPLY_CATEGORY_RECURSIVE + ); + if ($applyTo && !in_array($applyTo, $validApplyTo)) { + $hasError = true; + } + break; + case self::APPLY_FOR_PRODUCT: + $validApplyTo = array( + self::CATEGORY_APPLY_CATEGORY_AND_PRODUCT_RECURSIVE + ); + if ($applyTo && !in_array($applyTo, $validApplyTo)) { + $hasError = true; + } + break; + default: + $hasError = true; + break; + } + } + return !$hasError; + } - switch ($calledFrom) { - case 3: - if ($application && $application != 1 && $application != 3) - $error = 1; - - $calledFrom = 0; - break; + /** + * Check and apply design + * + * @param string $design + * @param array $date + */ + protected function _isApplyDesign($design, array $date) + { + if (!array_key_exists('from', $date) || !array_key_exists('to', $date)) { + return false; + } - case 4: - if ($application && $application != 1 && $application != 4) - $error = 1; + $designInfo = explode("/", $design); + if (count($designInfo) != 2) { + return false; + } - //$calledFrom = 0; - break; + // define package and theme + $package = $designInfo[0]; + $theme = $designInfo[1]; - default: - if ($application && $application != 1) - $error = 1; + // compare dates + $storeTimeStamp = Mage::app()->getLocale()->storeTimeStamp(); + $fromTimeStamp = strtotime($date['from']); + $toTimeStamp = strtotime($date['to']); - break; + if ($date['from'] && $storeTimeStamp < $fromTimeStamp) { + return false; + } + elseif ($date['to'] && $storeTimeStamp > $toTimeStamp) { + return false; } - if ($package && $theme) { - $date['from'] = strtotime($date['from']); - $date['to'] = strtotime($date['to']); + $this->_apply($package, $theme); + return true; + } - if ($date['from'] && $date['from'] > strtotime("today")) { - $error = 1; - } else if ($date['to'] && $date['to'] < strtotime("today")) { - $error = 1; - } + /** + * Apply design recursively (if using EAV) + * + * @param Varien_Object $object + * @param int $calledFrom + * @return Mage_Catalog_Model_Design + */ + protected function _applyDesignRecursively($object, $calledFrom = 0, $pass = 0) + { + $design = $object->getCustomDesign(); + $date = $object->getCustomDesignDate(); + $applyTo = $object->getCustomDesignApply(); - if (!$error) { - $this->_apply($package, $theme); - return; - } + $checkAndApply = $this->_isApplyFor($calledFrom, $applyTo, $pass) + && $this->_isApplyDesign($design, $date); + if ($checkAndApply) { + return $this; } + $pass ++; + + $category = null; if ($object instanceof Mage_Catalog_Model_Product) { $category = $object->getCategory(); - } else if ($object instanceof Mage_Catalog_Model_Category) { + $pass --; + } + elseif ($object instanceof Mage_Catalog_Model_Category) { $category = $object->getParentCategory(); } if ($category && $category->getId()){ - $this->_applyDesignRecursively($category, $calledFrom); + $this->_applyDesignRecursively($category, $calledFrom, $pass); } + + return $this; } /** - * Apply design + * Apply design (if using Flat Category) * * @param Varien_Object|array $designUpdateData - * @param integer $calledFrom - * @param boolean $loaded + * @param int $calledFrom + * @param bool $loaded * @return Mage_Catalog_Model_Design */ - protected function _applyDesign($designUpdateData, $calledFrom = 0, $loaded = false) + protected function _applyDesign($designUpdateData, $calledFrom = 0, $loaded = false, $pass = 0) { $objects = array(); if (is_object($designUpdateData)) { @@ -144,52 +228,19 @@ protected function _applyDesign($designUpdateData, $calledFrom = 0, $loaded = fa $objects = &$designUpdateData; } foreach ($objects as $object) { - $error = 0; - $package = ''; - $theme = ''; $design = $object->getCustomDesign(); $date = $object->getCustomDesignDate(); - $application= $object->getCustomDesignApply(); + $applyTo = $object->getCustomDesignApply(); - $designInfo = explode("/", $design); - if (count($designInfo) > 1){ - $package= $designInfo[0]; - $theme = $designInfo[1]; + $checkAndApply = $this->_isApplyFor($calledFrom, $applyTo, $pass) + && $this->_isApplyDesign($design, $date); + if ($checkAndApply) { + return $this; } + } - switch ($calledFrom) { - case 3: - if ($application && $application != 1 && $application != 3) - $error = 1; - $calledFrom = 0; - break; - case 4: - if ($application && $application != 1 && $application != 4) - $error = 1; - //$calledFrom = 0; - break; - default: - if ($application && $application != 1) - $error = 1; - break; - } - - if ($package && $theme) { - $date['from'] = strtotime($date['from']); - $date['to'] = strtotime($date['to']); - - if ($date['from'] && $date['from'] > strtotime("today")) { - $error = 1; - } else if ($date['to'] && $date['to'] < strtotime("today")) { - $error = 1; - } + $pass ++; - if (!$error) { - $this->_apply($package, $theme); - return; - } - } - } if (false === $loaded && is_object($designUpdateData)) { $_designUpdateData = array(); if ($designUpdateData instanceof Mage_Catalog_Model_Product) { @@ -197,6 +248,7 @@ protected function _applyDesign($designUpdateData, $calledFrom = 0, $loaded = fa $_designUpdateData = array_merge( $_designUpdateData, array($_category) ); + $pass --; } elseif ($designUpdateData instanceof Mage_Catalog_Model_Category) { $_category = &$designUpdateData; } @@ -205,10 +257,7 @@ protected function _applyDesign($designUpdateData, $calledFrom = 0, $loaded = fa $_designUpdateData, $_category->getResource()->getDesignUpdateData($_category) ); - $this->_applyDesign( - $_designUpdateData, - $calledFrom, true - ); + $this->_applyDesign($_designUpdateData, $calledFrom, true, $pass); } } return $this; 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 bddc62c872..be07d989c4 100644 --- a/app/code/core/Mage/Catalog/Model/Layer/Filter/Price.php +++ b/app/code/core/Mage/Catalog/Model/Layer/Filter/Price.php @@ -196,22 +196,19 @@ public function apply(Zend_Controller_Request_Abstract $request, $filterBlock) if ((int)$index && (int)$range) { $this->setPriceRange((int)$range); - $entityIds = Mage::getSingleton('catalogindex/price')->getFilteredEntities( + + Mage::getSingleton('catalogindex/price')->applyFilterToCollection( + $this->getLayer()->getProductCollection(), $this->getAttributeModel(), $range, - $index, - $this->_getFilterEntityIds() + $index ); - if ($entityIds) { - $this->getLayer()->getProductCollection() - ->addFieldToFilter('entity_id', array('in'=>$entityIds)); + $this->getLayer()->getState()->addFilter( + $this->_createItem($this->_renderItemLabel($range, $index), $filter) + ); - $this->getLayer()->getState()->addFilter( - $this->_createItem($this->_renderItemLabel($range, $index), $filter) - ); - $this->_items = array(); - } + $this->_items = array(); } return $this; } diff --git a/app/code/core/Mage/Catalog/Model/Product/Attribute/Source/Layout.php b/app/code/core/Mage/Catalog/Model/Product/Attribute/Source/Layout.php new file mode 100644 index 0000000000..e9c66520d0 --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Product/Attribute/Source/Layout.php @@ -0,0 +1,50 @@ + + */ +class Mage_Catalog_Model_Product_Attribute_Source_Layout extends Mage_Eav_Model_Entity_Attribute_Source_Abstract +{ + public function getAllOptions() + { + if (!$this->_options) { + $layouts = array(); + foreach (Mage::getConfig()->getNode('global/cms/layouts')->children() as $layoutName=>$layoutConfig) { + $this->_options[] = array( + 'value'=>$layoutName, + 'label'=>(string)$layoutConfig->label + ); + } + array_unshift($this->_options, array('value'=>'', 'label'=>Mage::helper('catalog')->__('No layout updates'))); + } + return $this->_options; + } +} \ No newline at end of file diff --git a/app/code/core/Mage/Catalog/Model/Product/Flat/Observer.php b/app/code/core/Mage/Catalog/Model/Product/Flat/Observer.php index 074949adbc..4659757830 100644 --- a/app/code/core/Mage/Catalog/Model/Product/Flat/Observer.php +++ b/app/code/core/Mage/Catalog/Model/Product/Flat/Observer.php @@ -69,13 +69,11 @@ public function catalogEntityAttributeSaveAfter(Varien_Event_Observer $observer) /* @var $attribute Mage_Catalog_Model_Entity_Attribute */ $enableBefore = ($attribute->getOrigData('backend_type') == 'static') - || ($attribute->getOrigData('is_filterable') > 0) - || ($attribute->getOrigData('is_filterable_in_search') == 1) + || ($this->_getHelper()->isAddFilterableAttributes() && $attribute->getOrigData('is_filterable') > 0) || ($attribute->getOrigData('used_in_product_listing') == 1) || ($attribute->getOrigData('used_for_sort_by') == 1); $enableAfter = ($attribute->getData('backend_type') == 'static') - || ($attribute->getData('is_filterable') > 0) - || ($attribute->getData('is_filterable_in_search') == 1) + || ($this->_getHelper()->isAddFilterableAttributes() && $attribute->getData('is_filterable') > 0) || ($attribute->getData('used_in_product_listing') == 1) || ($attribute->getData('used_for_sort_by') == 1); @@ -287,4 +285,20 @@ public function customerGroupSaveAfter(Varien_Event_Observer $observer) } return $this; } + + /** + * Update category ids in flat + * + * @param Varien_Event_Observer $observer + * @return Mage_Catalog_Model_Product_Flat_Observer + */ + public function catalogCategoryChangeProducts(Varien_Event_Observer $observer) + { + if (!$this->_getHelper()->isBuilt()) { + return $this; + } + $this->_getIndexer()->updateAttribute('category_ids'); + + return $this; + } } 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 5f9e9e7804..c27042ebbc 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 @@ -150,21 +150,35 @@ public function prepareForCart() */ public function getFormattedOptionValue($optionValue) { - $option = $this->getOption(); + if ($this->_formattedOptionValue === null) { - 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); - } 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); - } 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()); - } else { - $result = $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); + } 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); + } 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()); + } else { + $result = $optionValue; + } + $this->_formattedOptionValue = $result; } - return $result; + return $this->_formattedOptionValue; + } + + /** + * Return printable option value + * + * @param string $optionValue Prepared for cart option value + * @return string + */ + public function getPrintableOptionValue($optionValue) + { + return $this->getFormattedOptionValue($optionValue); } /** diff --git a/app/code/core/Mage/Catalog/Model/Product/Option/Type/Default.php b/app/code/core/Mage/Catalog/Model/Product/Option/Type/Default.php index 53dd268bc0..80e1907fc0 100644 --- a/app/code/core/Mage/Catalog/Model/Product/Option/Type/Default.php +++ b/app/code/core/Mage/Catalog/Model/Product/Option/Type/Default.php @@ -195,6 +195,16 @@ public function prepareForCart() Mage::throwException(Mage::helper('catalog')->__('Option validation failed to add product to cart')); } + /** + * Flag to indicate that custom option has own customized output (blocks, native html etc.) + * + * @return boolean + */ + public function isCustomizedView() + { + return false; + } + /** * Return formatted option value for quote option * @@ -206,6 +216,28 @@ public function getFormattedOptionValue($optionValue) return $optionValue; } + /** + * Return option html + * + * @param array $optionInfo + * @return string + */ + public function getCustomizedView($optionInfo) + { + return isset($optionInfo['value']) ? $optionInfo['value'] : $optionInfo; + } + + /** + * Return printable option value + * + * @param string $optionValue Prepared for cart option value + * @return string + */ + public function getPrintableOptionValue($optionValue) + { + return $optionValue; + } + /** * Return formatted option value ready to edit, ready to parse * (ex: Admin re-order, see Mage_Adminhtml_Model_Sales_Order_Create) diff --git a/app/code/core/Mage/Catalog/Model/Product/Option/Type/File.php b/app/code/core/Mage/Catalog/Model/Product/Option/Type/File.php index a9ecc21f0a..990193ee52 100644 --- a/app/code/core/Mage/Catalog/Model/Product/Option/Type/File.php +++ b/app/code/core/Mage/Catalog/Model/Product/Option/Type/File.php @@ -33,6 +33,27 @@ */ class Mage_Catalog_Model_Product_Option_Type_File extends Mage_Catalog_Model_Product_Option_Type_Default { + public function isCustomizedView() + { + return true; + } + + /** + * Return option html + * + * @param array $optionInfo + * @return string + */ + public function getCustomizedView($optionInfo) + { + try { + $result = $this->_getOptionHtml($optionInfo['option_value']); + return $result; + } catch (Exception $e) { + return $optionInfo['value']; + } + } + /** * Validate user input for option * @@ -84,8 +105,18 @@ public function validateUserValue($values) $fileInfo = $fileInfo[$file]; } catch (Exception $e) { - $this->setIsValid(false); - Mage::throwException(Mage::helper('catalog')->__("Files upload failed")); + // when file exceeds the upload_max_filesize, $_FILES is empty + if (isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['CONTENT_LENGTH'] > $this->_getUploadMaxFilesize()) { + $this->setIsValid(false); + Mage::throwException( + Mage::helper('catalog')->__("The file you uploaded is larger than %s Megabytes allowed by server", + $this->_bytesToMbytes($this->_getUploadMaxFilesize()) + ) + ); + } else { + $this->setUserValue(null); + return $this; + } } /** @@ -94,10 +125,10 @@ public function validateUserValue($values) // Image dimensions $_dimentions = array(); - if ($option->getImageSizeX() > 0) { + if ($option->getImageSizeX() > 0 && $this->_isImage($fileInfo)) { $_dimentions['maxwidth'] = $option->getImageSizeX(); } - if ($option->getImageSizeY() > 0) { + if ($option->getImageSizeY() > 0 && $this->_isImage($fileInfo)) { $_dimentions['maxheight'] = $option->getImageSizeY(); } if (count($_dimentions) > 0) { @@ -115,6 +146,9 @@ public function validateUserValue($values) } } + // Maximum filesize + $upload->addValidator('FilesSize', false, array('max' => $this->_getUploadMaxFilesize())); + /** * Upload process */ @@ -189,6 +223,11 @@ public function validateUserValue($values) $option->getImageSizeX(), $option->getImageSizeY() ); + } elseif ($errorCode == Zend_Validate_File_FilesSize::TOO_BIG) { + $errors[] = Mage::helper('catalog')->__("The file '%s' you uploaded is larger than %s Megabytes allowed by server", + $fileInfo['name'], + $this->_bytesToMbytes($this->_getUploadMaxFilesize()) + ); } } if (count($errors) > 0) { @@ -229,29 +268,69 @@ public function prepareForCart() */ public function getFormattedOptionValue($optionValue) { - try { - $value = unserialize($optionValue); - if ($value !== false) { - if ($value['width'] > 0 && $value['height'] > 0) { - $sizes = $value['width'] . ' x ' . $value['height'] . ' ' . Mage::helper('catalog')->__('px.'); - } else { - $sizes = ''; - } - $result = sprintf('%s %s', - $this->_getOptionDownloadUrl($value['secret_key']), - Mage::helper('core')->htmlEscape($value['title']), - $sizes + if ($this->_formattedOptionValue === null) { + try { + $value = unserialize($optionValue); + + $value['url'] = array( + 'route' => 'sales/download/downloadCustomOption', + 'params' => array( + 'id' => $this->getQuoteItemOption()->getId(), + 'key' => $value['secret_key'] + ) ); - return $result; - } - throw new Exception(); + $this->_formattedOptionValue = $this->_getOptionHtml($value); + $this->getQuoteItemOption()->setValue(serialize($value)); + return $this->_formattedOptionValue; + + } catch (Exception $e) { + return $optionValue; + } + } + return $this->_formattedOptionValue; + } + /** + * Format File option html + * + * @param string|array $optionValue Serialized string of option data or its data array + * @return string + */ + protected function _getOptionHtml($optionValue) + { + try { + $value = unserialize($optionValue); } catch (Exception $e) { - return $optionValue; + $value = $optionValue; + } + try { + if ($value['width'] > 0 && $value['height'] > 0) { + $sizes = $value['width'] . ' x ' . $value['height'] . ' ' . Mage::helper('catalog')->__('px.'); + } else { + $sizes = ''; + } + return sprintf('%s %s', + $this->_getOptionDownloadUrl($value['url']['route'], $value['url']['params']), + Mage::helper('core')->htmlEscape($value['title']), + $sizes + ); + } catch (Exception $e) { + Mage::throwException(Mage::helper('catalog')->__("File options format is not valid")); } } + /** + * Return printable option value + * + * @param string $optionValue Prepared for cart option value + * @return string + */ + public function getPrintableOptionValue($optionValue) + { + return strip_tags($this->getFormattedOptionValue($optionValue)); + } + /** * Return formatted option value ready to edit, ready to parse * @@ -262,15 +341,10 @@ public function getEditableOptionValue($optionValue) { try { $value = unserialize($optionValue); - if ($value !== false) { - $result = sprintf('%s [%d]', - Mage::helper('core')->htmlEscape($value['title']), - $this->getQuoteItemOption()->getId() - ); - return $result; - } - - throw new Exception(); + return sprintf('%s [%d]', + Mage::helper('core')->htmlEscape($value['title']), + $this->getQuoteItemOption()->getId() + ); } catch (Exception $e) { return $optionValue; @@ -418,12 +492,9 @@ protected function _createWriteableDir($path) * * @return string */ - protected function _getOptionDownloadUrl($sekretKey) + protected function _getOptionDownloadUrl($route, $params) { - return Mage::getUrl('sales/download/downloadCustomOption', array( - 'id' => $this->getQuoteItemOption()->getId(), - 'key' => $sekretKey - )); + return Mage::getUrl($route, $params); } /** @@ -434,10 +505,75 @@ protected function _getOptionDownloadUrl($sekretKey) */ protected function _parseExtensionsString($extensions) { - preg_match_all('/[a-z]+/si', strtolower($extensions), $matches); + preg_match_all('/[a-z0-9]+/si', strtolower($extensions), $matches); if (isset($matches[0]) && is_array($matches[0]) && count($matches[0]) > 0) { return $matches[0]; } return null; } + + /** + * Simple check if file is image + * + * @param array $fileInfo File data from Zend_File_Transfer + * @return boolean + */ + protected function _isImage($fileInfo) + { + try { + + return strstr($fileInfo['type'], 'image/'); + + // We can use Zend Validator, but the lack of mime types + // $validator = new Zend_Validate_File_IsImage(); + // return $validator->isValid($fileInfo['tmp_name'], $fileInfo); + + } catch (Exception $e) { + return false; + } + } + + /** + * Max upload filesize in bytes + * + * @return int + */ + protected function _getUploadMaxFilesize() + { + return min($this->_getBytesIniValue('upload_max_filesize'), $this->_getBytesIniValue('post_max_size')); + } + + /** + * Return php.ini setting value in bytes + * + * @param string $ini_key php.ini Var name + * @return int Setting value + */ + protected function _getBytesIniValue($ini_key) + { + $_bytes = @ini_get($ini_key); + + // kilobytes + if (stristr($_bytes, 'k')) { + $_bytes = intval($_bytes) * 1024; + // megabytes + } elseif (stristr($_bytes, 'm')) { + $_bytes = intval($_bytes) * 1024 * 1024; + // gigabytes + } elseif (stristr($_bytes, 'g')) { + $_bytes = intval($_bytes) * 1024 * 1024 * 1024; + } + return (int)$_bytes; + } + + /** + * Simple converrt bytes to Megabytes + * + * @param int $bytes + * @return int + */ + protected function _bytesToMbytes($bytes) + { + return round($bytes / (1024 * 1024)); + } } \ No newline at end of file diff --git a/app/code/core/Mage/Catalog/Model/Product/Option/Type/Select.php b/app/code/core/Mage/Catalog/Model/Product/Option/Type/Select.php index 92b1a43b08..76d3ed94ec 100644 --- a/app/code/core/Mage/Catalog/Model/Product/Option/Type/Select.php +++ b/app/code/core/Mage/Catalog/Model/Product/Option/Type/Select.php @@ -70,10 +70,11 @@ public function validateUserValue($values) */ public function prepareForCart() { - if ($this->getIsValid()) { + if ($this->getIsValid() && $this->getUserValue()) { return is_array($this->getUserValue()) ? implode(',', $this->getUserValue()) : $this->getUserValue(); + } else { + return null; } - Mage::throwException(Mage::helper('catalog')->__('Option validation failed to add product to cart')); } /** @@ -84,8 +85,23 @@ public function prepareForCart() */ public function getFormattedOptionValue($optionValue) { - $result = $this->getEditableOptionValue($optionValue); - return Mage::helper('core')->htmlEscape($result); + if ($this->_formattedOptionValue === null) { + $this->_formattedOptionValue = Mage::helper('core')->htmlEscape( + $this->getEditableOptionValue($optionValue) + ); + } + return $this->_formattedOptionValue; + } + + /** + * Return printable option value + * + * @param string $optionValue Prepared for cart option value + * @return string + */ + public function getPrintableOptionValue($optionValue) + { + return $this->getFormattedOptionValue($optionValue); } /** diff --git a/app/code/core/Mage/Catalog/Model/Product/Type/Abstract.php b/app/code/core/Mage/Catalog/Model/Product/Type/Abstract.php index 7d673629f3..316fad1e65 100644 --- a/app/code/core/Mage/Catalog/Model/Product/Type/Abstract.php +++ b/app/code/core/Mage/Catalog/Model/Product/Type/Abstract.php @@ -434,8 +434,11 @@ public function getOrderOptions($product = null) $optionArr['options'][] = array( 'label' => $option->getTitle(), 'value' => $group->getFormattedOptionValue($quoteItemOption->getValue()), + 'print_value' => $group->getPrintableOptionValue($quoteItemOption->getValue()), 'option_id' => $option->getId(), - 'option_value' => $quoteItemOption->getValue() + 'option_type' => $option->getType(), + 'option_value' => $quoteItemOption->getValue(), + 'custom_view' => $group->isCustomizedView() ); } } diff --git a/app/code/core/Mage/Catalog/Model/Product/Type/Configurable/Attribute.php b/app/code/core/Mage/Catalog/Model/Product/Type/Configurable/Attribute.php index f43bf9a0ad..bfc376b4bf 100644 --- a/app/code/core/Mage/Catalog/Model/Product/Type/Configurable/Attribute.php +++ b/app/code/core/Mage/Catalog/Model/Product/Type/Configurable/Attribute.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Catalog - * @copyright Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com) + * @copyright Copyright (c) 2009 Irubin Consulting Inc. DBA Varien (http://www.varien.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -30,15 +30,25 @@ * * @category Mage * @package Mage_Catalog - * @author Magento Core Team + * @author Magento Core Team */ class Mage_Catalog_Model_Product_Type_Configurable_Attribute extends Mage_Core_Model_Abstract { + /** + * Initialize resource model + * + */ protected function _construct() { $this->_init('catalog/product_type_configurable_attribute'); } + /** + * Add price data to attribute + * + * @param array $priceData + * @return Mage_Catalog_Model_Product_Type_Configurable_Attribute + */ public function addPrice($priceData) { $data = $this->getPrices(); @@ -50,6 +60,11 @@ public function addPrice($priceData) return $this; } + /** + * Retrieve label + * + * @return string + */ public function getLabel() { if (is_null($this->getData('label')) && $this->getProductAttribute()) { @@ -68,6 +83,11 @@ public function getLabel() return $this; }*/ + /** + * After save process + * + * @return Mage_Catalog_Model_Product_Type_Configurable_Attribute + */ protected function _afterSave() { parent::_afterSave(); @@ -75,4 +95,4 @@ protected function _afterSave() $this->_getResource()->savePrices($this); return $this; } -} \ No newline at end of file +} diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Abstract.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Abstract.php index b9a7debcb9..aac5e37184 100644 --- a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Abstract.php +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Abstract.php @@ -266,7 +266,7 @@ protected function _deleteAttributes($object, $table, $info) $storeAttributes[] = $itemData['attribute_id']; } elseif ($attribute->isScopeWebsite()) { - $websiteAttributes = $itemData['attribute_id']; + $websiteAttributes[] = $itemData['attribute_id']; } else { $globalValues[] = $itemData['value_id']; diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category.php index 1246849527..dedfa43a23 100644 --- a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category.php +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Catalog - * @copyright Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com) + * @copyright Copyright (c) 2009 Irubin Consulting Inc. DBA Varien (http://www.varien.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -29,7 +29,7 @@ * * @category Mage * @package Mage_Catalog - * @author Magento Core Team + * @author Magento Core Team */ class Mage_Catalog_Model_Resource_Eav_Mysql4_Category extends Mage_Catalog_Model_Resource_Eav_Mysql4_Abstract { @@ -47,7 +47,6 @@ class Mage_Catalog_Model_Resource_Eav_Mysql4_Category extends Mage_Catalog_Model */ protected $_categoryProductTable; - /** * Id of 'is_active' category attribute * @@ -270,13 +269,13 @@ protected function _saveCategoryProducts($category) * new category-product relationships */ $products = $category->getPostedProducts(); + /** + * Example re-save category + */ if (is_null($products)) { return $this; } - $catId = $category->getId(); - $prodTable = $this->getTable('catalog/product'); - /** * old category-product relationships */ @@ -293,80 +292,73 @@ protected function _saveCategoryProducts($category) */ $update = array_diff_assoc($update, $oldProducts); - $write = $this->getWriteConnection(); - $updateProducts = array(); + $productTable = $this->getTable('catalog/product'); + $productUpdateSql = sprintf('UPDATE `%s` AS `e` SET `category_ids`=(SELECT + GROUP_CONCAT(`category_id`) FROM `%s` AS `cp` WHERE `cp`.`product_id`=`e`.`entity_id`) + WHERE `e`.`entity_id` IN(?)', $productTable, $this->_categoryProductTable); + /** + * Delete products from category + * + */ if (!empty($delete)) { $deleteIds = array_keys($delete); - $write->delete($this->_categoryProductTable, - $write->quoteInto('product_id in(?)', $deleteIds) . - $write->quoteInto(' AND category_id=?', $catId) + $this->_getWriteAdapter()->delete($this->_categoryProductTable, + $this->_getWriteAdapter()->quoteInto('product_id in(?)', $deleteIds) . + $this->_getWriteAdapter()->quoteInto(' AND category_id=?', $category->getId()) ); - /** - * Delete association rewrites - */ - Mage::getResourceSingleton('catalog/url')->deleteCategoryProductRewrites($catId, $deleteIds); - - $select = $write->select() - ->from($prodTable, array('entity_id', 'category_ids')) - ->where('entity_id IN (?)', $deleteIds); - $prods = $write->fetchPairs($select); - foreach ($prods as $k=>$v) { - $a = !empty($v) ? explode(',', $v) : array(); - $key = array_search($catId, $a); - if ($key!==false) { - unset($a[$key]); - } - $updateProducts[$k] = "when ".(int)$k." then '".implode(',', array_unique($a))."'"; - } + $sql = $this->_getWriteAdapter()->quoteInto($productUpdateSql, $deleteIds); + $this->_getWriteAdapter()->query($sql); } + /** + * Add products to category + * + */ if (!empty($insert)) { $insertSql = array(); - foreach ($insert as $k=>$v) { - $insertSql[] = '('.(int)$catId.','.(int)$k.','.(int)$v.')'; - } - - $write->query("insert into {$this->_categoryProductTable} - (category_id, product_id, position) values ".join(',', $insertSql)); - - $select = $write->select() - ->from($prodTable, array('entity_id', 'category_ids')) - ->where('entity_id IN (?)', array_keys($insert)); - - $prods = $write->fetchPairs($select); - foreach ($prods as $k=>$v) { - $a = !empty($v) ? explode(',', $v) : array(); - $a[] = (int)$catId; - $updateProducts[$k] = "when ".(int)$k." then '".implode(',', array_unique($a))."'"; + foreach ($insert as $k => $v) { + $insertSql[] = '('.(int)$category->getId().','.(int)$k.','.(int)$v.')'; } - } - - if (!empty($updateProducts)) { - $write->update( - $prodTable, - array('category_ids'=>new Zend_Db_Expr('case entity_id '.join(' ', $updateProducts).' end')), - $write->quoteInto('entity_id in (?)', array_keys($updateProducts)) + $sql = sprintf( + 'INSERT INTO `%s` (`category_id`,`product_id`,`position`) VALUES%s', + $this->_categoryProductTable, + join(',', $insertSql) ); + $this->_getWriteAdapter()->query($sql); + + $insertIds = array_keys($insert); + $sql = $this->_getWriteAdapter()->quoteInto($productUpdateSql, $insertIds); + $this->_getWriteAdapter()->query($sql); } + /** + * Update product positions in category + * + */ if (!empty($update)) { - $updateProductsPosition = array(); - foreach ($update as $k=>$v) { - if ($v!=$oldProducts[$k]) { - $updateProductsPosition[$k] = 'when '.(int)$k.' then '.(int)$v; - } - } - if (!empty($updateProductsPosition)) { - $write->update($this->_categoryProductTable, - array('position'=>new Zend_Db_Expr('case product_id '.join(' ', $updateProductsPosition).' end')), - $write->quoteInto('product_id in (?)', array_keys($updateProductsPosition)) - .' and '.$write->quoteInto('category_id=?', $catId) + foreach ($update as $k => $v) { + $cond = array( + $this->_getWriteAdapter()->quoteInto('category_id=?', (int)$category->getId()), + $this->_getWriteAdapter()->quoteInto('product_id=?', (int)$k) ); + $where = join(' AND ', $cond); + $bind = array( + 'position' => (int)$v + ); + $this->_getWriteAdapter()->update($this->_categoryProductTable, $bind, $where); } } + if (!empty($insert) || !empty($delete)) { + $productIds = array_unique(array_merge(array_keys($insert), array_keys($delete))); + Mage::dispatchEvent('catalog_category_change_products', array( + 'category' => $category, + 'product_ids' => $productIds + )); + } + if (!empty($insert) || !empty($update) || !empty($delete)) { $category->setIsChangedProductList(true); $categoryIds = explode('/', $category->getPath()); diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category/Collection.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category/Collection.php index 695eb57375..5a81d4fac9 100644 --- a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category/Collection.php +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category/Collection.php @@ -296,6 +296,8 @@ public function joinUrlRewrite() public function addIsActiveFilter() { $this->addAttributeToFilter('is_active', 1); + Mage::dispatchEvent($this->_eventPrefix . '_add_is_active_filter', + array($this->_eventObject => $this)); return $this; } diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category/Flat.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category/Flat.php index 2d8cb30e9b..56c919b2c9 100644 --- a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category/Flat.php +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category/Flat.php @@ -40,6 +40,13 @@ class Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Flat extends Mage_Core_Mod protected $_nodes = array(); + /** + * Inactive categories ids + * + * @var array + */ + protected $_inactiveCategoryIds = null; + protected $_isRebuilt = null; protected function _construct() @@ -110,6 +117,46 @@ public function getUseStoreTables() return true; } + /** + * Add inactive categories ids + * + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Flat + */ + public function addInactiveCategoryIds($ids) + { + if (!is_array($this->_inactiveCategoryIds)) { + $this->_initInactiveCategoryIds(); + } + $this->_inactiveCategoryIds = array_merge($ids, $this->_inactiveCategoryIds); + return $this; + } + + /** + * Retreive inactive categories ids + * + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Flat + */ + protected function _initInactiveCategoryIds() + { + $this->_inactiveCategoryIds = array(); + Mage::dispatchEvent('catalog_category_tree_init_inactive_category_ids', array('tree'=>$this)); + return $this; + } + + /** + * Retreive inactive categories ids + * + * @return array + */ + public function getInactiveCategoryIds() + { + if (!is_array($this->_inactiveCategoryIds)) { + $this->_initInactiveCategoryIds(); + } + + return $this->_inactiveCategoryIds; + } + /** * Load nodes by parent id * @@ -155,6 +202,12 @@ protected function _loadNodes($parentNode = null, $recursionLevel = 0, $storeId $select->where("main_table.level <= ?", $startLevel + $recursionLevel); } + $inactiveCategories = $this->getInactiveCategoryIds(); + + if (!empty($inactiveCategories)) { + $select->where('main_table.entity_id NOT IN (?)', $inactiveCategories); + } + $arrNodes = $_conn->fetchAll($select); $nodes = array(); foreach ($arrNodes as $node) { @@ -162,7 +215,6 @@ protected function _loadNodes($parentNode = null, $recursionLevel = 0, $storeId $nodes[$node['id']] = Mage::getModel('catalog/category')->setData($node); } - Mage::dispatchEvent('catalog_category_flat_load_nodes_after', array('nodes'=>$nodes)); return $nodes; } @@ -862,4 +914,21 @@ public function getDesignUpdateData($category) } return $categories; } + + /** + * Retrieve anchors above + * + * @param array $filterIds + * @param int $storeId + * @return array + */ + public function getAnchorsAbove(array $filterIds, $storeId = 0) + { + $select = $this->_getReadAdapter()->select() + ->from(array('e' => $this->getMainStoreTable($storeId)), 'entity_id') + ->where('is_anchor = ?', 1) + ->where('entity_id IN (?)', $filterIds); + + return $this->_getReadAdapter()->fetchCol($select); + } } diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category/Flat/Collection.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category/Flat/Collection.php index ea95deaa23..7d7f277b32 100644 --- a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category/Flat/Collection.php +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category/Flat/Collection.php @@ -193,6 +193,8 @@ public function addSortedField($sorted) public function addIsActiveFilter() { $this->addFieldToFilter('is_active', 1); + Mage::dispatchEvent($this->_eventPrefix . '_add_is_active_filter', + array($this->_eventObject => $this)); return $this; } @@ -242,6 +244,48 @@ public function addAttributeToSelect($attribute = '*') return $this; } + /** + * Retrieve resource instance + * + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Flat + */ + public function getResource() + { + return parent::getResource(); + } + + /** + * Add attribute to sort order + * + * @param string $attribute + * @param string $dir + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Flat_Collection + */ + public function addAttributeToSort($attribute, $dir='asc') + { + if (!is_string($attribute)) { + return $this; + } + $this->setOrder($attribute, $dir); + return $this; + } + + /** + * Emulate simple add attribute filter to collection + * + * @param string $attribute + * @param mixed $condition + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Flat_Collection + */ + public function addAttributeToFilter($attribute, $condition = null) + { + if (!is_string($attribute) || $condition === null) { + return $this; + } + + return $this->addFieldToFilter($attribute, $condition); + } + public function addUrlRewriteToResult() { $storeId = Mage::app()->getStore()->getId(); diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category/Tree.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category/Tree.php index 6200db8df3..bc8d4cf845 100644 --- a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category/Tree.php +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category/Tree.php @@ -51,6 +51,13 @@ class Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Tree extends Varien_Data_T protected $_joinUrlRewriteIntoCollection = false; + /** + * Inactive categories ids + * + * @var array + */ + protected $_inactiveCategoryIds = null; + /** * Enter description here... * @@ -104,6 +111,7 @@ public function addCollectionData($collection=null, $sorted=false, $exclude=arra } $collection->addIdFilter($nodeIds); if ($onlyActive) { + $disabledIds = $this->_getDisabledIds($collection); if ($disabledIds) { $collection->addFieldToFilter('entity_id', array('nin'=>$disabledIds)); @@ -129,10 +137,58 @@ public function addCollectionData($collection=null, $sorted=false, $exclude=arra return $this; } + /** + * Add inactive categories ids + * + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Flat + */ + public function addInactiveCategoryIds($ids) + { + if (!is_array($this->_inactiveCategoryIds)) { + $this->_initInactiveCategoryIds(); + } + $this->_inactiveCategoryIds = array_merge($ids, $this->_inactiveCategoryIds); + return $this; + } + + /** + * Retreive inactive categories ids + * + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Flat + */ + protected function _initInactiveCategoryIds() + { + $this->_inactiveCategoryIds = array(); + Mage::dispatchEvent('catalog_category_tree_init_inactive_category_ids', array('tree'=>$this)); + return $this; + } + + /** + * Retreive inactive categories ids + * + * @return array + */ + public function getInactiveCategoryIds() + { + if (!is_array($this->_inactiveCategoryIds)) { + $this->_initInactiveCategoryIds(); + } + + return $this->_inactiveCategoryIds; + } + protected function _getDisabledIds($collection) { $storeId = Mage::app()->getStore()->getId(); - $this->_inactiveItems = $this->_getInactiveItemIds($collection, $storeId); + + $this->_inactiveItems = $this->getInactiveCategoryIds(); + + + $this->_inactiveItems = array_merge( + $this->_getInactiveItemIds($collection, $storeId), + $this->_inactiveItems + ); + $allIds = $collection->getAllIds(); $disabledIds = array(); diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Collection.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Collection.php index 5c0d483836..1408c75a5b 100644 --- a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Collection.php +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Collection.php @@ -241,9 +241,13 @@ protected function _initSelect() if ($this->isEnabledFlat()) { $this->getSelect() ->from(array('e' => $this->getEntity()->getFlatTableName()), null) - ->where('e.is_child=?', 0) ->from(null, array('status' => new Zend_Db_Expr(Mage_Catalog_Model_Product_Status::STATUS_ENABLED))); - $this->addAttributeToSelect(array('entity_id', 'type_id', 'child_id', 'is_child', 'attribute_set_id')); + $this->addAttributeToSelect(array('entity_id', 'type_id', 'attribute_set_id')); + if ($this->getFlatHelper()->isAddChildData()) { + $this->getSelect() + ->where('e.is_child=?', 0); + $this->addAttributeToSelect(array('child_id', 'is_child')); + } } else { $this->getSelect()->from(array('e'=>$this->getEntity()->getEntityTable())); @@ -994,6 +998,16 @@ public function addAttributeToFilter($attribute, $condition=null, $joinType='inn $attribute = $attribute->getAttributeCode(); } + if (is_array($attribute)) { + $sqlArr = array(); + foreach ($attribute as $condition) { + $sqlArr[] = $this->_getAttributeConditionSql($condition['attribute'], $condition, $joinType); + } + $conditionSql = '('.join(') OR (', $sqlArr).')'; + $this->getSelect()->where($conditionSql); + return $this; + } + if (!isset($this->_selectAttributes[$attribute])) { $this->addAttributeToSelect($attribute); } @@ -1162,7 +1176,10 @@ public function setOrder($attribute, $dir='desc') } } else { if ($this->isEnabledFlat()) { - if ($sortColumn = $this->getEntity()->getAttributeSortColumn($attribute)) { + if ($attribute == 'position') { + $this->getSelect()->order("{$attribute} {$dir}"); + } + elseif ($sortColumn = $this->getEntity()->getAttributeSortColumn($attribute)) { $this->getSelect()->order("e.{$sortColumn} {$dir}"); } } diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Flat.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Flat.php index 75854ac7d8..b90eb06955 100644 --- a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Flat.php +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Flat.php @@ -85,7 +85,7 @@ public function getFlatTableName($store = null) */ public function getTypeId() { - return Mage::getSingleton('eav/config') + return Mage::getSingleton('catalog/config') ->getEntityType('catalog_product') ->getEntityTypeId(); } @@ -139,4 +139,58 @@ public function getAllTableColumns() $describe = $this->_getWriteAdapter()->describeTable($this->getFlatTableName()); return array_keys($describe); } + + /** + * Check whether the attribute is a real field in entity table + * Rewrited for EAV Collection + * + * @param integer|string|Mage_Eav_Model_Entity_Attribute_Abstract $attribute + * @return bool + */ + public function isAttributeStatic($attribute) + { + $attributeCode = null; + if ($attribute instanceof Mage_Eav_Model_Entity_Attribute_Interface) { + $attributeCode = $attribute->getAttributeCode(); + } + elseif (is_string($attribute)) { + $attributeCode = $attribute; + } + elseif (is_numeric($attribute)) { + $attributeCode = $this->getAttribute($attribute) + ->getAttributeCode(); + } + + if ($attributeCode) { + $columns = $this->getAllTableColumns(); + if (in_array($attributeCode, $columns)) { + return true; + } + } + + return false; + } + /** + * Retrieve entity id field name in entity table + * Rewrited for EAV collection compatible + * + * @return string + */ + public function getEntityIdField() + { + return $this->getIdFieldName(); + } + + /** + * Retrieve attribute instance + * Special for non static flat table + * + * @param mixed $attribute + * @return Mage_Eav_Model_Entity_Attribute_Abstract + */ + public function getAttribute($attribute) + { + return Mage::getSingleton('catalog/config') + ->getAttribute('catalog_product', $attribute); + } } diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Flat/Indexer.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Flat/Indexer.php index 6b35839cf1..20d574459d 100644 --- a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Flat/Indexer.php +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Flat/Indexer.php @@ -153,12 +153,13 @@ public function getAttributeCodes() $this->_attributeCodes = array(); $whereCond = array( $this->_getReadAdapter()->quoteInto('backend_type=?', 'static'), - $this->_getReadAdapter()->quoteInto('is_filterable>?', 0), - $this->_getReadAdapter()->quoteInto('is_filterable_in_search=?', 1), $this->_getReadAdapter()->quoteInto('used_in_product_listing=?', 1), $this->_getReadAdapter()->quoteInto('used_for_sort_by=?', 1), $this->_getReadAdapter()->quoteInto('attribute_code IN(?)', $this->_systemAttributes) ); + if ($this->getFlatHelper()->isAddFilterableAttributes()) { + $whereCond[] = $this->_getReadAdapter()->quoteInto('is_filterable>?', 0); + } $select = $this->_getReadAdapter()->select() ->from($this->getTable('eav/attribute'), array('attribute_id','attribute_code')) @@ -262,51 +263,56 @@ public function getFlatTableName($store) public function getFlatColumns() { if (is_null($this->_columns)) { - $this->_columns = array( - 'entity_id' => array( - 'type' => 'int(10)', - 'unsigned' => true, - 'is_null' => false, - 'default' => null, - 'extra' => 'auto_increment' - ), - 'child_id' => array( + $this->_columns = array(); + $this->_columns['entity_id'] = array( + 'type' => 'int(10)', + 'unsigned' => true, + 'is_null' => false, + 'default' => null, + 'extra' => 'auto_increment' + ); + if ($this->getFlatHelper()->isAddChildData()) { + $this->_columns['child_id'] = array( 'type' => 'int(10)', 'unsigned' => true, 'is_null' => true, 'default' => null, 'extra' => null - ), - 'is_child' => array( + ); + $this->_columns['is_child'] = array( 'type' => 'tinyint(1)', 'unsigned' => true, 'is_null' => false, 'default' => 0, 'extra' => null - ), - 'attribute_set_id' => array( - 'type' => 'smallint(5)', - 'unsigned' => true, - 'is_null' => false, - 'default' => 0, - 'extra' => null - ), - 'type_id' => array( - 'type' => 'varchar(32)', - 'unsigned' => false, - 'is_null' => false, - 'default' => 'simple', - 'extra' => null - ) + ); + } + $this->_columns['attribute_set_id'] = array( + 'type' => 'smallint(5)', + 'unsigned' => true, + 'is_null' => false, + 'default' => 0, + 'extra' => null + ); + $this->_columns['type_id'] = array( + 'type' => 'varchar(32)', + 'unsigned' => false, + 'is_null' => false, + 'default' => 'simple', + 'extra' => null ); foreach ($this->getAttributes() as $attribute) { /* @var $attribute Mage_Eav_Model_Entity_Attribute */ - if (is_null($attribute->getFlatColumns())) { + $columns = $attribute + ->setFlatAddFilterableAttributes($this->getFlatHelper()->isAddFilterableAttributes()) + ->setFlatAddChildData($this->getFlatHelper()->isAddChildData()) + ->getFlatColumns(); + if (is_null($columns)) { continue; } - $this->_columns = array_merge($this->_columns, $attribute->getFlatColumns()); + $this->_columns = array_merge($this->_columns, $columns); } $columnsObject = new Varien_Object(); @@ -327,35 +333,47 @@ public function getFlatColumns() public function getFlatIndexes() { if (is_null($this->_indexes)) { - $this->_indexes = array( - 'PRIMARY' => array( + $this->_indexes = array(); + + if ($this->getFlatHelper()->isAddChildData()) { + $this->_indexes['PRIMARY'] = array( 'type' => 'primary', 'fields' => array('entity_id', 'child_id') - ), - 'IDX_CHILD_ID' => array( + ); + $this->_indexes['IDX_CHILD'] = array( 'type' => 'index', 'fields' => array('child_id') - ), - 'IDX_IS_CHILD' => array( + ); + $this->_indexes['IDX_IS_CHILD'] = array( 'type' => 'index', 'fields' => array('entity_id', 'is_child') - ), - 'IDX_TYPE_ID' => array( - 'type' => 'index', - 'fields' => array('type_id') - ), - 'IDX_ATRRIBUTE_SET' => array( - 'type' => 'index', - 'fields' => array('attribute_set_id') - ), + ); + } + else { + $this->_indexes['PRIMARY'] = array( + 'type' => 'primary', + 'fields' => array('entity_id') + ); + } + $this->_indexes['IDX_TYPE_ID'] = array( + 'type' => 'index', + 'fields' => array('type_id') + ); + $this->_indexes['IDX_ATRRIBUTE_SET'] = array( + 'type' => 'index', + 'fields' => array('attribute_set_id') ); foreach ($this->getAttributes() as $attribute) { /* @var $attribute Mage_Eav_Model_Entity_Attribute */ - if (is_null($attribute->getFlatColumns())) { + $indexes = $attribute + ->setFlatAddFilterableAttributes($this->getFlatHelper()->isAddFilterableAttributes()) + ->setFlatAddChildData($this->getFlatHelper()->isAddChildData()) + ->getFlatIndexes(); + if (is_null($indexes)) { continue; } - $this->_indexes = array_merge($this->_indexes, $attribute->getFlatIndexes()); + $this->_indexes = array_merge($this->_indexes, $indexes); } $indexesObject = new Varien_Object(); @@ -434,10 +452,12 @@ public function prepareFlatTable($store) } $sql .= " CONSTRAINT `FK_CATALOG_PRODUCT_FLAT_{$store}_ENTITY` FOREIGN KEY (`entity_id`)" - . " REFERENCES `{$this->getTable('catalog/product')}` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE,\n" - . " CONSTRAINT `FK_CATALOG_PRODUCT_FLAT_{$store}_CHILD` FOREIGN KEY (`child_id`)" - . " REFERENCES `{$this->getTable('catalog/product')}` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE\n" - . ") ENGINE=InnoDB DEFAULT CHARSET=utf8"; + . " REFERENCES `{$this->getTable('catalog/product')}` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE"; + if ($this->getFlatHelper()->isAddChildData()) { + $sql .= ",\n CONSTRAINT `FK_CATALOG_PRODUCT_FLAT_{$store}_CHILD` FOREIGN KEY (`child_id`)" + . " REFERENCES `{$this->getTable('catalog/product')}` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE"; + } + $sql .= "\n) ENGINE=InnoDB DEFAULT CHARSET=utf8"; $this->_getWriteAdapter()->query($sql); } else { @@ -449,9 +469,43 @@ public function prepareFlatTable($store) $addIndexes = array_diff_key($indexes, $indexList); $dropIndexes = array_diff_key($indexList, $indexes); + $addConstraints = array(); + + if (!$this->getFlatHelper()->isAddChildData() && isset($describe['is_child'])) { + $this->_getWriteAdapter()->delete($tableName, 'is_child=1'); + $this->_getWriteAdapter()->dropForeignKey($tableName, "FK_CATALOG_PRODUCT_FLAT_{$store}_CHILD"); + } + if ($this->getFlatHelper()->isAddChildData() && !isset($describe['is_child'])) { + $this->_getWriteAdapter()->truncate($tableName); + $dropIndexes['PRIMARY'] = $indexList['PRIMARY']; + $addIndexes['PRIMARY'] = $indexes['PRIMARY']; + $addConstraints["FK_CATALOG_PRODUCT_FLAT_{$store}_CHILD"] = array( + 'table_index' => 'child_id', + 'ref_table' => $this->getTable('catalog/product'), + 'ref_index' => 'entity_id', + 'on_update' => 'CASCADE', + 'on_delete' => 'CASCADE' + ); + } if ($addColumns or $dropColumns or $addIndexes or $dropIndexes) { $sql = "ALTER TABLE {$tableNameQuote}"; + // drop columns + foreach ($dropColumns as $columnName => $columnProp) { + $columnNameQuote = $this->_getWriteAdapter()->quoteIdentifier($columnName); + $sql .= " DROP COLUMN {$columnNameQuote},"; + } + // drop indexes + foreach ($dropIndexes as $indexName => $indexProp) { + if ($indexName == 'PRIMARY') { + $sql .= " DROP PRIMARY KEY,"; + } + else { + $indexNameQuote = $this->_getWriteAdapter()->quoteIdentifier($indexName); + $sql .= " DROP INDEX {$indexNameQuote},"; + } + } + // add columns foreach ($addColumns as $columnName => $columnProp) { //var_dump($columnProp); @@ -463,6 +517,9 @@ public function prepareFlatTable($store) $sql .= ($columnProp['default'] === null ? ' DEFAULT NULL' : $this->_getReadAdapter()->quoteInto(' DEFAULT ?', $columnProp['default'])); + if ($afterField = $this->_arrayPrevKey($columns, $columnName)) { + $sql .= ' AFTER ' . $this->_getWriteAdapter()->quoteIdentifier($afterField); + } $sql .= ","; } // add indexes @@ -498,20 +555,21 @@ public function prepareFlatTable($store) $sql .= " ADD {$condition} ({$fieldSql}),"; } - // drop columns - foreach ($dropColumns as $columnName => $columnProp) { - $columnNameQuote = $this->_getWriteAdapter()->quoteIdentifier($columnName); - $sql .= " DROP COLUMN {$columnNameQuote},"; - } - // drop indexes - foreach ($dropIndexes as $indexName => $indexProp) { - $indexNameQuote = $this->_getWriteAdapter()->quoteIdentifier($indexName); - $sql .= " DROP INDEX {$indexNameQuote},"; - } - $sql = rtrim($sql, ","); $this->_getWriteAdapter()->query($sql); } + + foreach ($addConstraints as $constraintName => $constraintProp) { + $this->_getWriteAdapter()->addConstraint( + $constraintName, + $tableName, + $constraintProp['table_index'], + $constraintProp['ref_table'], + $constraintProp['ref_index'], + $constraintProp['on_delete'], + $constraintProp['on_update'] + ); + } } return $this; @@ -529,13 +587,19 @@ public function updateStaticAttributes($store, $productIds = null) $website = Mage::app()->getStore($store)->getWebsite()->getId(); $status = $this->getAttribute('status'); /* @var $status Mage_Eav_Model_Entity_Attribute */ - $fieldList = array('entity_id', 'child_id', 'is_child', 'type_id', 'attribute_set_id'); - $isChild = new Zend_Db_Expr('0'); + $fieldList = array('entity_id', 'type_id', 'attribute_set_id'); + $colsList = array('entity_id', 'type_id', 'attribute_set_id'); + if ($this->getFlatHelper()->isAddChildData()) { + $fieldList = array_merge($fieldList, array('child_id', 'is_child')); + $isChild = new Zend_Db_Expr('0'); + $colsList = array_merge($colsList, array('entity_id', $isChild)); + } + $columns = $this->getFlatColumns(); $select = $this->_getWriteAdapter()->select() ->from( array('e' => $this->getTable('catalog/product')), - array('entity_id', 'entity_id', $isChild, 'type_id', 'attribute_set_id')) + $colsList) ->join( array('wp' => $this->getTable('catalog/product_website')), "`e`.`entity_id`=`wp`.`product_id` AND `wp`.`website_id`={$website}", @@ -586,19 +650,26 @@ public function cleanNonWebsiteProducts($store, $productIds = null) { $website = Mage::app()->getStore($store)->getWebsite()->getId(); + $joinCond = "`e`.`entity_id`=`wp`.`product_id` AND `wp`.`website_id`={$website}"; + if ($this->getFlatHelper()->isAddChildData()) { + $joinCond .= " AND `e`.`child_id`=`wp`.`product_id`"; + } + $select = $this->_getWriteAdapter()->select() ->from( array('e' => $this->getFlatTableName($store)), null) ->joinLeft( array('wp' => $this->getTable('catalog/product_website')), - "`e`.`entity_id`=`wp`.`product_id` AND `e`.`child_id`=`wp`.`product_id` AND `wp`.`website_id`={$website}", + $joinCond, array()); if (!is_null($productIds)) { $cond = array( - $this->_getWriteAdapter()->quoteInto('e.entity_id IN(?)', $productIds), - $this->_getWriteAdapter()->quoteInto('e.child_id IN(?)', $productIds) + $this->_getWriteAdapter()->quoteInto('e.entity_id IN(?)', $productIds) ); + if ($this->getFlatHelper()->isAddChildData()) { + $cond[] = $this->_getWriteAdapter()->quoteInto('e.child_id IN(?)', $productIds); + } $select->where(join(' OR ', $cond)); } @@ -620,12 +691,19 @@ public function updateAttribute($attribute, $store, $productIds = null) { if ($attribute->getBackend()->getType() == 'static') { $select = $this->_getWriteAdapter()->select() - ->from(array('main_table' => $this->getTable('catalog/product')), $attribute->getAttributeCode()); + ->join( + array('main_table' => $this->getTable('catalog/product')), + 'main_table.entity_id=e.entity_id ', + array($attribute->getAttributeCode() => 'main_table.' . $attribute->getAttributeCode()) + ); + if ($this->getFlatHelper()->isAddChildData()) { + $select->where("e.is_child=?", 0); + } if (!is_null($productIds)) { $select->where('main_table.entity_id IN(?)', $productIds); } $sql = $select->crossUpdateFromSelect(array('e' => $this->getFlatTableName($store))); - $this->_getWriteAdapter()->query($sql); + $this->_getWriteAdapter()->query($sql); } else { $select = $attribute->getFlatUpdateSelect($store); @@ -702,7 +780,10 @@ public function getProductTypeInstances() */ public function updateRelationProducts($store, $productIds = null) { -// $website = Mage::app()->getStore($store)->getWebsite()->getId(); + if (!$this->getFlatHelper()->isAddChildData()) { + return $this; + } + foreach ($this->getProductTypeInstances() as $typeInstance) { if (!$typeInstance->isComposite()) { continue; @@ -723,12 +804,6 @@ public function updateRelationProducts($store, $productIds = null) ->from( array('t' => $this->getTable($relation->getTable())), array($relation->getParentFieldName(), $relation->getChildFieldName(), new Zend_Db_Expr('1'))) -// ->join( -// array('wp' => $this->getTable('catalog/product_website')), -// "`t`.`{$relation->getChildFieldName()}`=`wp`.`product_id`" -// . " AND `t`.`{$relation->getParentFieldName()}`=`wp`.`product_id`" -// . " AND `wp`.`website_id`={$website}", -// array()) ->join( array('e' => $this->getFlatTableName($store)), "`e`.`entity_id`=`t`.`{$relation->getChildFieldName()}`", @@ -762,6 +837,10 @@ public function updateRelationProducts($store, $productIds = null) */ public function updateChildrenDataFromParent($store, $productIds = null) { + if (!$this->getFlatHelper()->isAddChildData()) { + return $this; + } + $select = $this->_getWriteAdapter()->select(); foreach (array_keys($this->getFlatColumns()) as $columnName) { if ($columnName == 'entity_id' || $columnName == 'child_id' || $columnName == 'is_child') { @@ -794,6 +873,10 @@ public function updateChildrenDataFromParent($store, $productIds = null) */ public function cleanRelationProducts($store) { + if (!$this->getFlatHelper()->isAddChildData()) { + return $this; + } + foreach ($this->getProductTypeInstances() as $typeInstance) { if (!$typeInstance->isComposite()) { continue; @@ -851,9 +934,11 @@ public function cleanRelationProducts($store) public function removeProduct($productIds, $store) { $cond = array( - $this->_getWriteAdapter()->quoteInto('entity_id IN(?)', $productIds), - $this->_getWriteAdapter()->quoteInto('child_id IN(?)', $productIds) + $this->_getWriteAdapter()->quoteInto('entity_id IN(?)', $productIds) ); + if ($this->getFlatHelper()->isAddChildData()) { + $cond[] = $this->_getWriteAdapter()->quoteInto('child_id IN(?)', $productIds); + } $cond = join(' OR ', $cond); $this->_getWriteAdapter()->delete($this->getFlatTableName($store), $cond); @@ -869,6 +954,9 @@ public function removeProduct($productIds, $store) */ public function removeProductChildren($productIds, $store) { + if (!$this->getFlatHelper()->isAddChildData()) { + return $this; + } $cond = array( $this->_getWriteAdapter()->quoteInto('entity_id IN(?)', $productIds), $this->_getWriteAdapter()->quoteInto('is_child=?', 1), @@ -932,4 +1020,43 @@ public function deleteFlatTable($store) return $this; } + + /** + * Retrieve previous key from array by key + * + * @param array $array + * @param mixed $key + * @return mixed + */ + protected function _arrayPrevKey(array $array, $key) + { + $prev = false; + foreach (array_keys($array) as $k) { + if ($k == $key) { + return $prev; + } + $prev = $k; + } + } + + /** + * Retrieve next key from array by key + * + * @param array $array + * @param mixed $key + * @return mixed + */ + protected function _arrayNextKey(array $array, $key) + { + $next = false; + foreach (array_keys($array) as $k) { + if ($next === true) { + return $k; + } + if ($k == $key) { + $next = true; + } + } + return false; + } } diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Type/Configurable/Attribute.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Type/Configurable/Attribute.php index 34df3d48c3..160fe465fb 100644 --- a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Type/Configurable/Attribute.php +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Type/Configurable/Attribute.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Catalog - * @copyright Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com) + * @copyright Copyright (c) 2009 Irubin Consulting Inc. DBA Varien (http://www.varien.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -30,13 +30,28 @@ * * @category Mage * @package Mage_Catalog - * @author Magento Core Team + * @author Magento Core Team */ class Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Type_Configurable_Attribute extends Mage_Core_Model_Mysql4_Abstract { + /** + * Label table name cache + * + * @var string + */ protected $_labelTable; + + /** + * Price table name cache + * + * @var string + */ protected $_priceTable; + /** + * Inititalize connection and define tables + * + */ protected function _construct() { $this->_init('catalog/product_super_attribute', 'product_super_attribute_id'); @@ -44,22 +59,52 @@ protected function _construct() $this->_priceTable = $this->getTable('catalog/product_super_attribute_pricing'); } - public function loadLabel($attribute) + /** + * Retrieve Catalog Helper + * + * @return Mage_Catalog_Helper_Data + */ + public function getCatalogHelper() { + return Mage::helper('catalog'); + } + /** + * Load attribute labels + * + * @deprecated + * @param Mage_Eav_Model_Entity_Attribute_Abstract $attribute + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Type_Configurable_Attribute + */ + public function loadLabel($attribute) + { + return $this; } + /** + * Load prices + * + * @deprecated + * @param Mage_Eav_Model_Entity_Attribute_Abstract $attribute + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Type_Configurable_Attribute + */ public function loadPrices($attribute) { - + return $this; } + /** + * Save Custom labels for Attribute name + * + * @param Mage_Eav_Model_Entity_Attribute_Abstract $attribute + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Type_Configurable_Attribute + */ public function saveLabel($attribute) { $select = $this->_getWriteAdapter()->select() ->from($this->_labelTable, 'value_id') ->where('product_super_attribute_id=?', $attribute->getId()) - ->where('store_id=?', (int) $attribute->getStoreId()); + ->where('store_id=?', (int)$attribute->getStoreId()); if ($valueId = $this->_getWriteAdapter()->fetchOne($select)) { $this->_getWriteAdapter()->update($this->_labelTable,array('value'=>$attribute->getLabel()), $this->_getWriteAdapter()->quoteInto('value_id=?', $valueId) @@ -75,24 +120,153 @@ public function saveLabel($attribute) return $this; } + /** + * Save Options prices (Depends from price save scope) + * + * @param Mage_Eav_Model_Entity_Attribute_Abstract $attribute + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Type_Configurable_Attribute + */ public function savePrices($attribute) { - $this->_getWriteAdapter()->delete($this->_priceTable, - $this->_getWriteAdapter()->quoteInto('product_super_attribute_id=?', $attribute->getId()) - ); - $prices = $attribute->getValues(); - foreach ($prices as $data) { - if(empty($data['pricing_value'])) { - continue; + $newValues = $attribute->getValues(); + + $oldValues = array(); + $valueIndexes = array(); + $select = $this->_getWriteAdapter()->select() + ->from($this->_priceTable) + ->where('product_super_attribute_id=?', $attribute->getId()); + $query = $this->_getWriteAdapter()->query($select); + while ($row = $query->fetch()) { + $key = join('-', array($row['website_id'], $row['value_index'])); + $oldValues[$key] = $row; + } + + $delete = array(); + $insert = array(); + $update = array(); + + foreach ($newValues as $value) { + $valueIndexes[$value['value_index']] = $value['value_index']; + } + + if ($this->getCatalogHelper()->isPriceGlobal()) { + foreach ($oldValues as $row) { + if (!isset($valueIndexes[$row['value_index']])) { + $delete[] = $row['value_id']; + continue; + } + } + foreach ($newValues as $value) { + $valueObject = new Varien_Object($value); + $key = join('-', array(0, $value['value_index'])); + + $pricingValue = $valueObject->getPricingValue(); + if ($pricingValue == '' || is_null($pricingValue)) { + $pricingValue = null; + } + else { + $pricingValue = Mage::app()->getLocale()->getNumber($pricingValue); + } + // update + if (isset($oldValues[$key])) { + $oldValue = $oldValues[$key]; + $update[$oldValue['value_id']] = array( + 'pricing_value' => $pricingValue, + 'is_percent' => intval($valueObject->getIsPercent()) + ); + } + // insert + else { + if (!empty($value['pricing_value'])) { + $insert[] = array( + 'product_super_attribute_id' => $attribute->getId(), + 'value_index' => $valueObject->getValueIndex(), + 'is_percent' => intval($valueObject->getIsPercent()), + 'pricing_value' => $pricingValue, + 'website_id' => 0 + ); + } + } } - $priceObject = new Varien_Object($data); - $this->_getWriteAdapter()->insert($this->_priceTable, array( - 'product_super_attribute_id' => $attribute->getId(), - 'value_index' => $priceObject->getValueIndex(), - 'is_percent' => $priceObject->getIsPercent(), - 'pricing_value' => $priceObject->getPricingValue() - )); } + else { + $websiteId = Mage::app()->getStore($attribute->getStoreId())->getWebsiteId(); + foreach ($oldValues as $row) { + if (!isset($valueIndexes[$row['value_index']])) { + $delete[] = $row['value_id']; + continue; + } + } + foreach ($newValues as $value) { + $valueObject = new Varien_Object($value); + $key = join('-', array($websiteId, $value['value_index'])); + + $pricingValue = $valueObject->getPricingValue(); + if ($pricingValue == '' || is_null($pricingValue)) { + $pricingValue = null; + } + else { + $pricingValue = Mage::app()->getLocale()->getNumber($pricingValue); + } + + // update + if (isset($oldValues[$key])) { + $oldValue = $oldValues[$key]; + + if ($websiteId && $valueObject->getUseDefaultValue()) { + $delete[] = $oldValue['value_id']; + } + else { + $update[$oldValue['value_id']] = array( + 'pricing_value' => $pricingValue, + 'is_percent' => intval($valueObject->getIsPercent()) + ); + } + } + // insert + else { + if ($websiteId && $valueObject->getUseDefaultValue()) { + continue; + } + $insert[] = array( + 'product_super_attribute_id' => $attribute->getId(), + 'value_index' => $valueObject->getValueIndex(), + 'is_percent' => intval($valueObject->getIsPercent()), + 'pricing_value' => $pricingValue, + 'website_id' => $websiteId + ); + } + $key = join('-', array(0, $value['value_index'])); + if (!isset($oldValues[$key])) { + $insert[] = array( + 'product_super_attribute_id' => $attribute->getId(), + 'value_index' => $valueObject->getValueIndex(), + 'is_percent' => 0, + 'pricing_value' => null, + 'website_id' => 0 + ); + } + } + } + + if (!empty($delete)) { + $where = $this->_getWriteAdapter()->quoteInto('value_id IN(?)', $delete); + $this->_getWriteAdapter()->delete($this->_priceTable, $where); + } + + if (!empty($update)) { + foreach ($update as $valueId => $valueData) { + $where = $this->_getWriteAdapter()->quoteInto('value_id=?', $valueId); + $this->_getWriteAdapter()->update($this->_priceTable, $valueData, $where); + } + } + + if (!empty($insert)) { + foreach ($insert as $valueData) { + $this->_getWriteAdapter()->insert($this->_priceTable, $valueData); + } + } + return $this; } -} \ No newline at end of file +} diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Type/Configurable/Attribute/Collection.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Type/Configurable/Attribute/Collection.php index e542ca0319..23eed6c67b 100644 --- a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Type/Configurable/Attribute/Collection.php +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Type/Configurable/Attribute/Collection.php @@ -1,240 +1,296 @@ - - */ -class Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Type_Configurable_Attribute_Collection - extends Mage_Core_Model_Mysql4_Collection_Abstract -{ - protected $_labelTable; - protected $_priceTable; - /** - * Product instance - * - * @var Mage_Catalog_Model_Product - */ - protected $_product; - - protected function _construct() - { - $this->_init('catalog/product_type_configurable_attribute'); - $this->_labelTable = $this->getTable('catalog/product_super_attribute_label'); - $this->_priceTable = $this->getTable('catalog/product_super_attribute_pricing'); - } - - protected function _initSelect() - { - parent::_initSelect(); - return $this; - } - - public function setProductFilter($product) - { - $this->_product = $product; - $this->addFieldToFilter('product_id', $product->getId()); - return $this; - } - - public function orderByPosition($dir='asc') - { - $this->getSelect()->order('position '.$dir); - return $this; - } - - public function getStoreId() - { - return (int) $this->_product->getStoreId(); - } - - protected function _afterLoad() - { - parent::_afterLoad(); - Varien_Profiler::start('TTT1:'.__METHOD__); - $this->_addProductAttributes(); - Varien_Profiler::stop('TTT1:'.__METHOD__); - Varien_Profiler::start('TTT2:'.__METHOD__); - $this->_addAssociatedProductFilters(); - Varien_Profiler::stop('TTT2:'.__METHOD__); - Varien_Profiler::start('TTT3:'.__METHOD__); - $this->_loadLabels(); - Varien_Profiler::stop('TTT3:'.__METHOD__); - Varien_Profiler::start('TTT4:'.__METHOD__); - $this->_loadPrices(); - Varien_Profiler::stop('TTT4:'.__METHOD__); - return $this; - } - - /** - * Add product attributes to collection items - * - * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Type_Configurable_Attribute_Collection - */ - protected function _addProductAttributes() - { - foreach ($this->_items as $item) { - $productAttribute = $this->getProduct()->getTypeInstance(true) - ->getAttributeById($item->getAttributeId(), $this->getProduct()); - $item->setProductAttribute($productAttribute); - } - return $this; - } - - public function _addAssociatedProductFilters() - { - $this->getProduct()->getTypeInstance(true) - ->getUsedProducts($this->getColumnValues('attribute_id'), $this->getProduct()); // Filter associated products - return $this; - } - - /** - * Load attribute labels - * - * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Type_Configurable_Attribute_Collection - */ - protected function _loadLabels() - { - if ($this->count()) { - $select = $this->getConnection()->select() - ->from(array('default'=>$this->_labelTable)) - ->joinLeft( - array('store'=>$this->_labelTable), - 'store.product_super_attribute_id=default.product_super_attribute_id AND store.store_id='.$this->getStoreId(), - array( - 'store_lebel'=>'value', - 'label' => new Zend_Db_Expr('IFNULL(store.value, default.value)') - ) - ) - ->where('default.product_super_attribute_id IN (?)', array_keys($this->_items)) - ->where('default.store_id=0'); - foreach ($this->getConnection()->fetchAll($select) as $data) { - $this->getItemById($data['product_super_attribute_id'])->setLabel($data['label']); - } - } - return $this; - } - - /** - * Load attribute prices information - * - * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Type_Configurable_Attribute_Collection - */ - protected function _loadPrices() - { - if ($this->count()) { - /*$select = $this->getConnection()->select() - ->from(array('price'=>$this->_priceTable)) - ->join(array('option'=>$this->getTable('eav/attribute_option')), - 'option.option_id=price.value_index' - ) - ->joinLeft(array('option_label'=>$this->getTable('eav/attribute_option_value')), - 'option_label.option_id=price.value_index AND option_label.store_id=' . $this->getStoreId(), - array('store_label'=>'value') - ) - ->join(array('option_default_label'=>$this->getTable('eav/attribute_option_value')), - 'option_default_label.option_id=price.value_index', - array( - 'default_label'=>'value', - 'label' => new Zend_Db_Expr('IFNULL(option_label.value, option_default_label.value)') - ) - ) - ->where('price.product_super_attribute_id IN (?)', array_keys($this->_items)) - ->where('option_default_label.store_id=0') - ->order('option.sort_order asc'); OLD */ - - - - $select = $this->getConnection()->select() - ->from(array('price'=>$this->_priceTable)) - ->where('price.product_super_attribute_id IN (?)', array_keys($this->_items)) - ->where('price.pricing_value IS NOT NULL'); - - $pricings = $this->getConnection()->fetchAll($select); - - $values = array(); - - - foreach ($this->_items as $item) { - $productAttribute = $item->getProductAttribute(); - if (!($productAttribute instanceof Mage_Eav_Model_Entity_Attribute_Abstract)) - continue; - $options = $productAttribute->getFrontend()->getSelectOptions(); - foreach ($options as $option) { - foreach ($this->getProduct()->getTypeInstance(true)->getUsedProducts(null, $this->getProduct()) as $associatedProduct) { - if (!empty($option['value']) - && $option['value'] == $associatedProduct->getData( - $productAttribute->getAttributeCode())) { - // If option aviable in associated product - if (!isset($values[$item->getId() . ':' . $option['value']])) { - // If option not added, we will add it. - $values[$item->getId() . ':' . $option['value']] = array( - 'product_super_attribute_id' => $item->getId(), - 'value_index' => $option['value'], - 'label' => $option['label'], - 'default_label' => $option['label'], - 'store_label' => $option['label'], - 'is_percent' => 0, - 'pricing_value' => null - ); - } - } - } - } - } - - - foreach ($pricings as $pricing) { - // Addding pricing to options - $valueKey = $pricing['product_super_attribute_id'] . ':' . $pricing['value_index']; - if (isset($values[$valueKey])) { - $values[$valueKey]['pricing_value'] = $pricing['pricing_value']; - $values[$valueKey]['is_percent'] = $pricing['is_percent']; - $values[$valueKey]['value_id'] = $pricing['value_id']; - } - } - - foreach ($values as $data) { - $this->getItemById($data['product_super_attribute_id'])->addPrice($data); - } - } - return $this; - } - - /** - * Retrive product instance - * - * @return Mage_Catalog_Model_Product - */ - public function getProduct() - { - return $this->_product; - } -} \ No newline at end of file + + */ +class Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Type_Configurable_Attribute_Collection + extends Mage_Core_Model_Mysql4_Collection_Abstract +{ + /** + * Configurable attributes label table name + * + * @var string + */ + protected $_labelTable; + + /** + * Configurable attributes price table name + * + * @var string + */ + protected $_priceTable; + + /** + * Product instance + * + * @var Mage_Catalog_Model_Product + */ + protected $_product; + + /** + * Initialize connection and define table names + * + */ + protected function _construct() + { + $this->_init('catalog/product_type_configurable_attribute'); + $this->_labelTable = $this->getTable('catalog/product_super_attribute_label'); + $this->_priceTable = $this->getTable('catalog/product_super_attribute_pricing'); + } + + /** + * Retrieve catalog helper + * + * @return Mage_Catalog_Helper_Data + */ + public function getHelper() + { + return Mage::helper('catalog'); + } + + /** + * Set Product filter (Configurable) + * + * @param Mage_Catalog_Model_Product $product + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Type_Configurable_Attribute_Collection + */ + public function setProductFilter($product) + { + $this->_product = $product; + $this->addFieldToFilter('product_id', $product->getId()); + return $this; + } + + /** + * Set order collection by Position + * + * @param string $dir + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Type_Configurable_Attribute_Collection + */ + public function orderByPosition($dir='asc') + { + $this->getSelect()->order('position '.$dir); + return $this; + } + + /** + * Retrieve Store Id + * + * @return int + */ + public function getStoreId() + { + return (int)$this->_product->getStoreId(); + } + + /** + * After load collection process + * + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Type_Configurable_Attribute_Collection + */ + protected function _afterLoad() + { + parent::_afterLoad(); + Varien_Profiler::start('TTT1:'.__METHOD__); + $this->_addProductAttributes(); + Varien_Profiler::stop('TTT1:'.__METHOD__); + Varien_Profiler::start('TTT2:'.__METHOD__); + $this->_addAssociatedProductFilters(); + Varien_Profiler::stop('TTT2:'.__METHOD__); + Varien_Profiler::start('TTT3:'.__METHOD__); + $this->_loadLabels(); + Varien_Profiler::stop('TTT3:'.__METHOD__); + Varien_Profiler::start('TTT4:'.__METHOD__); + $this->_loadPrices(); + Varien_Profiler::stop('TTT4:'.__METHOD__); + return $this; + } + + /** + * Add product attributes to collection items + * + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Type_Configurable_Attribute_Collection + */ + protected function _addProductAttributes() + { + foreach ($this->_items as $item) { + $productAttribute = $this->getProduct()->getTypeInstance(true) + ->getAttributeById($item->getAttributeId(), $this->getProduct()); + $item->setProductAttribute($productAttribute); + } + return $this; + } + + /** + * Add Associated Product Filters (From Product Type Instance) + * + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Type_Configurable_Attribute_Collection + */ + public function _addAssociatedProductFilters() + { + $this->getProduct()->getTypeInstance(true) + ->getUsedProducts($this->getColumnValues('attribute_id'), $this->getProduct()); // Filter associated products + return $this; + } + + /** + * Load attribute labels + * + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Type_Configurable_Attribute_Collection + */ + protected function _loadLabels() + { + if ($this->count()) { + $select = $this->getConnection()->select() + ->from(array('default'=>$this->_labelTable)) + ->joinLeft( + array('store'=>$this->_labelTable), + 'store.product_super_attribute_id=default.product_super_attribute_id AND store.store_id='.$this->getStoreId(), + array( + 'store_lebel'=>'value', + 'label' => new Zend_Db_Expr('IFNULL(store.value, default.value)') + ) + ) + ->where('default.product_super_attribute_id IN (?)', array_keys($this->_items)) + ->where('default.store_id=0'); + foreach ($this->getConnection()->fetchAll($select) as $data) { + $this->getItemById($data['product_super_attribute_id'])->setLabel($data['label']); + } + } + return $this; + } + + /** + * Load attribute prices information + * + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Type_Configurable_Attribute_Collection + */ + protected function _loadPrices() + { + if ($this->count()) { + $pricings = array( + 0 => array() + ); + if ($this->getHelper()->isPriceGlobal()) { + $websiteId = 0; + } + else { + $websiteId = (int)Mage::app()->getStore($this->getStoreId())->getWebsiteId(); + $pricing[$websiteId] = array(); + } + $select = $this->getConnection()->select() + ->from(array('price' => $this->_priceTable)) + ->where('price.product_super_attribute_id IN (?)', array_keys($this->_items)); + if ($websiteId > 0) { + $select->where('price.website_id IN(?)', array(0, $websiteId)); + } + else { + $select->where('price.website_id=0'); + } + $query = $this->getConnection()->query($select); + + while ($row = $query->fetch()) { + $pricings[(int)$row['website_id']][] = $row; + } + + $values = array(); + + foreach ($this->_items as $item) { + $productAttribute = $item->getProductAttribute(); + if (!($productAttribute instanceof Mage_Eav_Model_Entity_Attribute_Abstract)) { + continue; + } + $options = $productAttribute->getFrontend()->getSelectOptions(); + foreach ($options as $option) { + foreach ($this->getProduct()->getTypeInstance(true)->getUsedProducts(null, $this->getProduct()) as $associatedProduct) { + if (!empty($option['value']) + && $option['value'] == $associatedProduct->getData( + $productAttribute->getAttributeCode())) { + // If option aviable in associated product + if (!isset($values[$item->getId() . ':' . $option['value']])) { + // If option not added, we will add it. + $values[$item->getId() . ':' . $option['value']] = array( + 'product_super_attribute_id' => $item->getId(), + 'value_index' => $option['value'], + 'label' => $option['label'], + 'default_label' => $option['label'], + 'store_label' => $option['label'], + 'is_percent' => 0, + 'pricing_value' => null, + 'use_default_value' => true + ); + } + } + } + } + } + + foreach ($pricings[0] as $pricing) { + // Addding pricing to options + $valueKey = $pricing['product_super_attribute_id'] . ':' . $pricing['value_index']; + if (isset($values[$valueKey])) { + $values[$valueKey]['pricing_value'] = $pricing['pricing_value']; + $values[$valueKey]['is_percent'] = $pricing['is_percent']; + $values[$valueKey]['value_id'] = $pricing['value_id']; + $values[$valueKey]['use_default_value'] = true; + } + } + + if ($websiteId && isset($pricings[$websiteId])) { + foreach ($pricings[$websiteId] as $pricing) { + $valueKey = $pricing['product_super_attribute_id'] . ':' . $pricing['value_index']; + if (isset($values[$valueKey])) { + $values[$valueKey]['pricing_value'] = $pricing['pricing_value']; + $values[$valueKey]['is_percent'] = $pricing['is_percent']; + $values[$valueKey]['value_id'] = $pricing['value_id']; + $values[$valueKey]['use_default_value'] = false; + } + } + } + + foreach ($values as $data) { + $this->getItemById($data['product_super_attribute_id'])->addPrice($data); + } + } + return $this; + } + + /** + * Retrive product instance + * + * @return Mage_Catalog_Model_Product + */ + public function getProduct() + { + return $this->_product; + } +} diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Setup.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Setup.php index fff7ce30eb..440aff8680 100644 --- a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Setup.php +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Setup.php @@ -1245,6 +1245,26 @@ public function getDefaultEntities() 'unique' => false, 'group' => 'Design', ), + 'page_layout' => array( + 'type' => 'varchar', + 'backend' => '', + 'frontend' => '', + 'label' => 'Page Layout', + 'input' => 'select', + 'class' => '', + 'source' => 'catalog/product_attribute_source_layout', + 'global' => Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_STORE, + 'visible' => true, + 'required' => false, + 'user_defined' => false, + 'default' => '', + 'searchable' => false, + 'filterable' => false, + 'comparable' => false, + 'visible_on_front' => false, + 'unique' => false, + 'group' => 'Design' + ), 'category_ids' => array( 'type' => 'static', 'backend' => '', diff --git a/app/code/core/Mage/Catalog/controllers/CategoryController.php b/app/code/core/Mage/Catalog/controllers/CategoryController.php index bd77eaf3d9..ee83b1872a 100644 --- a/app/code/core/Mage/Catalog/controllers/CategoryController.php +++ b/app/code/core/Mage/Catalog/controllers/CategoryController.php @@ -102,7 +102,7 @@ public function viewAction() $this->_initLayoutMessages('checkout/session'); $this->renderLayout(); } - else { + elseif (!$this->getResponse()->isRedirect()) { $this->_forward('noRoute'); } } diff --git a/app/code/core/Mage/Catalog/controllers/ProductController.php b/app/code/core/Mage/Catalog/controllers/ProductController.php index 2ade6484d5..6cfc8bda76 100644 --- a/app/code/core/Mage/Catalog/controllers/ProductController.php +++ b/app/code/core/Mage/Catalog/controllers/ProductController.php @@ -142,9 +142,9 @@ public function viewAction() $this->renderLayout(); } else { - if (isset($_GET['store']) && $this->getRequest()->isDispatched()) { + if (isset($_GET['store']) && !$this->getResponse()->isRedirect()) { $this->_redirect(''); - } elseif ($this->getRequest()->isDispatched()) { + } elseif (!$this->getResponse()->isRedirect()) { $this->_forward('noRoute'); } } @@ -156,9 +156,9 @@ public function viewAction() public function galleryAction() { if (!$this->_initProduct()) { - if (isset($_GET['store']) && $this->getRequest()->isDispatched()) { + if (isset($_GET['store']) && !$this->getResponse()->isRedirect()) { $this->_redirect(''); - } elseif ($this->getRequest()->isDispatched()) { + } elseif (!$this->getResponse()->isRedirect()) { $this->_forward('noRoute'); } return; diff --git a/app/code/core/Mage/Catalog/etc/config.xml b/app/code/core/Mage/Catalog/etc/config.xml index 1fb67964fd..665079d7bf 100644 --- a/app/code/core/Mage/Catalog/etc/config.xml +++ b/app/code/core/Mage/Catalog/etc/config.xml @@ -28,7 +28,7 @@ - 0.7.63 + 0.7.66 @@ -346,6 +346,15 @@ + + + + singleton + catalog/product_flat_observer + catalogCategoryChangeProducts + + + @@ -466,6 +475,8 @@ frontend/product/collection/attributes + 0 + 0 @@ -634,9 +645,7 @@ - - @@ -644,6 +653,8 @@ + + diff --git a/app/code/core/Mage/Catalog/etc/wsdl.xml b/app/code/core/Mage/Catalog/etc/wsdl.xml index d03e3c24ba..c9507c3243 100644 --- a/app/code/core/Mage/Catalog/etc/wsdl.xml +++ b/app/code/core/Mage/Catalog/etc/wsdl.xml @@ -372,7 +372,7 @@ - + diff --git a/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-0.7.63-0.7.64.php b/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-0.7.63-0.7.64.php new file mode 100644 index 0000000000..123777c58b --- /dev/null +++ b/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-0.7.63-0.7.64.php @@ -0,0 +1,53 @@ +startSetup(); + +$installer->addAttribute('catalog_product', 'page_layout', array( + 'type' => 'varchar', + 'backend' => '', + 'frontend' => '', + 'label' => 'Page Layout', + 'input' => 'select', + 'class' => '', + 'source' => 'catalog/product_attribute_source_layout', + 'global' => Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_STORE, + 'visible' => true, + 'required' => false, + 'user_defined' => false, + 'default' => '', + 'searchable' => false, + 'filterable' => false, + 'comparable' => false, + 'visible_on_front' => false, + 'unique' => false, + 'group' => 'Design' +)); + +$installer->endSetup(); diff --git a/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-0.7.64-0.7.65.php b/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-0.7.64-0.7.65.php new file mode 100644 index 0000000000..e9a273671d --- /dev/null +++ b/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-0.7.64-0.7.65.php @@ -0,0 +1,40 @@ +startSetup(); +$connection = $installer->getConnection(); +/* @var $connection Varien_Db_Adapter_Pdo_Mysql */ +$connection->addColumn($installer->getTable('catalog/product_super_attribute_pricing'), 'website_id', 'smallint(5) UNSIGNED NOT NULL DEFAULT 0'); +$connection->addConstraint('FK_CATALOG_PRODUCT_SUPER_PRICE_WEBSITE', + $installer->getTable('catalog/product_super_attribute_pricing'), 'website_id', + $installer->getTable('core/website'), 'website_id', + 'cascade', 'cascade' +); +$installer->endSetup(); diff --git a/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-0.7.65-0.7.66.php b/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-0.7.65-0.7.66.php new file mode 100644 index 0000000000..ecc5b108e8 --- /dev/null +++ b/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-0.7.65-0.7.66.php @@ -0,0 +1,41 @@ +loadSelf() + ->setIsBuild(false) + ->save(); + +$installer->startSetup(); +$installer->run(" + UPDATE `{$installer->getTable('core/config_data')}` SET `value`=0 + WHERE `path` LIKE '".Mage_Catalog_Helper_Product_Flat::XML_PATH_USE_PRODUCT_FLAT."'; +"); +$installer->endSetup(); \ No newline at end of file diff --git a/app/code/core/Mage/CatalogIndex/Model/Mysql4/Attribute.php b/app/code/core/Mage/CatalogIndex/Model/Mysql4/Attribute.php index 41f882e282..1f287a8e2c 100644 --- a/app/code/core/Mage/CatalogIndex/Model/Mysql4/Attribute.php +++ b/app/code/core/Mage/CatalogIndex/Model/Mysql4/Attribute.php @@ -81,10 +81,13 @@ public function getCount($attribute, $entitySelect) public function applyFilterToCollection($collection, $attribute, $value) { - if ($collection->isEnabledFlat()) { - $collection->getSelect()->where("e.{$attribute->getAttributeCode()}=?", $value); - return $this; - } + /** + * Will be used after SQL review + */ +// if ($collection->isEnabledFlat()) { +// $collection->getSelect()->where("e.{$attribute->getAttributeCode()}=?", $value); +// return $this; +// } $alias = 'attr_index_'.$attribute->getId(); $collection->getSelect()->join( diff --git a/app/code/core/Mage/CatalogIndex/Model/Mysql4/Indexer.php b/app/code/core/Mage/CatalogIndex/Model/Mysql4/Indexer.php index 8a0484cc5d..ee87801262 100644 --- a/app/code/core/Mage/CatalogIndex/Model/Mysql4/Indexer.php +++ b/app/code/core/Mage/CatalogIndex/Model/Mysql4/Indexer.php @@ -605,6 +605,7 @@ public function updateCatalogProductFlat($storeId, $productIds = null, $tableNam if (is_null($tableName)) { $tableName = $this->getTable('catalog/product_flat') . '_' . $storeId; } + $addChildData = Mage::helper('catalog/product_flat')->isAddChildData(); $priceAttribute = Mage::getSingleton('eav/entity_attribute') ->getIdByCode('catalog_product', 'price'); @@ -623,11 +624,13 @@ public function updateCatalogProductFlat($storeId, $productIds = null, $tableNam . " AND `p`.`attribute_id`={$priceAttribute}" . " AND `p`.`customer_group_id`={$group->getId()}" . " AND `p`.`website_id`={$websiteId}", - array($columnName => 'value')) - ->where('e.is_child=?', 0); + array($columnName => 'value')); + if ($addChildData) { + $select->where('e.is_child=?', 0); + } if ($productIds instanceof Mage_Catalog_Model_Product_Condition_Interface) { - $select->where('e.entity_id IN ('.$productIds->getIdsSelect($this->_getWriteAdapter())->__toString().')'); + $select->where('e.entity_id IN ('.$productIds->getIdsSelect($this->_getWriteAdapter())->__toString().')'); } elseif (!is_null($productIds)) { $select->where("e.entity_id IN(?)", $productIds); } @@ -635,28 +638,30 @@ public function updateCatalogProductFlat($storeId, $productIds = null, $tableNam $sql = $select->crossUpdateFromSelect(array('e' => $tableName)); $this->_getWriteAdapter()->query($sql); - /** - * Update prices for children products in flat table - */ - $select = $this->_getWriteAdapter()->select() - ->join( - array('p' => $this->getTable('catalogindex/price')), - "`e`.`child_id`=`p`.`entity_id`" - . " AND `p`.`attribute_id`={$priceAttribute}" - . " AND `p`.`customer_group_id`={$group->getId()}" - . " AND `p`.`website_id`={$websiteId}", - array($columnName => 'value')) - ->where('e.is_child=?', 1); + if ($addChildData) { + /** + * Update prices for children products in flat table + */ + $select = $this->_getWriteAdapter()->select() + ->join( + array('p' => $this->getTable('catalogindex/price')), + "`e`.`child_id`=`p`.`entity_id`" + . " AND `p`.`attribute_id`={$priceAttribute}" + . " AND `p`.`customer_group_id`={$group->getId()}" + . " AND `p`.`website_id`={$websiteId}", + array($columnName => 'value')) + ->where('e.is_child=?', 1); + + if ($productIds instanceof Mage_Catalog_Model_Product_Condition_Interface) { + $select->where('e.child_id IN ('.$productIds->getIdsSelect($this->_getWriteAdapter())->__toString().')'); + } elseif (!is_null($productIds)) { + $select->where("e.child_id IN(?)", $productIds); + } - if ($productIds instanceof Mage_Catalog_Model_Product_Condition_Interface) { - $select->where('e.child_id IN ('.$productIds->getIdsSelect($this->_getWriteAdapter())->__toString().')'); - } elseif (!is_null($productIds)) { - $select->where("e.child_id IN(?)", $productIds); + $sql = $select->crossUpdateFromSelect(array('e' => $tableName)); + $this->_getWriteAdapter()->query($sql); } - $sql = $select->crossUpdateFromSelect(array('e' => $tableName)); - $this->_getWriteAdapter()->query($sql); - } return $this; diff --git a/app/code/core/Mage/CatalogIndex/Model/Mysql4/Price.php b/app/code/core/Mage/CatalogIndex/Model/Mysql4/Price.php index 5b7232e783..d500a76c8f 100644 --- a/app/code/core/Mage/CatalogIndex/Model/Mysql4/Price.php +++ b/app/code/core/Mage/CatalogIndex/Model/Mysql4/Price.php @@ -72,21 +72,21 @@ public function getMaxValue($attribute = null, $entitySelect) $select->reset(Zend_Db_Select::LIMIT_COUNT); $select->reset(Zend_Db_Select::LIMIT_OFFSET); - if ($attribute->getAttributeCode() == 'price') { - $select->where('price_table.customer_group_id = ?', $this->getCustomerGroupId()); - } + $response = new Varien_Object(); + $response->setAdditionalCalculations(array()); $select->join(array('price_table'=>$this->getMainTable()), 'price_table.entity_id=e.entity_id', array()); - $response = new Varien_Object(); - $response->setAdditionalCalculations(array()); - $args = array( - 'select'=>$select, - 'table'=>'price_table', - 'store_id'=>$this->getStoreId(), - 'response_object'=>$response, - ); - Mage::dispatchEvent('catalogindex_prepare_price_select', $args); + if ($attribute->getAttributeCode() == 'price') { + $select->where('price_table.customer_group_id = ?', $this->getCustomerGroupId()); + $args = array( + 'select'=>$select, + 'table'=>'price_table', + 'store_id'=>$this->getStoreId(), + 'response_object'=>$response, + ); + Mage::dispatchEvent('catalogindex_prepare_price_select', $args); + } $select ->from('', "MAX(price_table.value".implode('', $response->getAdditionalCalculations()).")") @@ -105,20 +105,20 @@ public function getCount($range, $attribute, $entitySelect) $select->reset(Zend_Db_Select::LIMIT_OFFSET); $select->join(array('price_table'=>$this->getMainTable()), 'price_table.entity_id=e.entity_id', array()); + $response = new Varien_Object(); + $response->setAdditionalCalculations(array()); if ($attribute->getAttributeCode() == 'price') { $select->where('price_table.customer_group_id = ?', $this->getCustomerGroupId()); + $args = array( + 'select'=>$select, + 'table'=>'price_table', + 'store_id'=>$this->getStoreId(), + 'response_object'=>$response, + ); + Mage::dispatchEvent('catalogindex_prepare_price_select', $args); } - $response = new Varien_Object(); - $response->setAdditionalCalculations(array()); - $args = array( - 'select'=>$select, - 'table'=>'price_table', - 'store_id'=>$this->getStoreId(), - 'response_object'=>$response, - ); - Mage::dispatchEvent('catalogindex_prepare_price_select', $args); $fields = array('count'=>'COUNT(DISTINCT price_table.entity_id)', 'range'=>"FLOOR(((price_table.value".implode('', $response->getAdditionalCalculations()).")*{$this->getRate()})/{$range})+1"); @@ -127,6 +127,7 @@ public function getCount($range, $attribute, $entitySelect) ->where('price_table.website_id = ?', $this->getWebsiteId()) ->where('price_table.attribute_id = ?', $attribute->getId()); + $result = $this->_getReadAdapter()->fetchAll($select); $counts = array(); @@ -144,13 +145,6 @@ public function getFilteredEntities($range, $index, $attribute, $entityIdsFilter $response = new Varien_Object(); $response->setAdditionalCalculations(array()); - $args = array( - 'select'=>$select, - 'table'=>$tableName, - 'store_id'=>$this->getStoreId(), - 'response_object'=>$response, - ); - Mage::dispatchEvent('catalogindex_prepare_price_select', $args); $select ->distinct(true) @@ -160,14 +154,61 @@ public function getFilteredEntities($range, $index, $attribute, $entityIdsFilter if ($attribute->getAttributeCode() == 'price') { $select->where($tableName . '.customer_group_id = ?', $this->getCustomerGroupId()); + $args = array( + 'select'=>$select, + 'table'=>$tableName, + 'store_id'=>$this->getStoreId(), + 'response_object'=>$response, + ); + Mage::dispatchEvent('catalogindex_prepare_price_select', $args); } $select->where("(({$tableName}.value".implode('', $response->getAdditionalCalculations()).")*{$this->getRate()}) >= ?", ($index-1)*$range); $select->where("(({$tableName}.value".implode('', $response->getAdditionalCalculations()).")*{$this->getRate()}) < ?", $index*$range); + return $this->_getReadAdapter()->fetchCol($select); } + public function applyFilterToCollection($collection, $attribute, $range, $index, $tableName = 'price_table') + { + /** + * Distinct required for removing duplicates in case when we have grouped products + * which contain multiple rows for one product id + */ + $collection->getSelect()->distinct(true); + $tableName = $tableName.'_'.$attribute->getAttributeCode(); + $collection->getSelect()->joinLeft( + array($tableName => $this->getMainTable()), + $tableName .'.entity_id=e.entity_id', + array() + ); + + $response = new Varien_Object(); + $response->setAdditionalCalculations(array()); + + $collection->getSelect() + ->where($tableName . '.website_id = ?', $this->getWebsiteId()) + ->where($tableName . '.attribute_id = ?', $attribute->getId()); + + if ($attribute->getAttributeCode() == 'price') { + $collection->getSelect()->where($tableName . '.customer_group_id = ?', $this->getCustomerGroupId()); + $args = array( + 'select'=>$collection->getSelect(), + 'table'=>$tableName, + 'store_id'=>$this->getStoreId(), + 'response_object'=>$response, + ); + + Mage::dispatchEvent('catalogindex_prepare_price_select', $args); + } + + $collection->getSelect()->where("(({$tableName}.value".implode('', $response->getAdditionalCalculations()).")*{$this->getRate()}) >= ?", ($index-1)*$range); + $collection->getSelect()->where("(({$tableName}.value".implode('', $response->getAdditionalCalculations()).")*{$this->getRate()}) < ?", $index*$range); + + return $this; + } + public function getMinimalPrices($ids) { if (!$ids) { diff --git a/app/code/core/Mage/CatalogIndex/Model/Price.php b/app/code/core/Mage/CatalogIndex/Model/Price.php index b986be1cad..bd116e82a0 100644 --- a/app/code/core/Mage/CatalogIndex/Model/Price.php +++ b/app/code/core/Mage/CatalogIndex/Model/Price.php @@ -55,6 +55,11 @@ public function getFilteredEntities($attribute, $range, $index, $entityIdsFilter return $this->_getResource()->getFilteredEntities($range, $index, $attribute, $entityIdsFilter); } + public function applyFilterToCollection($collection, $attribute, $range, $index) + { + return $this->_getResource()->applyFilterToCollection($collection, $attribute, $range, $index); + } + public function addMinimalPrices(Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Collection $collection) { $minimalPrices = $this->_getResource()->getMinimalPrices($collection->getLoadedIds()); diff --git a/app/code/core/Mage/CatalogRule/Model/Mysql4/Rule.php b/app/code/core/Mage/CatalogRule/Model/Mysql4/Rule.php index 9aae021fba..6824a56e72 100644 --- a/app/code/core/Mage/CatalogRule/Model/Mysql4/Rule.php +++ b/app/code/core/Mage/CatalogRule/Model/Mysql4/Rule.php @@ -188,6 +188,15 @@ public function removeCatalogPricesForDateRange($fromDate, $toDate, $productId=n $conds[] = $write->quoteInto('product_id=?', $productId); } + /** + * Add information about affected products + * It can be used in processes which related with product price (like catalog index) + */ + $select = $this->_getWriteAdapter()->select() + ->from($this->getTable('catalogrule/rule_product_price'), 'product_id') + ->where(implode(' AND ', $conds)); + $insertQuery = 'REPLACE INTO ' . $this->getTable('catalogrule/affected_product') . ' (product_id)' . $select->__toString(); + $this->_getWriteAdapter()->query($insertQuery); $write->delete($this->getTable('catalogrule/rule_product_price'), $conds); return $this; } diff --git a/app/code/core/Mage/CatalogRule/etc/config.xml b/app/code/core/Mage/CatalogRule/etc/config.xml index adca8d3265..2032adc7d4 100644 --- a/app/code/core/Mage/CatalogRule/etc/config.xml +++ b/app/code/core/Mage/CatalogRule/etc/config.xml @@ -28,7 +28,7 @@ - 0.7.6 + 0.7.7 diff --git a/app/code/core/Mage/CatalogRule/sql/catalogrule_setup/mysql4-upgrade-0.7.6-0.7.7.php b/app/code/core/Mage/CatalogRule/sql/catalogrule_setup/mysql4-upgrade-0.7.6-0.7.7.php new file mode 100644 index 0000000000..a7b41bdd71 --- /dev/null +++ b/app/code/core/Mage/CatalogRule/sql/catalogrule_setup/mysql4-upgrade-0.7.6-0.7.7.php @@ -0,0 +1,44 @@ +startSetup(); + +$installer->run(" +DROP TABLE IF EXISTS `{$this->getTable('catalogrule_affected_product')}`; +"); +$installer->run(" +CREATE TABLE `{$this->getTable('catalogrule_affected_product')}` ( + `product_id` int(10) unsigned NOT NULL, + PRIMARY KEY (`product_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +"); + +$installer->endSetup(); + diff --git a/app/code/core/Mage/Checkout/Block/Cart/Item/Renderer.php b/app/code/core/Mage/Checkout/Block/Cart/Item/Renderer.php index fa7298bace..0743423c7c 100644 --- a/app/code/core/Mage/Checkout/Block/Cart/Item/Renderer.php +++ b/app/code/core/Mage/Checkout/Block/Cart/Item/Renderer.php @@ -138,7 +138,11 @@ public function getProductOptions() $options[] = array( 'label' => $option->getTitle(), - 'value' => $group->getFormattedOptionValue($quoteItemOption->getValue()) + 'value' => $group->getFormattedOptionValue($quoteItemOption->getValue()), + 'print_value' => $group->getPrintableOptionValue($quoteItemOption->getValue()), + 'option_id' => $option->getId(), + 'option_type' => $option->getType(), + 'custom_view' => $group->isCustomizedView() ); } } @@ -223,13 +227,58 @@ public function getMessages() return array_unique($messages); } + /** + * Accept option value and return its formatted view + * + * @param mixed $optionValue + * Method works well with these $optionValue format: + * 1. String + * 2. Indexed array e.g. array(val1, val2, ...) + * 3. Associative array, containing additional option info, including option value, e.g. + * array + * ( + * [label] => ..., + * [value] => ..., + * [print_value] => ..., + * [option_id] => ..., + * [option_type] => ..., + * [custom_view] =>..., + * ) + * + * @return array + */ public function getFormatedOptionValue($optionValue) { - if (Mage::helper('catalog/product_options')->isHtmlFormattedOptionValue($optionValue)) { - return array('value' => $optionValue); + $optionInfo = array(); + + // define input data format + if (is_array($optionValue)) { + if (isset($optionValue['option_id'])) { + $optionInfo = $optionValue; + if (isset($optionInfo['value'])) { + $optionValue = $optionInfo['value']; + } + } elseif (isset($optionValue['value'])) { + $optionValue = $optionValue['value']; + } } - $formateOptionValue = array(); + // render customized option view + if (isset($optionInfo['custom_view']) && $optionInfo['custom_view']) { + $_default = array('value' => $optionValue); + if (isset($optionInfo['option_type'])) { + try { + $group = Mage::getModel('catalog/product_option')->groupFactory($optionInfo['option_type']); + return array('value' => $group->getCustomizedView($optionInfo)); + } catch (Exception $e) { + return $_default; + } + } + return $_default; + } + + // truncate standard view + $result = array(); if (is_array($optionValue)) { $_truncatedValue = implode("\n", $optionValue); $_truncatedValue = nl2br($_truncatedValue); @@ -239,16 +288,14 @@ public function getFormatedOptionValue($optionValue) $_truncatedValue = nl2br($_truncatedValue); } - $formateOptionValue = array( - 'value' => $_truncatedValue - ); + $result = array('value' => $_truncatedValue); if (Mage::helper('core/string')->strlen($optionValue) > 55) { - $formateOptionValue['value'] = $formateOptionValue['value'] . ' ...'; + $result['value'] = $result['value'] . ' ...'; $optionValue = nl2br($optionValue); - $formateOptionValue = array_merge($formateOptionValue, array('full_view' => $optionValue)); + $result = array_merge($result, array('full_view' => $optionValue)); } - return $formateOptionValue; + return $result; } } \ No newline at end of file diff --git a/app/code/core/Mage/Checkout/Model/Type/Onepage.php b/app/code/core/Mage/Checkout/Model/Type/Onepage.php index 538da1286e..f82d5e13d2 100644 --- a/app/code/core/Mage/Checkout/Model/Type/Onepage.php +++ b/app/code/core/Mage/Checkout/Model/Type/Onepage.php @@ -136,6 +136,11 @@ public function saveBilling($data, $customerAddressId) if (!empty($customerAddressId)) { $customerAddress = Mage::getModel('customer/address')->load($customerAddressId); if ($customerAddress->getId()) { + if ($customerAddress->getCustomerId() != $this->getQuote()->getCustomerId()) { + return array('error' => 1, + 'message' => Mage::helper('checkout')->__('Customer Address is not valid.') + ); + } $address->importCustomerAddress($customerAddress); } } else { @@ -152,7 +157,7 @@ public function saveBilling($data, $customerAddressId) return $res; } - if (!$this->getQuote()->getCustomerId() && 'register' == $this->getQuote()->getCheckoutMethod()) { + if (!$this->getQuote()->getCustomerId() && Mage_Sales_Model_Quote::CHECKOUT_METHOD_REGISTER == $this->getQuote()->getCheckoutMethod()) { if ($this->_customerEmailExists($address->getEmail(), Mage::app()->getWebsite()->getId())) { return array('error' => 1, 'message' => Mage::helper('checkout')->__('There is already a customer registered using this email address') @@ -224,7 +229,7 @@ protected function _processValidateCustomer(Mage_Sales_Model_Quote_Address $addr } // invoke customer model, if it is registering - if ('register' == $this->getQuote()->getCheckoutMethod()) { + if (Mage_Sales_Model_Quote::CHECKOUT_METHOD_REGISTER == $this->getQuote()->getCheckoutMethod()) { // set customer password hash for further usage $customer = Mage::getModel('customer/customer'); $this->getQuote()->setPasswordHash($customer->encryptPassword($address->getCustomerPassword())); @@ -250,7 +255,7 @@ protected function _processValidateCustomer(Mage_Sales_Model_Quote_Address $addr 'message' => implode(', ', $validationResult) ); } - } elseif('guest' == $this->getQuote()->getCheckoutMethod()) { + } elseif(Mage_Sales_Model_Quote::CHECKOUT_METHOD_GUEST == $this->getQuote()->getCheckoutMethod()) { $email = $address->getData('email'); if (!Zend_Validate::is($email, 'EmailAddress')) { return array( @@ -277,6 +282,11 @@ public function saveShipping($data, $customerAddressId) if (!empty($customerAddressId)) { $customerAddress = Mage::getModel('customer/address')->load($customerAddressId); if ($customerAddress->getId()) { + if ($customerAddress->getCustomerId() != $this->getQuote()->getCustomerId()) { + return array('error' => 1, + 'message' => Mage::helper('checkout')->__('Customer Address is not valid.') + ); + } $address->importCustomerAddress($customerAddress); } } else { @@ -396,7 +406,7 @@ public function saveOrder() $shipping = $this->getQuote()->getShippingAddress(); } switch ($this->getQuote()->getCheckoutMethod()) { - case 'guest': + case Mage_Sales_Model_Quote::CHECKOUT_METHOD_GUEST: if (!$this->getQuote()->isAllowedGuestCheckout()) { Mage::throwException(Mage::helper('checkout')->__('Sorry, guest checkout is not enabled. Please try again or contact store owner.')); } @@ -405,7 +415,7 @@ public function saveOrder() ->setCustomerGroupId(Mage_Customer_Model_Group::NOT_LOGGED_IN_ID); break; - case 'register': + case Mage_Sales_Model_Quote::CHECKOUT_METHOD_REGISTER: $customer = Mage::getModel('customer/customer'); /* @var $customer Mage_Customer_Model_Customer */ @@ -501,14 +511,14 @@ public function saveOrder() */ Mage::dispatchEvent('checkout_type_onepage_save_order', array('order'=>$order, 'quote'=>$this->getQuote())); // check again, if customer exists - if ($this->getQuote()->getCheckoutMethod() == 'register') { + if ($this->getQuote()->getCheckoutMethod() == Mage_Sales_Model_Quote::CHECKOUT_METHOD_REGISTER) { if ($this->_customerEmailExists($customer->getEmail(), Mage::app()->getWebsite()->getId())) { Mage::throwException(Mage::helper('checkout')->__('There is already a customer registered using this email address')); } } $order->place(); - if ($this->getQuote()->getCheckoutMethod()=='register') { + if ($this->getQuote()->getCheckoutMethod()==Mage_Sales_Model_Quote::CHECKOUT_METHOD_REGISTER) { $customer->save(); $customerBillingId = $customerBilling->getId(); if (!$this->getQuote()->isVirtual()) { @@ -567,7 +577,7 @@ public function saveOrder() $order->sendNewOrderEmail(); } - if ($this->getQuote()->getCheckoutMethod()=='register') { + if ($this->getQuote()->getCheckoutMethod()==Mage_Sales_Model_Quote::CHECKOUT_METHOD_REGISTER) { /** * we need to save quote here to have it saved with Customer Id. * so when loginById() executes checkout/session method loadCustomerQuote diff --git a/app/code/core/Mage/Cms/sql/cms_setup/mysql4-install-0.7.0.php b/app/code/core/Mage/Cms/sql/cms_setup/mysql4-install-0.7.0.php index 2f371e0d85..e7fa0926fe 100644 --- a/app/code/core/Mage/Cms/sql/cms_setup/mysql4-install-0.7.0.php +++ b/app/code/core/Mage/Cms/sql/cms_setup/mysql4-install-0.7.0.php @@ -65,7 +65,7 @@ UNIQUE KEY `identifier` (`identifier`,`store_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='CMS pages'; -insert into {$this->getTable('cms_page')}(`page_id`,`title`,`root_template`,`meta_keywords`,`meta_description`,`identifier`,`content`,`creation_time`,`update_time`,`is_active`,`store_id`,`sort_order`) values (1,'404 Not Found 1','right_column','Page keywords','Page description','no-route','

Whoops, our bad...

\r\n
\r\n
The page you requested was not found, and we have a fine guess why.
\r\n
\r\n
    \r\n
  • If you typed the URL directly, please make sure the spelling is correct.
  • \r\n
  • If you clicked on a link to get here, the link is outdated.
  • \r\n
\r\n
\r\n
\r\n
\r\n
What can you do?
\r\n
Have no fear, help is near! There are many ways you can get back on track with Magento Demo Store.
\r\n
\r\n
    \r\n
  • Go back to the previous page.
  • \r\n
  • Use the search bar at the top of the page to search for your products.
  • \r\n
  • Follow these links to get you back on track!
    Store Home
    My Account

\r\n

','2007-06-20 18:38:32','2007-08-26 19:11:13',1,0,0),(2,'Home page','right_column','','','home','

Home Page

\r\n','2007-08-23 10:03:25','2007-09-06 13:26:53',1,0,0),(3,'About Us','one_column','','','about-magento-demo-store','
\r\n

About Magento Demo Store

\r\n
\r\n
\r\n

\"Varien

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede.

\r\n

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta.

\r\n
\r\n

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit.

\r\n

Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper. Phasellus id massa. Pellentesque nisl. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc augue. Aenean sed justo non leo vehicula laoreet. Praesent ipsum libero, auctor ac, tempus nec, tempor nec, justo.

\r\n

Maecenas ullamcorper, odio vel tempus egestas, dui orci faucibus orci, sit amet aliquet lectus dolor et quam. Pellentesque consequat luctus purus. Nunc et risus. Etiam a nibh. Phasellus dignissim metus eget nisi. Vestibulum sapien dolor, aliquet nec, porta ac, malesuada a, libero. Praesent feugiat purus eget est. Nulla facilisi. Vestibulum tincidunt sapien eu velit. Mauris purus. Maecenas eget mauris eu orci accumsan feugiat. Pellentesque eget velit. Nunc tincidunt.

\r\n
\r\n

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta. Suspendisse convallis felis ac enim. Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper

\r\n

Maecenas ullamcorper, odio vel tempus egestas, dui orci faucibus orci, sit amet aliquet lectus dolor et quam. Pellentesque consequat luctus purus.

\r\n

Nunc et risus. Etiam a nibh. Phasellus dignissim metus eget nisi.

\r\n
\r\n

To all of you, from all of us at Magento Demo Store - Thank you and Happy eCommerce!

\r\n

John Doe
Some important guy

\r\n
','2007-08-30 14:01:18','2007-08-30 14:01:18',1,0,0),(4,'Customer Service','three_column','','','customer-service','
\r\n

Customer Service

\r\n
\r\n\r\n
\r\n
Shipping & Delivery
\r\n
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta. Suspendisse convallis felis ac enim. Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper. Phasellus id massa. Pellentesque nisl. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc augue. Aenean sed justo non leo vehicula laoreet. Praesent ipsum libero, auctor ac, tempus nec, tempor nec, justo.
\r\n
Privacy & Security
\r\n
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta. Suspendisse convallis felis ac enim. Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper. Phasellus id massa. Pellentesque nisl. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc augue. Aenean sed justo non leo vehicula laoreet. Praesent ipsum libero, auctor ac, tempus nec, tempor nec, justo.
\r\n
Returns & Replacements
\r\n
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta. Suspendisse convallis felis ac enim. Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper. Phasellus id massa. Pellentesque nisl. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc augue. Aenean sed justo non leo vehicula laoreet. Praesent ipsum libero, auctor ac, tempus nec, tempor nec, justo.
\r\n
Ordering
\r\n
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta. Suspendisse convallis felis ac enim. Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper. Phasellus id massa. Pellentesque nisl. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc augue. Aenean sed justo non leo vehicula laoreet. Praesent ipsum libero, auctor ac, tempus nec, tempor nec, justo.
\r\n
Payment, Pricing & Promotions
\r\n
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta. Suspendisse convallis felis ac enim. Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper. Phasellus id massa. Pellentesque nisl. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc augue. Aenean sed justo non leo vehicula laoreet. Praesent ipsum libero, auctor ac, tempus nec, tempor nec, justo.
\r\n
Viewing Orders
\r\n
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta. Suspendisse convallis felis ac enim. Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper. Phasellus id massa. Pellentesque nisl. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc augue. Aenean sed justo non leo vehicula laoreet. Praesent ipsum libero, auctor ac, tempus nec, tempor nec, justo.
\r\n
Updating Account Information
\r\n
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta. Suspendisse convallis felis ac enim. Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper. Phasellus id massa. Pellentesque nisl. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc augue. Aenean sed justo non leo vehicula laoreet. Praesent ipsum libero, auctor ac, tempus nec, tempor nec, justo.
\r\n
','2007-08-30 14:02:20','2007-08-30 14:03:37',1,0,0); +insert into {$this->getTable('cms_page')}(`page_id`,`title`,`root_template`,`meta_keywords`,`meta_description`,`identifier`,`content`,`creation_time`,`update_time`,`is_active`,`store_id`,`sort_order`) values (1,'404 Not Found 1','right_column','Page keywords','Page description','no-route','

Whoops, our bad...

\r\n
\r\n
The page you requested was not found, and we have a fine guess why.
\r\n
\r\n
    \r\n
  • If you typed the URL directly, please make sure the spelling is correct.
  • \r\n
  • If you clicked on a link to get here, the link is outdated.
  • \r\n
\r\n
\r\n
\r\n
\r\n
What can you do?
\r\n
Have no fear, help is near! There are many ways you can get back on track with Magento Demo Store.
\r\n
\r\n
    \r\n
  • Go back to the previous page.
  • \r\n
  • Use the search bar at the top of the page to search for your products.
  • \r\n
  • Follow these links to get you back on track!
    Store Home
    My Account

\r\n

','2007-06-20 18:38:32','2007-08-26 19:11:13',1,0,0),(2,'Home page','right_column','','','home','

Home Page

\r\n','2007-08-23 10:03:25','2007-09-06 13:26:53',1,0,0),(3,'About Us','one_column','','','about-magento-demo-store','
\r\n

About Magento Demo Store

\r\n
\r\n
\r\n

\"Varien

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede.

\r\n

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta.

\r\n
\r\n

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit.

\r\n

Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper. Phasellus id massa. Pellentesque nisl. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc augue. Aenean sed justo non leo vehicula laoreet. Praesent ipsum libero, auctor ac, tempus nec, tempor nec, justo.

\r\n

Maecenas ullamcorper, odio vel tempus egestas, dui orci faucibus orci, sit amet aliquet lectus dolor et quam. Pellentesque consequat luctus purus. Nunc et risus. Etiam a nibh. Phasellus dignissim metus eget nisi. Vestibulum sapien dolor, aliquet nec, porta ac, malesuada a, libero. Praesent feugiat purus eget est. Nulla facilisi. Vestibulum tincidunt sapien eu velit. Mauris purus. Maecenas eget mauris eu orci accumsan feugiat. Pellentesque eget velit. Nunc tincidunt.

\r\n
\r\n

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta. Suspendisse convallis felis ac enim. Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper

\r\n

Maecenas ullamcorper, odio vel tempus egestas, dui orci faucibus orci, sit amet aliquet lectus dolor et quam. Pellentesque consequat luctus purus.

\r\n

Nunc et risus. Etiam a nibh. Phasellus dignissim metus eget nisi.

\r\n
\r\n

To all of you, from all of us at Magento Demo Store - Thank you and Happy eCommerce!

\r\n

John Doe
Some important guy

\r\n
','2007-08-30 14:01:18','2007-08-30 14:01:18',1,0,0),(4,'Customer Service','three_column','','','customer-service','
\r\n

Customer Service

\r\n
\r\n\r\n
\r\n
Shipping & Delivery
\r\n
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta. Suspendisse convallis felis ac enim. Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper. Phasellus id massa. Pellentesque nisl. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc augue. Aenean sed justo non leo vehicula laoreet. Praesent ipsum libero, auctor ac, tempus nec, tempor nec, justo.
\r\n
Privacy & Security
\r\n
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta. Suspendisse convallis felis ac enim. Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper. Phasellus id massa. Pellentesque nisl. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc augue. Aenean sed justo non leo vehicula laoreet. Praesent ipsum libero, auctor ac, tempus nec, tempor nec, justo.
\r\n
Returns & Replacements
\r\n
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta. Suspendisse convallis felis ac enim. Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper. Phasellus id massa. Pellentesque nisl. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc augue. Aenean sed justo non leo vehicula laoreet. Praesent ipsum libero, auctor ac, tempus nec, tempor nec, justo.
\r\n
Ordering
\r\n
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta. Suspendisse convallis felis ac enim. Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper. Phasellus id massa. Pellentesque nisl. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc augue. Aenean sed justo non leo vehicula laoreet. Praesent ipsum libero, auctor ac, tempus nec, tempor nec, justo.
\r\n
Payment, Pricing & Promotions
\r\n
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta. Suspendisse convallis felis ac enim. Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper. Phasellus id massa. Pellentesque nisl. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc augue. Aenean sed justo non leo vehicula laoreet. Praesent ipsum libero, auctor ac, tempus nec, tempor nec, justo.
\r\n
Viewing Orders
\r\n
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta. Suspendisse convallis felis ac enim. Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper. Phasellus id massa. Pellentesque nisl. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc augue. Aenean sed justo non leo vehicula laoreet. Praesent ipsum libero, auctor ac, tempus nec, tempor nec, justo.
\r\n
Updating Account Information
\r\n
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta. Suspendisse convallis felis ac enim. Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper. Phasellus id massa. Pellentesque nisl. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc augue. Aenean sed justo non leo vehicula laoreet. Praesent ipsum libero, auctor ac, tempus nec, tempor nec, justo.
\r\n
','2007-08-30 14:02:20','2007-08-30 14:03:37',1,0,0); "); diff --git a/app/code/core/Mage/Core/Model/App.php b/app/code/core/Mage/Core/Model/App.php index 6c4da35986..2afcd86fd4 100644 --- a/app/code/core/Mage/Core/Model/App.php +++ b/app/code/core/Mage/Core/Model/App.php @@ -837,7 +837,8 @@ public function getHelper($name) */ public function getBaseCurrencyCode() { - return Mage::getStoreConfig(Mage_Directory_Model_Currency::XML_PATH_CURRENCY_BASE, 0); + //return Mage::getStoreConfig(Mage_Directory_Model_Currency::XML_PATH_CURRENCY_BASE, 0); + return (string) Mage::app()->getConfig()->getNode('default/'.Mage_Directory_Model_Currency::XML_PATH_CURRENCY_BASE); } /** diff --git a/app/code/core/Mage/Core/Model/Config.php b/app/code/core/Mage/Core/Model/Config.php index bfce4904bf..89b5cfe05c 100644 --- a/app/code/core/Mage/Core/Model/Config.php +++ b/app/code/core/Mage/Core/Model/Config.php @@ -28,12 +28,10 @@ /** * Core configuration class * - * Used to retrieve core configuration values - * - * @link http://var-dev.varien.com/wiki/doku.php?id=magento:api:mage:core:config + * @category Mage + * @package Mage_Core * @author Magento Core Team */ - class Mage_Core_Model_Config extends Mage_Core_Model_Config_Base { const CACHE_TAG = 'config'; @@ -514,7 +512,8 @@ protected function _loadDeclaredModules($mergeConfig) } $moduleDepends[$moduleName] = array( 'module' => $moduleName, - 'depends' => $depends + 'depends' => $depends, + 'active' => ('true' === (string)$moduleNode->active ? true : false), ); } @@ -553,6 +552,9 @@ protected function _sortModuleDepends($modules) foreach ($modules as $moduleName => $moduleProps) { $depends = $moduleProps['depends']; foreach ($moduleProps['depends'] as $depend => $true) { + if ($moduleProps['active'] && ((!isset($modules[$depend])) || empty($modules[$depend]['active']))) { + Mage::throwException(Mage::helper('core')->__('Module "%1$s" requires module "%2$s"', $moduleName, $depend)); + } $depends = array_merge($depends, $modules[$depend]['depends']); } $modules[$moduleName]['depends'] = $depends; diff --git a/app/code/core/Mage/Core/Model/Layout/Update.php b/app/code/core/Mage/Core/Model/Layout/Update.php index 3670f0fb6a..f772ced03a 100644 --- a/app/code/core/Mage/Core/Model/Layout/Update.php +++ b/app/code/core/Mage/Core/Model/Layout/Update.php @@ -267,6 +267,7 @@ public function fetchFileLayoutUpdates() if (empty($layoutStr)) { $updatesRoot = Mage::app()->getConfig()->getNode($area.'/layout/updates'); + Mage::dispatchEvent('core_layout_update_updates_get_after', array('updates' => $updatesRoot)); $updateFiles = array(); foreach ($updatesRoot->children() as $updateNode) { if ($updateNode->file) { diff --git a/app/code/core/Mage/Core/Model/Locale.php b/app/code/core/Mage/Core/Model/Locale.php index 02b7106764..d36d3f6698 100644 --- a/app/code/core/Mage/Core/Model/Locale.php +++ b/app/code/core/Mage/Core/Model/Locale.php @@ -88,10 +88,7 @@ class Mage_Core_Model_Locale public function __construct($locale = null) { - if (empty($locale)) { - $locale = $this->getDefaultLocale(); - } - $this->_localeCode = $locale; + $this->setLocale($locale); } /** @@ -131,12 +128,12 @@ public function getDefaultLocale() */ public function setLocale($locale = null) { - Mage::dispatchEvent('core_locale_set_locale', array('locale'=>$this)); - Zend_Locale_Data::setCache(Mage::app()->getCache()); - if ($locale === null) { - $locale = $this->_localeCode; + if (($locale !== null) && is_string($locale)) { + $this->_localeCode = $locale; + } else { + $this->_localeCode = $this->getDefaultLocale(); } - $this->_locale = new Zend_Locale($locale); + Mage::dispatchEvent('core_locale_set_locale', array('locale'=>$this)); return $this; } @@ -168,9 +165,10 @@ public function getCurrency() public function getLocale() { if (!$this->_locale) { - $this->setLocale(); + Zend_Locale_Data::setCache(Mage::app()->getCache()); + $this->_locale = new Zend_Locale($this->getLocaleCode()); } elseif ($this->_locale->__toString() != $this->_localeCode) { - $this->setLocale($this->_localeCode); + $this->setLocale($this->_localeCode); } return $this->_locale; @@ -183,6 +181,9 @@ public function getLocale() */ public function getLocaleCode() { + if ($this->_localeCode === null) { + $this->setLocale(); + } return $this->_localeCode; } diff --git a/app/code/core/Mage/Core/Model/Session/Abstract/Varien.php b/app/code/core/Mage/Core/Model/Session/Abstract/Varien.php index f39eb5ce88..6b596c2033 100644 --- a/app/code/core/Mage/Core/Model/Session/Abstract/Varien.php +++ b/app/code/core/Mage/Core/Model/Session/Abstract/Varien.php @@ -67,7 +67,12 @@ public function start($sessionName=null) break; } - Mage::dispatchEvent('core_session_before_set_cookie_params'); + if (Mage::app()->getStore()->isAdmin()) { + $adminSessionLifetime = (int)Mage::getStoreConfig('admin/security/session_cookie_lifetime'); + if ($adminSessionLifetime > 60) { + Mage::getSingleton('core/cookie')->setLifetime($adminSessionLifetime); + } + } // set session cookie params session_set_cookie_params( @@ -118,17 +123,17 @@ public function revalidateCookie() if (!$this->getCookie()->getLifetime()) { return $this; } - if (empty($this->_data['_cookie_revalidate'])) { - $time = time() + round(ini_get('session.gc_maxlifetime') / 4); - $this->_data['_cookie_revalidate'] = $time; + if (empty($_SESSION['_cookie_revalidate'])) { + $time = time() + round($this->getCookie()->getLifetime() / 4); + $_SESSION['_cookie_revalidate'] = $time; } else { - if ($this->_data['_cookie_revalidate'] < time()) { + if ($_SESSION['_cookie_revalidate'] < time()) { if (!headers_sent()) { $this->getCookie()->set(session_name(), session_id()); $time = time() + round($this->getCookie()->getLifetime() / 4); - $this->_data['_cookie_revalidate'] = $time; + $_SESSION['_cookie_revalidate'] = $time; } } } diff --git a/app/code/core/Mage/Core/Model/Store.php b/app/code/core/Mage/Core/Model/Store.php index a4d9a38bdb..0ed0e8f1dc 100644 --- a/app/code/core/Mage/Core/Model/Store.php +++ b/app/code/core/Mage/Core/Model/Store.php @@ -485,7 +485,7 @@ public function isFrontUrlSecure() public function isCurrentlySecure() { - if (!empty($_SERVER['HTTPS'])) { + if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') { return true; } diff --git a/app/code/core/Mage/Core/etc/config.xml b/app/code/core/Mage/Core/etc/config.xml index 26c47f672e..6bc6e07927 100644 --- a/app/code/core/Mage/Core/etc/config.xml +++ b/app/code/core/Mage/Core/etc/config.xml @@ -232,10 +232,10 @@ - 1 - 1 - 1 - 1 + 0 + 0 + 0 + 0 diff --git a/app/code/core/Mage/Core/etc/system.xml b/app/code/core/Mage/Core/etc/system.xml index 67e32bfbec..69efe89715 100644 --- a/app/code/core/Mage/Core/etc/system.xml +++ b/app/code/core/Mage/Core/etc/system.xml @@ -785,7 +785,7 @@ 0 - + select adminhtml/system_config_source_yesno adminhtml/system_config_backend_admin_usesecretkey @@ -794,6 +794,14 @@ 0 0 + + + Values less than 60 are ignored. + 3 + 1 + 0 + 0 + diff --git a/app/code/core/Mage/Core/functions.php b/app/code/core/Mage/Core/functions.php index a80f762dfc..3da5de84fd 100644 --- a/app/code/core/Mage/Core/functions.php +++ b/app/code/core/Mage/Core/functions.php @@ -61,10 +61,7 @@ function mageUndoMagicQuotes($array, $topLevel=true) { */ function __autoload($class) { - if (strpos($class, '/')!==false) { - return; - } - $classFile = uc_words($class, DS).'.php'; + $classFile = uc_words($class, DIRECTORY_SEPARATOR).'.php'; //$a = explode('_', $class); //Varien_Profiler::start('AUTOLOAD'); @@ -87,10 +84,6 @@ function destruct($object) foreach ($object as $obj) { destruct($obj); } - } elseif (is_object($object)) { - if (in_array('__destruct', get_class_methods($object))) { - $object->__destruct(); - } } unset($object); } @@ -98,6 +91,7 @@ function destruct($object) /** * Translator function * + * @deprecated 1.3 * @param string $text the text to translate * @param mixed optional parameters to use in sprintf */ diff --git a/app/code/core/Mage/Customer/Helper/Data.php b/app/code/core/Mage/Customer/Helper/Data.php index 496a7c7c9d..efc0cc1374 100644 --- a/app/code/core/Mage/Customer/Helper/Data.php +++ b/app/code/core/Mage/Customer/Helper/Data.php @@ -20,19 +20,32 @@ * * @category Mage * @package Mage_Customer - * @copyright Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com) + * @copyright Copyright (c) 2009 Irubin Consulting Inc. DBA Varien (http://www.varien.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** - * Customer data helper + * Customer Data Helper * - * @author Magento Core Team + * @category Mage + * @package Mage_Customer + * @author Magento Core Team */ class Mage_Customer_Helper_Data extends Mage_Core_Helper_Abstract { + /** + * Customer groups collection + * + * @var Mage_Customer_Model_Entity_Group_Collection + */ protected $_groups; + /** + * Check customer is logged in + * + * @return bool + */ public function isLoggedIn() { return Mage::getSingleton('customer/session')->isLoggedIn(); @@ -51,6 +64,11 @@ public function getCustomer() return $this->_customer; } + /** + * Retrieve customer groups collection + * + * @return Mage_Customer_Model_Entity_Group_Collection + */ public function getGroups() { if (empty($this->_groups)) { @@ -61,20 +79,34 @@ public function getGroups() return $this->_groups; } - + /** + * Retrieve current (loggined) customer object + * + * @return Mage_Customer_Model_Customer + */ public function getCurrentCustomer() { return $this->getCustomer(); } + /** + * Retrieve current customer name + * + * @return string + */ public function getCustomerName() { return $this->getCustomer()->getName(); } + /** + * Check customer has address + * + * @return bool + */ public function customerHasAddresses() { - return count($this->getCustomer()->getAddresses()); + return count($this->getCustomer()->getAddresses()) > 0; } /************************************************************************** @@ -91,6 +123,11 @@ public function getLoginUrl() return $this->_getUrl('customer/account/login'); } + /** + * Retrieve customer login POST URL + * + * @return string + */ public function getLoginPostUrl() { return $this->_getUrl('customer/account/loginPost'); @@ -156,6 +193,11 @@ public function getEditUrl() return $this->_getUrl('customer/account/edit'); } + /** + * Retrieve customer edit POST URL + * + * @return string + */ public function getEditPostUrl() { return $this->_getUrl('customer/account/editpost'); @@ -171,13 +213,36 @@ public function getForgotPasswordUrl() return $this->_getUrl('customer/account/forgotpassword'); } + /** + * Check is confirmation required + * + * @return bool + */ public function isConfirmationRequired() { return $this->getCustomer()->isConfirmationRequired(); } + /** + * Retrieve confirmation URL for Email + * + * @param string $email + * @return string + */ public function getEmailConfirmationUrl($email = null) { return $this->_getUrl('customer/account/confirmation', array('email' => $email)); } + + /** + * Check whether customers registration is allowed + * + * @return bool + */ + public function isRegistrationAllowed() + { + $result = new Varien_Object(array('is_allowed' => true)); + Mage::dispatchEvent('customer_registration_is_allowed', array('result' => $result)); + return $result->getIsAllowed(); + } } diff --git a/app/code/core/Mage/Directory/Model/Currency/Import/Webservicex.php b/app/code/core/Mage/Directory/Model/Currency/Import/Webservicex.php index d91999444d..b5ed10a963 100644 --- a/app/code/core/Mage/Directory/Model/Currency/Import/Webservicex.php +++ b/app/code/core/Mage/Directory/Model/Currency/Import/Webservicex.php @@ -56,7 +56,7 @@ protected function _convert($currencyFrom, $currencyTo, $retry=0) try { $response = $this->_httpClient ->setUri($url) - ->setConfig(array('timeout' => 15)) + ->setConfig(array('timeout' => Mage::getStoreConfig('currency/webservicex/timeout'))) ->request('GET') ->getBody(); diff --git a/app/code/core/Mage/Directory/etc/config.xml b/app/code/core/Mage/Directory/etc/config.xml index 0e19e4e068..9f8ee1f123 100644 --- a/app/code/core/Mage/Directory/etc/config.xml +++ b/app/code/core/Mage/Directory/etc/config.xml @@ -149,6 +149,9 @@ USD USD + + 100 + 0 diff --git a/app/code/core/Mage/Directory/etc/system.xml b/app/code/core/Mage/Directory/etc/system.xml index 7a60b4366e..ca1c60b674 100644 --- a/app/code/core/Mage/Directory/etc/system.xml +++ b/app/code/core/Mage/Directory/etc/system.xml @@ -78,7 +78,24 @@ 1 - z + + + + 40 + 1 + 0 + 0 + + + + text + 0 + 1 + 0 + 0 + + + text diff --git a/app/code/core/Mage/Downloadable/Model/Sales/Order/Pdf/Items/Creditmemo.php b/app/code/core/Mage/Downloadable/Model/Sales/Order/Pdf/Items/Creditmemo.php index cb991a1b09..598638d82d 100644 --- a/app/code/core/Mage/Downloadable/Model/Sales/Order/Pdf/Items/Creditmemo.php +++ b/app/code/core/Mage/Downloadable/Model/Sales/Order/Pdf/Items/Creditmemo.php @@ -71,7 +71,8 @@ public function draw() } // draw options value $this->_setFontRegular(); - foreach (Mage::helper('core/string')->str_split(strip_tags($option['value']), $x, true, true) as $_value) { + $_printValue = isset($option['print_value']) ? $option['print_value'] : strip_tags($option['value']); + foreach (Mage::helper('core/string')->str_split($_printValue, $x, true, true) as $_value) { $page->drawText($_value, $x + 5, $pdf->y - $shift[0], 'UTF-8'); $shift[0] += 10; } diff --git a/app/code/core/Mage/Downloadable/Model/Sales/Order/Pdf/Items/Invoice.php b/app/code/core/Mage/Downloadable/Model/Sales/Order/Pdf/Items/Invoice.php index 8dec4ab5e5..61c35ddc2f 100644 --- a/app/code/core/Mage/Downloadable/Model/Sales/Order/Pdf/Items/Invoice.php +++ b/app/code/core/Mage/Downloadable/Model/Sales/Order/Pdf/Items/Invoice.php @@ -65,7 +65,8 @@ public function draw() // draw options value $this->_setFontRegular(); if ($option['value']) { - $values = explode(', ', 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, 60,true,true) as $_value) { $page->drawText($_value, 40, $pdf->y-$shift[0], 'UTF-8'); diff --git a/app/code/core/Mage/Eav/Model/Entity/Attribute/Abstract.php b/app/code/core/Mage/Eav/Model/Entity/Attribute/Abstract.php index b2dd65e3c9..86fecf584f 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Attribute/Abstract.php +++ b/app/code/core/Mage/Eav/Model/Entity/Attribute/Abstract.php @@ -569,7 +569,12 @@ public function getFlatColumns() { */ public function getFlatIndexes() { - if ($this->getIsFilterable() or $this->getIsFilterableInSearch() or $this->getUsedForSortBy()) { + $condition = $this->getUsedForSortBy(); + if ($this->getFlatAddFilterableAttributes()) { + $condition = $condition || $this->getIsFilterable(); + } + + if ($condition) { if ($this->usesSource() && $this->getBackendType() != 'static') { return $this->getSource()->getFlatIndexes(); } diff --git a/app/code/core/Mage/Eav/Model/Entity/Attribute/Source/Boolean.php b/app/code/core/Mage/Eav/Model/Entity/Attribute/Source/Boolean.php index e301998819..f676ff80fc 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Attribute/Source/Boolean.php +++ b/app/code/core/Mage/Eav/Model/Entity/Attribute/Source/Boolean.php @@ -89,7 +89,7 @@ public function getFlatColums() { $columns = array(); $columns[$this->getAttribute()->getAttributeCode()] = array( - 'type' => 'int', + 'type' => 'tinyint(1)', 'unsigned' => false, 'is_null' => true, 'default' => null, @@ -106,7 +106,15 @@ public function getFlatColums() */ public function getFlatIndexes() { - return array(); + $indexes = array(); + + $index = 'IDX_' . strtoupper($this->getAttribute()->getAttributeCode()); + $indexes[$index] = array( + 'type' => 'index', + 'fields' => array($this->getAttribute()->getAttributeCode()) + ); + + return $indexes; } /** diff --git a/app/code/core/Mage/Eav/Model/Entity/Attribute/Source/Table.php b/app/code/core/Mage/Eav/Model/Entity/Attribute/Source/Table.php index 0dadda2c00..1468ce692a 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Attribute/Source/Table.php +++ b/app/code/core/Mage/Eav/Model/Entity/Attribute/Source/Table.php @@ -175,25 +175,19 @@ public function getFlatIndexes() { $indexes = array(); - $filterable = $this->getAttribute()->getIsFilterable() - or $this->getAttribute()->getIsFilterableInSearch(); - $sortable = $this->getAttribute()->getUsedForSortBy(); + $index = 'IDX_' . strtoupper($this->getAttribute()->getAttributeCode()); + $indexes[$index] = array( + 'type' => 'index', + 'fields' => array($this->getAttribute()->getAttributeCode()) + ); - if ($filterable or $sortable) { - if ($sortable and $this->getAttribute()->getFrontend()->getInputType() != 'multiselect') { - $index = 'IDX_' . strtoupper($this->getAttribute()->getAttributeCode()) . '_VALUE'; - $indexes[$index] = array( - 'type' => 'index', - 'fields' => array($this->getAttribute()->getAttributeCode() . '_value') - ); - } - if ($filterable) { - $index = 'IDX_' . strtoupper($this->getAttribute()->getAttributeCode()); - $indexes[$index] = array( - 'type' => 'index', - 'fields' => array($this->getAttribute()->getAttributeCode()) - ); - } + $sortable = $this->getAttribute()->getUsedForSortBy(); + if ($sortable and $this->getAttribute()->getFrontend()->getInputType() != 'multiselect') { + $index = 'IDX_' . strtoupper($this->getAttribute()->getAttributeCode()) . '_VALUE'; + $indexes[$index] = array( + 'type' => 'index', + 'fields' => array($this->getAttribute()->getAttributeCode() . '_value') + ); } return $indexes; diff --git a/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute.php b/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute.php index f30f45953b..29411a72e5 100644 --- a/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute.php +++ b/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute.php @@ -393,10 +393,14 @@ public function getAttributeCodesByFrontendType($type) * @return Varien_Db_Select */ public function getFlatUpdateSelect(Mage_Eav_Model_Entity_Attribute_Abstract $attribute, $store) { - return $this->_getReadAdapter()->select() + $joinCondition = "`e`.`entity_id`=`t1`.`entity_id`"; + if ($attribute->getFlatAddChildData()) { + $joinCondition .= " AND `e`.`child_id`=`t1`.`entity_id`"; + } + $select = $this->_getReadAdapter()->select() ->joinLeft( array('t1' => $attribute->getBackend()->getTable()), - "`e`.`entity_id`=`t1`.`entity_id` AND `e`.`child_id`=`t1`.`entity_id`", + $joinCondition, array() ) ->joinLeft( @@ -408,8 +412,11 @@ public function getFlatUpdateSelect(Mage_Eav_Model_Entity_Attribute_Abstract $at array($attribute->getAttributeCode() => "IFNULL(t2.value, t1.value)")) ->where("t1.entity_type_id=?", $attribute->getEntityTypeId()) ->where("t1.attribute_id=?", $attribute->getId()) - ->where("t1.store_id=?", 0) - ->where("e.is_child=?", 0); + ->where("t1.store_id=?", 0); + if ($attribute->getFlatAddChildData()) { + $select->where("e.is_child=?", 0); + } + return $select; } /** diff --git a/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute/Collection.php b/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute/Collection.php index c03889ef18..7c5e887991 100644 --- a/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute/Collection.php +++ b/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute/Collection.php @@ -64,6 +64,8 @@ public function useLoadDataFields() 'backend_model', 'backend_type', 'backend_table', + 'frontend_input', + 'source_model', 'is_global' )); return $this; diff --git a/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute/Option.php b/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute/Option.php index 6823144f0b..9083605d38 100644 --- a/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute/Option.php +++ b/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute/Option.php @@ -81,11 +81,16 @@ public function getFlatUpdateSelect(Mage_Eav_Model_Entity_Attribute_Abstract $at $attributeTable = $attribute->getBackend()->getTable(); $attributeCode = $attribute->getAttributeCode(); + $joinCondition = "`e`.`entity_id`=`t1`.`entity_id`"; + if ($attribute->getFlatAddChildData()) { + $joinCondition .= " AND `e`.`child_id`=`t1`.`entity_id`"; + } + $valueExpr = new Zend_Db_Expr("IFNULL(t2.value, t1.value)"); $select = $this->_getReadAdapter()->select() ->joinLeft( array('t1' => $attributeTable), - "`e`.`entity_id`=`t1`.`entity_id` AND `e`.`child_id`=`t1`.`entity_id`", + $joinCondition, array() ) ->joinLeft( @@ -113,6 +118,9 @@ public function getFlatUpdateSelect(Mage_Eav_Model_Entity_Attribute_Abstract $at ->where('t1.attribute_id=?', $attribute->getId()) ->where('t1.store_id=?', 0); + if ($attribute->getFlatAddChildData()) { + $select->where("e.is_child=?", 0); + } return $select; } } diff --git a/app/code/core/Mage/GoogleAnalytics/Block/Ga.php b/app/code/core/Mage/GoogleAnalytics/Block/Ga.php index cf8f5a0325..5fbe2acfcb 100644 --- a/app/code/core/Mage/GoogleAnalytics/Block/Ga.php +++ b/app/code/core/Mage/GoogleAnalytics/Block/Ga.php @@ -20,22 +20,24 @@ * * @category Mage * @package Mage_GoogleAnalytics - * @copyright Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com) + * @copyright Copyright (c) 2009 Irubin Consulting Inc. DBA Varien (http://www.varien.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** - * Google Analytics block + * GoogleAnalitics Page Block * * @category Mage * @package Mage_GoogleAnalytics + * @author Magento Core Team */ class Mage_GoogleAnalytics_Block_Ga extends Mage_Core_Block_Text { /** * Retrieve Quote Data HTML * - * @return unknown + * @return string */ public function getQuoteOrdersHtml() { @@ -94,12 +96,12 @@ public function getOrderHtml() $html .= 'pageTracker._addTrans('; $html .= '"' . $order->getIncrementId() . '",'; $html .= '"' . $order->getAffiliation() . '",'; - $html .= '"' . $order->getGrandTotal() . '",'; - $html .= '"' . $order->getTaxAmount() . '",'; - $html .= '"' . $order->getShippingAmount() . '",'; - $html .= '"' . $address->getCity() . '",'; - $html .= '"' . $address->getRegion() . '",'; - $html .= '"' . $address->getCountry() . '"'; + $html .= '"' . $order->getBaseGrandTotal() . '",'; + $html .= '"' . $order->getBaseTaxAmount() . '",'; + $html .= '"' . $order->getBaseShippingAmount() . '",'; + $html .= '"' . $this->jsQuoteEscape($address->getCity(), '"') . '",'; + $html .= '"' . $this->jsQuoteEscape($address->getRegion(), '"') . '",'; + $html .= '"' . $this->jsQuoteEscape($address->getCountry(), '"') . '"'; $html .= ');' . "\n"; foreach ($order->getAllItems() as $item) { @@ -109,10 +111,10 @@ public function getOrderHtml() $html .= 'pageTracker._addItem('; $html .= '"' . $order->getIncrementId() . '",'; - $html .= '"' . $item->getSku() . '",'; - $html .= '"' . $item->getName() . '",'; + $html .= '"' . $this->jsQuoteEscape($item->getSku(), '"') . '",'; + $html .= '"' . $this->jsQuoteEscape($item->getName(), '"') . '",'; $html .= '"' . $item->getCategory() . '",'; - $html .= '"' . $item->getPrice() . '",'; + $html .= '"' . $item->getBasePrice() . '",'; $html .= '"' . $item->getQtyOrdered() . '"'; $html .= ');' . "\n"; } diff --git a/app/code/core/Mage/GoogleCheckout/etc/system.xml b/app/code/core/Mage/GoogleCheckout/etc/system.xml index aafeb0a8cd..9c10af8677 100644 --- a/app/code/core/Mage/GoogleCheckout/etc/system.xml +++ b/app/code/core/Mage/GoogleCheckout/etc/system.xml @@ -39,7 +39,7 @@ text - Signup for Google Checkout]]> + Signup for Google Checkout]]> 20 1 1 diff --git a/app/code/core/Mage/Install/Block/Begin.php b/app/code/core/Mage/Install/Block/Begin.php index 83f6734935..3282bfbc7c 100644 --- a/app/code/core/Mage/Install/Block/Begin.php +++ b/app/code/core/Mage/Install/Block/Begin.php @@ -23,7 +23,7 @@ * @copyright Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ - + /** * Installation begin block * @@ -31,19 +31,40 @@ */ class Mage_Install_Block_Begin extends Mage_Install_Block_Abstract { - public function __construct() + /** + * Set template + * + */ + public function __construct() { parent::__construct(); $this->setTemplate('install/begin.phtml'); } - + + /** + * Deprecated + */ public function getLanguages() { - return Mage::getSingleton('install/config')->getLanguages(); } - + + /** + * Get wizard URL + * + * @return string + */ public function getPostUrl() { return Mage::getUrl('install/wizard/beginPost'); } + + /** + * Get License HTML contents + * + * @return string + */ + public function getLicenseHtml() + { + return file_get_contents(BP . DS . (string)Mage::getConfig()->getNode('install/eula_file')); + } } diff --git a/app/code/core/Mage/Install/Model/Observer.php b/app/code/core/Mage/Install/Model/Observer.php index 438e1ed8f6..960d24603b 100644 --- a/app/code/core/Mage/Install/Model/Observer.php +++ b/app/code/core/Mage/Install/Model/Observer.php @@ -35,7 +35,7 @@ public function bindLocale($observer) { if ($locale=$observer->getEvent()->getLocale()) { if ($choosedLocale = Mage::getSingleton('install/session')->getLocale()) { - $locale->setDefaultLocale($choosedLocale); + $locale->setLocaleCode($choosedLocale); } } return $this; diff --git a/app/code/core/Mage/Install/etc/config.xml b/app/code/core/Mage/Install/etc/config.xml index 9cef93f6bb..d99d1ea67d 100644 --- a/app/code/core/Mage/Install/etc/config.xml +++ b/app/code/core/Mage/Install/etc/config.xml @@ -118,5 +118,6 @@ + LICENSE.html
\ No newline at end of file diff --git a/app/code/core/Mage/Log/Model/Mysql4/Log.php b/app/code/core/Mage/Log/Model/Mysql4/Log.php index a8a092a8f8..4aafd154c9 100644 --- a/app/code/core/Mage/Log/Model/Mysql4/Log.php +++ b/app/code/core/Mage/Log/Model/Mysql4/Log.php @@ -83,12 +83,10 @@ protected function _cleanVisitors($time) array('visitor_id' => 'visitor_table.visitor_id')) ->joinLeft( array('customer_table' => $this->getTable('log/customer')), - 'visitor_table.visitor_id = customer_table.visitor_id', + 'visitor_table.visitor_id = customer_table.visitor_id AND customer_table.log_id IS NULL', array()) - ->where('customer_table.log_id IS NULL') ->where('visitor_table.last_visit_at < ?', gmdate('Y-m-d H:i:s', time() - $time)) - ->order('visitor_table.visitor_id') - ->limit(1000); + ->limit(100); $query = $this->_getReadAdapter()->query($select); $visitorIds = array(); @@ -179,7 +177,7 @@ protected function _cleanCustomers($time) ->where('log_id>?', $customerLogId) ->where('log_idorder('log_id') - ->limit(1000); + ->limit(100); $query = $this->_getReadAdapter()->query($select); $count = 0; @@ -250,11 +248,9 @@ protected function _cleanUrls() array('url_id')) ->joinLeft( array('url_table' => $this->getTable('log/url_table')), - 'url_info_table.url_id = url_table.url_id', + 'url_info_table.url_id = url_table.url_id AND url_table.url_id IS NULL', array()) - ->where('url_table.url_id IS NULL') - ->order('url_info_table.url_id ASC') - ->limit(1000); + ->limit(100); $query = $this->_getReadAdapter()->query($select); while ($row = $query->fetch()) { $urlIds[] = $row['url_id']; diff --git a/app/code/core/Mage/Log/etc/config.xml b/app/code/core/Mage/Log/etc/config.xml index 296d849792..472def4884 100644 --- a/app/code/core/Mage/Log/etc/config.xml +++ b/app/code/core/Mage/Log/etc/config.xml @@ -28,7 +28,7 @@ - 0.7.3 + 0.7.4 @@ -196,14 +196,14 @@ log/cron::logClean - + \ No newline at end of file diff --git a/app/code/core/Mage/Log/sql/log_setup/mysql4-upgrade-0.7.3-0.7.4.php b/app/code/core/Mage/Log/sql/log_setup/mysql4-upgrade-0.7.3-0.7.4.php new file mode 100644 index 0000000000..07f7db1607 --- /dev/null +++ b/app/code/core/Mage/Log/sql/log_setup/mysql4-upgrade-0.7.3-0.7.4.php @@ -0,0 +1,38 @@ +startSetup(); +$installer->getConnection()->addKey($installer->getTable('log/customer'), 'IDX_VISITOR', 'visitor_id'); +$installer->run(" +DELETE FROM `{$installer->getTable('log/url_table')}` WHERE `url_id`=1 AND `visitor_id`; +"); +$installer->getConnection()->addKey($installer->getTable('log/url_table'), 'PRIMARY', 'url_id', 'primary'); +$installer->getConnection()->addKey($installer->getTable('log/url_table'), 'IDX_VISITOR', 'visitor_id'); +$installer->endSetup(); diff --git a/app/code/core/Mage/Payment/etc/system.xml b/app/code/core/Mage/Payment/etc/system.xml index 2274f1aa21..7e77234bff 100644 --- a/app/code/core/Mage/Payment/etc/system.xml +++ b/app/code/core/Mage/Payment/etc/system.xml @@ -199,7 +199,7 @@ 61 1 1 - 0 + 1 @@ -207,7 +207,7 @@ 62 1 1 - 0 + 1 diff --git a/app/code/core/Mage/Paypal/Model/Express.php b/app/code/core/Mage/Paypal/Model/Express.php index fdfa333ce1..bc38303a73 100644 --- a/app/code/core/Mage/Paypal/Model/Express.php +++ b/app/code/core/Mage/Paypal/Model/Express.php @@ -304,7 +304,7 @@ protected function _getExpressCheckoutDetails() */ if ($this->getSession()->getExpressCheckoutMethod()=='shortcut' - || ($this->getSession()->getExpressCheckoutMethod()!='shortcut' && $q->getCheckoutMethod()!='register')){ + || ($this->getSession()->getExpressCheckoutMethod()!='shortcut' && $q->getCheckoutMethod()!=Mage_Sales_Model_Quote::CHECKOUT_METHOD_REGISTER)){ $q->getBillingAddress() ->setPrefix($a->getPrefix()) ->setFirstname($a->getFirstname()) diff --git a/app/code/core/Mage/Paypal/controllers/ExpressController.php b/app/code/core/Mage/Paypal/controllers/ExpressController.php index 9bfc2b49a1..7a1b119a5d 100644 --- a/app/code/core/Mage/Paypal/controllers/ExpressController.php +++ b/app/code/core/Mage/Paypal/controllers/ExpressController.php @@ -205,7 +205,7 @@ public function saveOrderAction() $order->place(); - if (isset($customer) && $customer && $this->getReview()->getQuote()->getCheckoutMethod()=='register') { + if (isset($customer) && $customer && $this->getReview()->getQuote()->getCheckoutMethod()==Mage_Sales_Model_Quote::CHECKOUT_METHOD_REGISTER) { $customer->save(); $customer->setDefaultBilling($customerBilling->getId()); $customerShippingId = isset($customerShipping) ? $customerShipping->getId() : $customerBilling->getId(); diff --git a/app/code/core/Mage/Paypal/etc/system.xml b/app/code/core/Mage/Paypal/etc/system.xml index cecd8e5790..9623f1f820 100644 --- a/app/code/core/Mage/Paypal/etc/system.xml +++ b/app/code/core/Mage/Paypal/etc/system.xml @@ -31,6 +31,7 @@ + Sign up for PayPal Merchant Account now!]]> text 101 1 @@ -111,6 +112,7 @@ + Sign up for PayPal Merchant Account now!]]> text 101 1 @@ -182,6 +184,7 @@ + Sign up for PayPal Merchant Account now!]]> text 102 1 @@ -282,6 +285,7 @@ + Sign up for PayPal Merchant Account now!]]> text 1 1 @@ -381,6 +385,7 @@ + Sign up for PayPal Merchant Account now!]]> text 1 1 diff --git a/app/code/core/Mage/PaypalUk/Model/Api/Pro.php b/app/code/core/Mage/PaypalUk/Model/Api/Pro.php index 758a697c24..6636c5421a 100644 --- a/app/code/core/Mage/PaypalUk/Model/Api/Pro.php +++ b/app/code/core/Mage/PaypalUk/Model/Api/Pro.php @@ -147,7 +147,8 @@ public function callDoDirectPayment() if ($result && $result->getResultCode()==self::RESPONSE_CODE_APPROVED) { $this->setTransactionId($result->getPnref()); - $this->setAvsZip($result->getAvsZip()); + $this->setAvsZip($result->getAvszip()); + $this->setAvsCode($result->getAvszip()); $this->setCvv2Match($result->getCvv2match()); } else { $errorArr['code'] = $result->getResultCode(); diff --git a/app/code/core/Mage/PaypalUk/Model/Direct.php b/app/code/core/Mage/PaypalUk/Model/Direct.php index d302666cd5..b97ae63e72 100644 --- a/app/code/core/Mage/PaypalUk/Model/Direct.php +++ b/app/code/core/Mage/PaypalUk/Model/Direct.php @@ -37,10 +37,23 @@ class Mage_PaypalUk_Model_Direct extends Mage_Payment_Model_Method_Cc protected $_infoBlockType = 'paypaluk/direct_info'; protected $_canSaveCc = false; - /* - * overwrites the method of Mage_Payment_Model_Method_Cc - * for switch or solo card - */ + /** + * Availability options + */ + protected $_isGateway = true; + protected $_canAuthorize = true; + protected $_canCapture = true; + protected $_canCapturePartial = false; + protected $_canRefund = false; + protected $_canVoid = true; + protected $_canUseInternal = true; + protected $_canUseCheckout = true; + protected $_canUseForMultishipping = true; + + /** + * overwrites the method of Mage_Payment_Model_Method_Cc + * for switch or solo card + */ public function OtherCcType($type) { return (parent::OtherCcType($type) || $type=='SS'); @@ -78,7 +91,7 @@ public function assignData($data) */ public function getApi() { - return Mage::getSingleton('paypalUk/api_pro'); + return Mage::getSingleton('paypaluk/api_pro'); } public function authorize(Varien_Object $payment, $amount) diff --git a/app/code/core/Mage/PaypalUk/Model/Express.php b/app/code/core/Mage/PaypalUk/Model/Express.php index d6946ea9fd..3fac1636f4 100644 --- a/app/code/core/Mage/PaypalUk/Model/Express.php +++ b/app/code/core/Mage/PaypalUk/Model/Express.php @@ -257,7 +257,7 @@ protected function _getExpressCheckoutDetails() if the customer checkout from mark(one page) and guest */ if ($this->getSession()->getExpressCheckoutMethod()=='shortcut' || - ($this->getSession()->getExpressCheckoutMethod()!='shortcut' && $q->getCheckoutMethod()!='register')){ + ($this->getSession()->getExpressCheckoutMethod()!='shortcut' && $q->getCheckoutMethod()!=Mage_Sales_Model_Quote::CHECKOUT_METHOD_REGISTER)){ $q->getBillingAddress() ->setFirstname($a->getFirstname()) ->setLastname($a->getLastname()) diff --git a/app/code/core/Mage/PaypalUk/controllers/ExpressController.php b/app/code/core/Mage/PaypalUk/controllers/ExpressController.php index 2291464de3..e99552904d 100644 --- a/app/code/core/Mage/PaypalUk/controllers/ExpressController.php +++ b/app/code/core/Mage/PaypalUk/controllers/ExpressController.php @@ -198,7 +198,7 @@ public function saveOrderAction() $order->place(); - if (isset($customer) && $customer && $this->getReview()->getQuote()->getCheckoutMethod()=='register') { + if (isset($customer) && $customer && $this->getReview()->getQuote()->getCheckoutMethod()==Mage_Sales_Model_Quote::CHECKOUT_METHOD_REGISTER) { $customer->save(); $customer->setDefaultBilling($customerBilling->getId()); $customerShippingId = isset($customerShipping) ? $customerShipping->getId() : $customerBilling->getId(); diff --git a/app/code/core/Mage/PaypalUk/etc/system.xml b/app/code/core/Mage/PaypalUk/etc/system.xml index 839ee7bda4..1e946659b8 100644 --- a/app/code/core/Mage/PaypalUk/etc/system.xml +++ b/app/code/core/Mage/PaypalUk/etc/system.xml @@ -31,6 +31,7 @@ + Sign up for PayPal Merchant Account now!]]> text 150 1 @@ -111,6 +112,7 @@ + Sign up for PayPal Merchant Account now!]]> text 150 1 @@ -186,6 +188,7 @@ + Sign up for PayPal Merchant Account now!]]> text 1 1 diff --git a/app/code/core/Mage/Reports/Model/Mysql4/Product/Sold/Collection.php b/app/code/core/Mage/Reports/Model/Mysql4/Product/Sold/Collection.php new file mode 100644 index 0000000000..57e1686d46 --- /dev/null +++ b/app/code/core/Mage/Reports/Model/Mysql4/Product/Sold/Collection.php @@ -0,0 +1,67 @@ + + */ +class Mage_Reports_Model_Mysql4_Product_Sold_Collection extends Mage_Reports_Model_Mysql4_Product_Collection +{ + /** + * Set Date range to collection + * + * @param int $from + * @param int $to + * @return Mage_Reports_Model_Mysql4_Product_Sold_Collection + */ + public function setDateRange($from, $to) + { + $this->_reset() + ->addAttributeToSelect('*') + ->addOrderedQty($from, $to) + ->setOrder('ordered_qty', 'desc'); + + return $this; + } + + /** + * Set Store filter to collection + * + * @param array $storeIds + * @return Mage_Reports_Model_Mysql4_Product_Sold_Collection + */ + public function setStoreIds($storeIds) + { + $storeId = array_pop($storeIds); + $this->setStoreId($storeId); + $this->addStoreFilter($storeId); + return $this; + } +} diff --git a/app/code/core/Mage/Reports/etc/config.xml b/app/code/core/Mage/Reports/etc/config.xml index e1a9dbb7b6..8315d4f153 100644 --- a/app/code/core/Mage/Reports/etc/config.xml +++ b/app/code/core/Mage/Reports/etc/config.xml @@ -225,6 +225,10 @@ Bestsellers adminhtml/report_product/ordered + + Products Ordered + adminhtml/report_product/sold + Most Viewed adminhtml/report_product/viewed @@ -361,6 +365,9 @@ Bestsellers + + Products Ordered + Most Viewed diff --git a/app/code/core/Mage/Review/controllers/ProductController.php b/app/code/core/Mage/Review/controllers/ProductController.php index 21db420aad..672f9dbe98 100644 --- a/app/code/core/Mage/Review/controllers/ProductController.php +++ b/app/code/core/Mage/Review/controllers/ProductController.php @@ -179,7 +179,7 @@ public function listAction() } $this->renderLayout(); - } elseif ($this->getRequest()->isDispatched()) { + } elseif (!$this->getResponse()->isRedirect()) { $this->_forward('noRoute'); } } diff --git a/app/code/core/Mage/Rss/Block/Catalog/Special.php b/app/code/core/Mage/Rss/Block/Catalog/Special.php index 6993f0b2c6..a02a1c9ab5 100644 --- a/app/code/core/Mage/Rss/Block/Catalog/Special.php +++ b/app/code/core/Mage/Rss/Block/Catalog/Special.php @@ -64,7 +64,7 @@ protected function _toHtml() array('attribute'=>'special_to_date', 'is' => new Zend_Db_Expr('null')) ), '', 'left') ->addAttributeToSort('special_from_date', 'desc') - ->addAttributeToSelect(array('name', 'short_description', 'description', 'price', 'thumbnail'), 'inner') + ->addAttributeToSelect(array('name', 'short_description', 'description', 'price', 'thumbnail', 'special_to_date'), 'inner') ->joinTable('catalogrule/rule_product_price', 'product_id=entity_id', array('rule_price'=>'rule_price', 'rule_start_date'=>'latest_start_date'), $rulePriceWhere, 'left') ; diff --git a/app/code/core/Mage/Rss/Helper/Data.php b/app/code/core/Mage/Rss/Helper/Data.php index 6609673a58..8e21ff758a 100644 --- a/app/code/core/Mage/Rss/Helper/Data.php +++ b/app/code/core/Mage/Rss/Helper/Data.php @@ -20,17 +20,24 @@ * * @category Mage * @package Mage_Rss - * @copyright Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com) + * @copyright Copyright (c) 2009 Irubin Consulting Inc. DBA Varien (http://www.varien.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** - * Default rss helper + * Rss data helper * - * @author Magento Core Team + * @category Mage + * @package Mage_Rss + * @author Magento Core Team */ class Mage_Rss_Helper_Data extends Mage_Core_Helper_Abstract { + /** + * Authenticate customer on frontend + * + */ public function authFrontend() { $session = Mage::getSingleton('rss/session'); @@ -46,6 +53,11 @@ public function authFrontend() } } + /** + * Authenticate admin and check ACL + * + * @param string $path + */ public function authAdmin($path) { $session = Mage::getSingleton('rss/session'); @@ -53,6 +65,7 @@ public function authAdmin($path) return; } list($username, $password) = $this->authValidate(); + Mage::getSingleton('adminhtml/url')->setNoSecret(true); $adminSession = Mage::getModel('admin/session'); $user = $adminSession->login($username, $password); //$user = Mage::getModel('admin/user')->login($username, $password); @@ -63,12 +76,22 @@ public function authAdmin($path) } } + /** + * Validate Authenticate + * + * @param array $headers + * @return array + */ public function authValidate($headers=null) { $userPass = Mage::helper('core/http')->authValidate($headers); return $userPass; } + /** + * Send authenticate failed headers + * + */ public function authFailed() { Mage::helper('core/http')->authFailed(); diff --git a/app/code/core/Mage/Sales/Block/Order/Item/Renderer/Default.php b/app/code/core/Mage/Sales/Block/Order/Item/Renderer/Default.php index e3b978282a..e0548905ec 100644 --- a/app/code/core/Mage/Sales/Block/Order/Item/Renderer/Default.php +++ b/app/code/core/Mage/Sales/Block/Order/Item/Renderer/Default.php @@ -67,21 +67,9 @@ public function getOrderItem() public function getItemOptions() { $result = array(); - if ($options = $this->getOrderItem()->getProductOptions()) { if (isset($options['options'])) { - /** - * Remove html tags from option - */ - $productOptions = $options['options']; - if ($this->getPrintStatus()) { - foreach ($productOptions as &$option) { - if (isset($option['value'])) { - $option['value'] = strip_tags($option['value']); - } - } - } - $result = array_merge($result, $productOptions); + $result = array_merge($result, $options['options']); } if (isset($options['additional_options'])) { $result = array_merge($result, $options['additional_options']); @@ -90,17 +78,61 @@ public function getItemOptions() $result = array_merge($result, $options['attributes_info']); } } - return $result; } + /** + * Accept option value and return its formatted view + * + * @param mixed $optionValue + * Method works well with these $optionValue format: + * 1. String + * 2. Indexed array e.g. array(val1, val2, ...) + * 3. Associative array, containing additional option info, including option value, e.g. + * array + * ( + * [label] => ..., + * [value] => ..., + * [print_value] => ..., + * [option_id] => ..., + * [option_type] => ..., + * [custom_view] =>..., + * ) + * + * @return array + */ public function getFormatedOptionValue($optionValue) { - if (Mage::helper('catalog/product_options')->isHtmlFormattedOptionValue($optionValue)) { - return array('value' => $optionValue); + $optionInfo = array(); + + // define input data format + if (is_array($optionValue)) { + if (isset($optionValue['option_id'])) { + $optionInfo = $optionValue; + if (isset($optionInfo['value'])) { + $optionValue = $optionInfo['value']; + } + } elseif (isset($optionValue['value'])) { + $optionValue = $optionValue['value']; + } + } + + // render customized option view + if (isset($optionInfo['custom_view']) && $optionInfo['custom_view']) { + $_default = array('value' => $optionValue); + if (isset($optionInfo['option_type'])) { + try { + $group = Mage::getModel('catalog/product_option')->groupFactory($optionInfo['option_type']); + return array('value' => $group->getCustomizedView($optionInfo)); + } catch (Exception $e) { + return $_default; + } + } + return $_default; } - $formateOptionValue = array(); + // truncate standard view + $result = array(); if (is_array($optionValue)) { $_truncatedValue = implode("\n", $optionValue); $_truncatedValue = nl2br($_truncatedValue); @@ -110,17 +142,15 @@ public function getFormatedOptionValue($optionValue) $_truncatedValue = nl2br($_truncatedValue); } - $formateOptionValue = array( - 'value' => $_truncatedValue - ); + $result = array('value' => $_truncatedValue); if (Mage::helper('core/string')->strlen($optionValue) > 55) { - $formateOptionValue['value'] = $formateOptionValue['value'] . ' ...'; + $result['value'] = $result['value'] . ' ...'; $optionValue = nl2br($optionValue); - $formateOptionValue = array_merge($formateOptionValue, array('full_view' => $optionValue)); + $result = array_merge($result, array('full_view' => $optionValue)); } - return $formateOptionValue; + return $result; } /** diff --git a/app/code/core/Mage/Sales/Model/Mysql4/Quote.php b/app/code/core/Mage/Sales/Model/Mysql4/Quote.php index a45453f4c0..d0c1d90684 100644 --- a/app/code/core/Mage/Sales/Model/Mysql4/Quote.php +++ b/app/code/core/Mage/Sales/Model/Mysql4/Quote.php @@ -141,4 +141,24 @@ public function substractProductFromQuotes($product) ); } } + + /** + * Mark recollect contain product(s) quotes + * + * @param array|int $productIds + * @return Mage_Sales_Model_Mysql4_Quote + */ + public function markQuotesRecollect($productIds) + { + $this->_getWriteAdapter()->query(" + UPDATE `{$this->getTable('sales/quote')}` SET `trigger_recollect` = 1 + WHERE `entity_id` IN ( + SELECT DISTINCT `quote_id` + FROM `{$this->getTable('sales/quote_item')}` + WHERE `product_id` IN (?) + )", $productIds + ); + + return $this; + } } diff --git a/app/code/core/Mage/Sales/Model/Observer.php b/app/code/core/Mage/Sales/Model/Observer.php index 259e7a503e..69639d826c 100644 --- a/app/code/core/Mage/Sales/Model/Observer.php +++ b/app/code/core/Mage/Sales/Model/Observer.php @@ -20,12 +20,26 @@ * * @category Mage * @package Mage_Sales - * @copyright Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com) + * @copyright Copyright (c) 2009 Irubin Consulting Inc. DBA Varien (http://www.varien.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + +/** + * Sales observer + * + * @category Mage + * @package Mage_Sales + * @author Magento Core Team + */ class Mage_Sales_Model_Observer { + /** + * Clean expired quotes (cron process) + * + * @param Mage_Cron_Model_Schedule $schedule + * @return Mage_Sales_Model_Observer + */ public function cleanExpiredQuotes($schedule) { $lifetimes = Mage::getConfig()->getStoresConfigByPath('checkout/cart/delete_quote_after'); @@ -40,26 +54,68 @@ public function cleanExpiredQuotes($schedule) $quotes->addFieldToFilter('is_active', 0); $quotes->walk('delete'); } + return $this; } /** * When deleting product, substract it from all quotes quantities * * @throws Exception + * @param Varien_Event_Observer + * @return Mage_Sales_Model_Observer */ public function substractQtyFromQuotes($observer) { $product = $observer->getEvent()->getProduct(); Mage::getResourceSingleton('sales/quote')->substractProductFromQuotes($product); + return $this; } /** * When applying a catalog price rule, make related quotes recollect on demand * - * @param object $observer + * @param Varien_Event_Observer $observer + * @return Mage_Sales_Model_Observer */ public function markQuotesRecollectOnCatalogRules($observer) { Mage::getResourceSingleton('sales/quote')->markQuotesRecollectOnCatalogRules(); + return $this; + } + + /** + * Catalog Product After Save (change status process) + * + * @param Varien_Event_Observer $observer + * @return Mage_Sales_Model_Observer + */ + public function catalogProductSaveAfter(Varien_Event_Observer $observer) + { + $product = $observer->getEvent()->getProduct(); + if ($product->getStatus() == Mage_Catalog_Model_Product_Status::STATUS_ENABLED) { + return $this; + } + + Mage::getResourceSingleton('sales/quote')->markQuotesRecollect($product->getId()); + + return $this; + } + + /** + * Catalog Mass Status update process + * + * @param Varien_Event_Observer $observer + * @return Mage_Sales_Model_Observer + */ + public function catalogProductStatusUpdate(Varien_Event_Observer $observer) + { + $status = $observer->getEvent()->getStatus(); + if ($status == Mage_Catalog_Model_Product_Status::STATUS_ENABLED) { + return $this; + } + $productId = $observer->getEvent()->getProductId(); + Mage::getResourceSingleton('sales/quote')->markQuotesRecollect($productId); + + return $this; } -} \ No newline at end of file +} 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 67b0bc9c3f..6bbebd9b2e 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 @@ -63,7 +63,8 @@ public function draw() } // draw options value $this->_setFontRegular(); - foreach (Mage::helper('core/string')->str_split(strip_tags($option['value']), $x, true, true) as $_value) { + $_printValue = isset($option['print_value']) ? $option['print_value'] : strip_tags($option['value']); + foreach (Mage::helper('core/string')->str_split($_printValue, $x, true, true) as $_value) { $page->drawText($_value, $x + 5, $pdf->y - $shift[0], 'UTF-8'); $shift[0] += 10; } 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 8e7d4ddad5..3a1e692c60 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 @@ -64,7 +64,8 @@ public function draw() // draw options value $this->_setFontRegular(); if ($option['value']) { - $values = explode(', ', 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, 60,true,true) as $_value) { $page->drawText($_value, 40, $pdf->y-$shift[0], 'UTF-8'); 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 9d82822124..3b7a46f2f4 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 @@ -65,7 +65,8 @@ public function draw() // draw options value $this->_setFontRegular(); if ($option['value']) { - $values = explode(', ', 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, 60,true,true) as $_value) { $page->drawText($_value, 65, $pdf->y-$shift[0], 'UTF-8'); diff --git a/app/code/core/Mage/Sales/Model/Quote.php b/app/code/core/Mage/Sales/Model/Quote.php index 14ec2010a8..f9c00a5463 100644 --- a/app/code/core/Mage/Sales/Model/Quote.php +++ b/app/code/core/Mage/Sales/Model/Quote.php @@ -38,6 +38,13 @@ */ class Mage_Sales_Model_Quote extends Mage_Core_Model_Abstract { + /** + * Checkout methods + */ + const CHECKOUT_METHOD_REGISTER = 'register'; + const CHECKOUT_METHOD_GUEST = 'guest'; + const CHECKOUT_METHOD_LOGIN_IN = 'login_in'; + /** * Performance +30% without cache */ @@ -309,6 +316,19 @@ public function getCustomerTaxClassId() return $this->getData('customer_tax_class_id'); } + /** + * Return quote checkout method code + * + * @return string + */ + public function getCheckoutMethod() + { + if ($this->getCustomerId()) { + return self::CHECKOUT_METHOD_LOGIN_IN; + } + return $this->_getData('checkout_method'); + } + /** * Retrieve quote address collection * @@ -932,7 +952,7 @@ public function collectTotals() } /** - * Get all quote totals + * Get all quote totals (sorted by priority) * * @return array */ @@ -947,7 +967,15 @@ public function getTotals() $totals[$code] = $total; } } - return $totals; + + $sortedTotals = array(); + foreach ($this->getBillingAddress()->getTotalModels() as $total) { + /* @var $total Mage_Sales_Model_Quote_Address_Total_Abstract */ + if (isset($totals[$total->getCode()])) { + $sortedTotals[$total->getCode()] = $totals[$total->getCode()]; + } + } + return $sortedTotals; } public function addMessage($message, $index='error') diff --git a/app/code/core/Mage/Sales/Model/Quote/Address.php b/app/code/core/Mage/Sales/Model/Quote/Address.php index f62db7c659..65db30a32b 100644 --- a/app/code/core/Mage/Sales/Model/Quote/Address.php +++ b/app/code/core/Mage/Sales/Model/Quote/Address.php @@ -1,32 +1,37 @@ + */ class Mage_Sales_Model_Quote_Address extends Mage_Customer_Model_Address_Abstract { const TYPE_BILLING = 'billing'; @@ -40,9 +45,33 @@ class Mage_Sales_Model_Quote_Address extends Mage_Customer_Model_Address_Abstrac * @var Mage_Sales_Model_Quote */ protected $_items = null; + + /** + * Quote object + * + * @var Mage_Sales_Model_Quote + */ protected $_quote = null; + + /** + * Sales Quote address rates + * + * @var Mage_Sales_Model_Quote_Address_Rate + */ protected $_rates = null; + + /** + * Total models array + * + * @var array + */ protected $_totalModels; + + /** + * Total data as array + * + * @var array + */ protected $_totals = array(); /** @@ -234,14 +263,14 @@ public function getAllItems() * For virtual quote we assign all items to billing address */ if ($isQuoteVirtual) { - if ($this->getAddressType() == self::TYPE_BILLING) { - $items[] = $qItem; - } + if ($this->getAddressType() == self::TYPE_BILLING) { + $items[] = $qItem; + } } else { - if ($this->getAddressType() == self::TYPE_SHIPPING) { - $items[] = $qItem; - } + if ($this->getAddressType() == self::TYPE_SHIPPING) { + $items[] = $qItem; + } } } } @@ -249,6 +278,11 @@ public function getAllItems() return $items; } + /** + * Retrieve all visible items + * + * @return array + */ public function getAllVisibleItems() { $items = array(); @@ -260,7 +294,13 @@ public function getAllVisibleItems() return $items; } - public function getItemQty($itemId=0) + /** + * Retrieve item quantity by id + * + * @param int $itemId + * @return float|int + */ + public function getItemQty($itemId = 0) { if ($this->hasData('item_qty')) { return $this->getData('item_qty'); @@ -279,11 +319,22 @@ public function getItemQty($itemId=0) return $qty; } + /** + * Check Quote address has Items + * + * @return bool + */ public function hasItems() { return sizeof($this->getAllItems())>0; } + /** + * Retrieve Item object by id + * + * @param int $itemId + * @return Mage_Sales_Model_Quote_Address_Item + */ public function getItemById($itemId) { foreach ($this->getItemsCollection() as $item) { @@ -294,6 +345,12 @@ public function getItemById($itemId) return false; } + /** + * Retrieve item object by quote item Id + * + * @param int $itemId + * @return Mage_Sales_Model_Quote_Address_Item + */ public function getItemByQuoteItemId($itemId) { foreach ($this->getItemsCollection() as $item) { @@ -304,6 +361,12 @@ public function getItemByQuoteItemId($itemId) return false; } + /** + * Remove item from collection + * + * @param int $itemId + * @return Mage_Sales_Model_Quote_Address + */ public function removeItem($itemId) { if ($item = $this->getItemById($itemId)) { @@ -411,6 +474,13 @@ public function getGroupedAllShippingRates() return $rates; } + /** + * Sort rates recursive callback + * + * @param array $a + * @param array $b + * @return int + */ protected function _sortRates($a, $b) { if ((int)$a[0]->carrier_sort_order < (int)$b[0]->carrier_sort_order) { @@ -456,6 +526,11 @@ public function getShippingRateByCode($code) return false; } + /** + * Mark all shipping rates as deleted + * + * @return Mage_Sales_Model_Quote_Address + */ public function removeAllShippingRates() { foreach ($this->getShippingRatesCollection() as $rate) { @@ -464,6 +539,12 @@ public function removeAllShippingRates() return $this; } + /** + * Add shipping rate + * + * @param Mage_Sales_Model_Quote_Address_Rate $rate + * @return Mage_Sales_Model_Quote_Address + */ public function addShippingRate(Mage_Sales_Model_Quote_Address_Rate $rate) { $rate->setAddress($this); @@ -554,6 +635,11 @@ public function collectShippingRates() return $this; } + /** + * Retrieve total models + * + * @return array + */ public function getTotalModels() { if (!$this->_totalModels) { @@ -575,6 +661,11 @@ public function getTotalModels() return $this->_totalModels; } + /** + * Collect address totals + * + * @return Mage_Sales_Model_Quote_Address + */ public function collectTotals() { foreach ($this->getTotalModels() as $model) { @@ -585,6 +676,11 @@ public function collectTotals() return $this; } + /** + * Retrieve totals as array + * + * @return array + */ public function getTotals() { foreach ($this->getTotalModels() as $model) { @@ -595,6 +691,12 @@ public function getTotals() return $this->_totals; } + /** + * Add total data or model + * + * @param Mage_Sales_Model_Quote_Total|array $total + * @return Mage_Sales_Model_Quote_Address + */ public function addTotal($total) { if (is_array($total)) { @@ -607,11 +709,21 @@ public function addTotal($total) return $this; } + /** + * Rewrite clone method + * + * @return Mage_Sales_Model_Quote_Address + */ public function __clone() { $this->setId(null); } + /** + * Validate minimum amount + * + * @return bool + */ public function validateMinimumAmount() { $storeId = $this->getQuote()->getStoreId(); @@ -633,16 +745,34 @@ public function validateMinimumAmount() return true; } + /** + * Retrieve applied taxes + * + * @return array + */ public function getAppliedTaxes() { return unserialize($this->getData('applied_taxes')); } + /** + * Set applied taxes + * + * @param array $data + * @return Mage_Sales_Model_Quote_Address + */ public function setAppliedTaxes($data) { return $this->setData('applied_taxes', serialize($data)); } + /** + * Set shipping amount + * + * @param float $value + * @param bool $alreadyExclTax + * @return Mage_Sales_Model_Quote_Address + */ public function setShippingAmount($value, $alreadyExclTax = false) { if (Mage::helper('tax')->shippingPriceIncludesTax()) { @@ -655,6 +785,13 @@ public function setShippingAmount($value, $alreadyExclTax = false) return $this->setData('shipping_amount', $value); } + /** + * Set base shipping amount + * + * @param float $value + * @param bool $alreadyExclTax + * @return Mage_Sales_Model_Quote_Address + */ public function setBaseShippingAmount($value, $alreadyExclTax = false) { if (Mage::helper('tax')->shippingPriceIncludesTax()) { @@ -666,4 +803,4 @@ public function setBaseShippingAmount($value, $alreadyExclTax = false) } return $this->setData('base_shipping_amount', $value); } -} \ No newline at end of file +} diff --git a/app/code/core/Mage/Sales/Model/Quote/Address/Total/Abstract.php b/app/code/core/Mage/Sales/Model/Quote/Address/Total/Abstract.php index e392f3edfb..bf23aebc8d 100644 --- a/app/code/core/Mage/Sales/Model/Quote/Address/Total/Abstract.php +++ b/app/code/core/Mage/Sales/Model/Quote/Address/Total/Abstract.php @@ -20,35 +20,68 @@ * * @category Mage * @package Mage_Sales - * @copyright Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com) + * @copyright Copyright (c) 2009 Irubin Consulting Inc. DBA Varien (http://www.varien.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ +/** + * Sales Quote Address Total abstract model + * + * @category Mage + * @package Mage_Sales + * @author Magento Core Team + */ abstract class Mage_Sales_Model_Quote_Address_Total_Abstract { - protected $_code; - - public function setCode($code) - { - $this->_code = $code; - return $this; - } - - public function getCode() - { - return $this->_code; - } - + /** + * Total Code name + * + * @var string + */ + protected $_code; + + /** + * Set total code code name + * + * @param string $code + * @return Mage_Sales_Model_Quote_Address_Total_Abstract + */ + public function setCode($code) + { + $this->_code = $code; + return $this; + } + + /** + * Retrieve total code name + * + * @return unknown + */ + public function getCode() + { + return $this->_code; + } + + /** + * Collect totals process + * + * @param Mage_Sales_Model_Quote_Address $address + * @return Mage_Sales_Model_Quote_Address_Total_Abstract + */ public function collect(Mage_Sales_Model_Quote_Address $address) { return $this; } - + + /** + * Fetch (Retrieve data as array) + * + * @param Mage_Sales_Model_Quote_Address $address + * @return array + */ public function fetch(Mage_Sales_Model_Quote_Address $address) { - $arr = array(); - - return $arr; + return array(); } -} \ No newline at end of file +} diff --git a/app/code/core/Mage/Sales/Model/Quote/Payment.php b/app/code/core/Mage/Sales/Model/Quote/Payment.php index 0f6186099e..90559d07b3 100644 --- a/app/code/core/Mage/Sales/Model/Quote/Payment.php +++ b/app/code/core/Mage/Sales/Model/Quote/Payment.php @@ -74,6 +74,14 @@ public function getQuote() public function importData(array $data) { $data = new Varien_Object($data); + Mage::dispatchEvent( + $this->_eventPrefix . '_import_data_before', + array( + $this->_eventObject=>$this, + 'input'=>$data, + ) + ); + $this->setMethod($data->getMethod()); $method = $this->getMethodInstance(); diff --git a/app/code/core/Mage/Sales/etc/config.xml b/app/code/core/Mage/Sales/etc/config.xml index 1d8cac80c7..df2a3fe038 100644 --- a/app/code/core/Mage/Sales/etc/config.xml +++ b/app/code/core/Mage/Sales/etc/config.xml @@ -28,7 +28,7 @@ - 0.9.32 + 0.9.35 @@ -826,6 +826,24 @@
+ + + + singleton + sales/observer + catalogProductSaveAfter + + + + + + + singleton + sales/observer + catalogProductStatusUpdate + + + diff --git a/app/code/core/Mage/Sales/etc/wsdl.xml b/app/code/core/Mage/Sales/etc/wsdl.xml index afa09729cb..60cc48c4e4 100644 --- a/app/code/core/Mage/Sales/etc/wsdl.xml +++ b/app/code/core/Mage/Sales/etc/wsdl.xml @@ -177,7 +177,7 @@ - + diff --git a/app/code/core/Mage/Sales/sql/sales_setup/mysql4-upgrade-0.9.32-0.9.33.php b/app/code/core/Mage/Sales/sql/sales_setup/mysql4-upgrade-0.9.32-0.9.33.php new file mode 100644 index 0000000000..c6e5a7dd0f --- /dev/null +++ b/app/code/core/Mage/Sales/sql/sales_setup/mysql4-upgrade-0.9.32-0.9.33.php @@ -0,0 +1,34 @@ +startSetup(); + +$installer->addAttribute('order', 'ext_order_item_id', array('type' => 'varchar')); + +$installer->endSetup(); diff --git a/app/code/core/Mage/Sales/sql/sales_setup/mysql4-upgrade-0.9.33-0.9.34.php b/app/code/core/Mage/Sales/sql/sales_setup/mysql4-upgrade-0.9.33-0.9.34.php new file mode 100644 index 0000000000..723b148380 --- /dev/null +++ b/app/code/core/Mage/Sales/sql/sales_setup/mysql4-upgrade-0.9.33-0.9.34.php @@ -0,0 +1,34 @@ +startSetup(); + +$installer->addAttribute('quote', 'ext_shipping_info', array('type' => 'text')); + +$installer->endSetup(); diff --git a/app/code/core/Mage/Sales/sql/sales_setup/mysql4-upgrade-0.9.34-0.9.35.php b/app/code/core/Mage/Sales/sql/sales_setup/mysql4-upgrade-0.9.34-0.9.35.php new file mode 100644 index 0000000000..61b1273c95 --- /dev/null +++ b/app/code/core/Mage/Sales/sql/sales_setup/mysql4-upgrade-0.9.34-0.9.35.php @@ -0,0 +1,34 @@ +startSetup(); +$installer->removeAttribute('order', 'ext_order_item_id'); +$installer->addAttribute('order_item', 'ext_order_item_id', array('type' => 'varchar')); + +$installer->endSetup(); diff --git a/app/code/core/Mage/Shipping/Block/Tracking/Popup.php b/app/code/core/Mage/Shipping/Block/Tracking/Popup.php index 41b7c80d16..7f86240ce6 100644 --- a/app/code/core/Mage/Shipping/Block/Tracking/Popup.php +++ b/app/code/core/Mage/Shipping/Block/Tracking/Popup.php @@ -166,7 +166,7 @@ public function getTrackingInfoByTrackId() */ public function formatDeliveryDateTime($date,$time) { - return Mage::app()->getLocale()->date(strtotime($date.' '.$time),Zend_Date::TIMESTAMP)->toString('MM/dd/YYYY hh:mm a'); + return Mage::app()->getLocale()->date(strtotime($date.' '.$time),Zend_Date::TIMESTAMP, null, false)->toString('MM/dd/YYYY hh:mm a'); } /* @@ -174,7 +174,7 @@ public function formatDeliveryDateTime($date,$time) */ public function formatDeliveryDate($date) { - return Mage::app()->getLocale()->date(strtotime($date),Zend_Date::TIMESTAMP)->toString('MM/dd/YYYY'); + return Mage::app()->getLocale()->date(strtotime($date),Zend_Date::TIMESTAMP, null, false)->toString('MM/dd/YYYY'); } /* @@ -185,7 +185,7 @@ public function formatDeliveryTime($time, $date = null) if (!empty($date)) { $time = $date.' '.$time; } - return Mage::app()->getLocale()->date(strtotime($time),Zend_Date::TIMESTAMP)->toString('hh:mm a'); + return Mage::app()->getLocale()->date(strtotime($time),Zend_Date::TIMESTAMP, null, false)->toString('hh:mm a'); } public function getStoreSupportEmail() diff --git a/app/code/core/Mage/Shipping/etc/system.xml b/app/code/core/Mage/Shipping/etc/system.xml index 858e5bdbae..bba93d97b6 100644 --- a/app/code/core/Mage/Shipping/etc/system.xml +++ b/app/code/core/Mage/Shipping/etc/system.xml @@ -116,7 +116,7 @@ 320 1 1 - 0 + 1 @@ -133,7 +133,7 @@ 1 1 1 - 1 + 0 @@ -149,7 +149,7 @@ 5 1 1 - 1 + 0 @@ -166,7 +166,7 @@ 8 1 1 - 1 + 0 @@ -174,7 +174,7 @@ 100 1 1 - 1 + 0 <label>Title</label> @@ -191,7 +191,7 @@ <sort_order>4</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </type> <sallowspecific translate="label"> <label>Ship to applicable countries</label> @@ -201,7 +201,7 @@ <source_model>adminhtml/system_config_source_shipping_allspecificcountries</source_model> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </sallowspecific> <specificcountry translate="label"> <label>Ship to Specific countries</label> @@ -210,7 +210,7 @@ <source_model>adminhtml/system_config_source_country</source_model> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </specificcountry> <showmethod translate="label"> <label>Show method if not applicable</label> @@ -219,7 +219,7 @@ <source_model>adminhtml/system_config_source_yesno</source_model> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </showmethod> <specificerrmsg translate="label"> <label>Displayed Error Message</label> @@ -246,7 +246,7 @@ <sort_order>1</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </active> <free_shipping_subtotal translate="label"> <label>Minimum order amount</label> @@ -254,7 +254,7 @@ <sort_order>4</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </free_shipping_subtotal> <name translate="label"> <label>Method name</label> @@ -270,7 +270,7 @@ <sort_order>100</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </sort_order> <title translate="label"> <label>Title</label> @@ -288,7 +288,7 @@ <source_model>adminhtml/system_config_source_shipping_allspecificcountries</source_model> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </sallowspecific> <specificcountry translate="label"> <label>Ship to Specific countries</label> @@ -297,7 +297,7 @@ <source_model>adminhtml/system_config_source_country</source_model> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </specificcountry> <showmethod translate="label"> <label>Show method if not applicable</label> @@ -306,7 +306,7 @@ <source_model>adminhtml/system_config_source_yesno</source_model> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </showmethod> <specificerrmsg translate="label"> <label>Displayed Error Message</label> @@ -341,7 +341,7 @@ <sort_order>8</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </handling_fee> <active translate="label"> <label>Enabled</label> @@ -350,7 +350,7 @@ <sort_order>1</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </active> <condition_name translate="label"> <label>Condition</label> @@ -392,7 +392,7 @@ <sort_order>100</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </sort_order> <title translate="label"> <label>Title</label> @@ -410,7 +410,7 @@ <source_model>adminhtml/system_config_source_shipping_allspecificcountries</source_model> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </sallowspecific> <specificcountry translate="label"> <label>Ship to Specific countries</label> @@ -419,7 +419,7 @@ <source_model>adminhtml/system_config_source_country</source_model> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </specificcountry> <showmethod translate="label"> <label>Show method if not applicable</label> @@ -428,7 +428,7 @@ <source_model>adminhtml/system_config_source_yesno</source_model> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </showmethod> <specificerrmsg translate="label"> <label>Displayed Error Message</label> diff --git a/app/code/core/Mage/Tag/Block/Product/Result.php b/app/code/core/Mage/Tag/Block/Product/Result.php index 15077b6ae6..40a129b09e 100644 --- a/app/code/core/Mage/Tag/Block/Product/Result.php +++ b/app/code/core/Mage/Tag/Block/Product/Result.php @@ -84,6 +84,7 @@ protected function _getProductCollection() ->addAttributeToSelect(Mage::getSingleton('catalog/config')->getProductAttributes()) ->addTagFilter($this->getTag()->getId()) ->addStoreFilter() + ->addMinimalPrice() ->addUrlRewrite(); Mage::getSingleton('catalog/product_status')->addSaleableFilterToCollection($this->_productCollection); Mage::getSingleton('catalog/product_visibility')->addVisibleInCatalogFilterToCollection($this->_productCollection); diff --git a/app/code/core/Mage/Usa/Model/Shipping/Carrier/Ups.php b/app/code/core/Mage/Usa/Model/Shipping/Carrier/Ups.php index 1acf5b80a8..e0293ec3b1 100644 --- a/app/code/core/Mage/Usa/Model/Shipping/Carrier/Ups.php +++ b/app/code/core/Mage/Usa/Model/Shipping/Carrier/Ups.php @@ -56,7 +56,6 @@ public function collectRates(Mage_Shipping_Model_Rate_Request $request) $this->setRequest($request); $this->_result = $this->_getQuotes(); - $this->_updateFreeMethodQuote($request); return $this->getResult(); @@ -261,8 +260,9 @@ protected function _parseCgiResponse($response) switch (substr($r[0],-1)) { case 3: case 4: if (in_array($r[1], $allowedMethods)) { - $costArr[$r[1]] = $r[8]; - $priceArr[$r[1]] = $this->getMethodPrice($r[8], $r[1]); + $responsePrice = Mage::app()->getLocale()->getNumber($r[8]); + $costArr[$r[1]] = $responsePrice; + $priceArr[$r[1]] = $this->getMethodPrice($responsePrice, $r[1]); } break; case 5: @@ -270,8 +270,9 @@ protected function _parseCgiResponse($response) break; case 6: if (in_array($r[3], $allowedMethods)) { - $costArr[$r[3]] = $r[10]; - $priceArr[$r[3]] = $this->getMethodPrice($r[10], $r[3]); + $responsePrice = Mage::app()->getLocale()->getNumber($r[10]); + $costArr[$r[3]] = $responsePrice; + $priceArr[$r[3]] = $this->getMethodPrice($responsePrice, $r[3]); } break; } diff --git a/app/code/core/Mage/Usa/etc/system.xml b/app/code/core/Mage/Usa/etc/system.xml index ec0aa10e17..2525a55621 100644 --- a/app/code/core/Mage/Usa/etc/system.xml +++ b/app/code/core/Mage/Usa/etc/system.xml @@ -43,7 +43,7 @@ <sort_order>7</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </account> <active translate="label"> <label>Enabled</label> @@ -52,7 +52,7 @@ <sort_order>1</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </active> <allowed_methods translate="label"> <label>Allowed methods</label> @@ -61,7 +61,7 @@ <sort_order>17</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </allowed_methods> <contentdesc translate="label"> <label>Package Description</label> @@ -69,7 +69,7 @@ <sort_order>12</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </contentdesc> <!-- If the free_shipping_enable flag enable, the system will check free_shipping_subtotal to give free shipping @@ -82,7 +82,7 @@ <sort_order>121</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </free_shipping_enable> <free_shipping_subtotal translate="label"> <label>Minimum order amount for free shipping</label> @@ -90,7 +90,7 @@ <sort_order>122</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </free_shipping_subtotal> <dutiable translate="label"> <label>Shipment Dutiable</label> @@ -99,7 +99,7 @@ <sort_order>13</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </dutiable> <dutypaymenttype translate="label"> <label>Shipment Duty Payment Type</label> @@ -108,7 +108,7 @@ <sort_order>14</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </dutypaymenttype> <free_method translate="label"> <label>Free method</label> @@ -118,7 +118,7 @@ <sort_order>120</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </free_method> <gateway_url translate="label"> <label>Gateway URL</label> @@ -126,7 +126,7 @@ <sort_order>2</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </gateway_url> <handling_type translate="label"> <label>Calculate Handling Fee</label> @@ -152,7 +152,7 @@ <sort_order>12</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </handling_fee> <max_package_weight translate="label"> <label>Maximum Package Weight (Please consult your shipping carrier for maximum supported shipping weight)</label> @@ -160,7 +160,7 @@ <sort_order>13</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </max_package_weight> <id translate="label"> <label>Access ID</label> @@ -169,7 +169,7 @@ <sort_order>5</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </id> <password translate="label"> <label>Password</label> @@ -178,7 +178,7 @@ <sort_order>6</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </password> <shipment_type translate="label"> <label>Shipment type</label> @@ -187,7 +187,7 @@ <sort_order>9</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </shipment_type> <shipping_intlkey translate="label"> <label>Shipping key (International)</label> @@ -196,7 +196,7 @@ <sort_order>8</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </shipping_intlkey> <shipping_key translate="label"> <label>Shipping key</label> @@ -205,7 +205,7 @@ <sort_order>8</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </shipping_key> <sort_order translate="label"> <label>Sort order</label> @@ -213,7 +213,7 @@ <sort_order>200</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </sort_order> <title translate="label"> <label>Title</label> @@ -231,7 +231,7 @@ <source_model>adminhtml/system_config_source_shipping_allspecificcountries</source_model> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </sallowspecific> <specificcountry translate="label"> <label>Ship to Specific countries</label> @@ -240,7 +240,7 @@ <source_model>adminhtml/system_config_source_country</source_model> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </specificcountry> <showmethod translate="label"> <label>Show method if not applicable</label> @@ -249,7 +249,7 @@ <source_model>adminhtml/system_config_source_yesno</source_model> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </showmethod> <specificerrmsg translate="label"> <label>Displayed Error Message</label> @@ -267,7 +267,7 @@ <sort_order>130</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </additional_protection_enabled> <additional_protection_min_value translate="label"> <label>Additional Protection Min Subtotal</label> @@ -275,7 +275,7 @@ <sort_order>131</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </additional_protection_min_value> <additional_protection_use_subtotal translate="label"> <label>Additional Protection Value</label> @@ -284,7 +284,7 @@ <sort_order>132</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </additional_protection_use_subtotal> <additional_protection_value translate="label comment"> <label>Additional Protection Configuration Value</label> @@ -293,7 +293,7 @@ <sort_order>133</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </additional_protection_value> <additional_protection_rounding translate="label comment"> @@ -303,7 +303,7 @@ <sort_order>134</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </additional_protection_rounding> <hazardous_materials translate="label"> @@ -313,7 +313,7 @@ <sort_order>135</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </hazardous_materials> <default_length translate="label"> @@ -322,7 +322,7 @@ <sort_order>136</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </default_length> <default_width translate="label"> <label>Default Package Width</label> @@ -330,7 +330,7 @@ <sort_order>137</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </default_width> <default_height translate="label"> <label>Default Package Height</label> @@ -338,7 +338,7 @@ <sort_order>138</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </default_height> <shipment_days translate="label"> @@ -348,7 +348,7 @@ <sort_order>139</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </shipment_days> <intl_shipment_days translate="label"> @@ -358,7 +358,7 @@ <sort_order>140</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </intl_shipment_days> </fields> @@ -378,7 +378,7 @@ <sort_order>3</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> <comment>Please make sure to use only digits here. No dashes are allowed.</comment> </account> <active translate="label"> @@ -388,7 +388,7 @@ <sort_order>1</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </active> <allowed_methods translate="label"> <label>Allowed methods</label> @@ -397,7 +397,7 @@ <sort_order>17</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </allowed_methods> <free_shipping_enable translate="label"> <label>Free shipping with minimum order amount</label> @@ -406,7 +406,7 @@ <sort_order>21</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </free_shipping_enable> <free_shipping_subtotal translate="label"> <label>Minimum order amount for free shipping</label> @@ -414,7 +414,7 @@ <sort_order>22</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </free_shipping_subtotal> <dropoff translate="label"> <label>Dropoff</label> @@ -423,7 +423,7 @@ <sort_order>5</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </dropoff> <free_method translate="label"> <label>Free method</label> @@ -433,7 +433,7 @@ <sort_order>20</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </free_method> <gateway_url translate="label"> <label>Gateway URL</label> @@ -441,7 +441,7 @@ <sort_order>2</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </gateway_url> <handling_type translate="label"> <label>Calculate Handling Fee</label> @@ -467,7 +467,7 @@ <sort_order>8</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </handling_fee> <max_package_weight translate="label"> <label>Maximum Package Weight (Please consult your shipping carrier for maximum supported shipping weight)</label> @@ -475,7 +475,7 @@ <sort_order>5</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </max_package_weight> <packaging translate="label"> <label>Packaging</label> @@ -484,7 +484,7 @@ <sort_order>4</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </packaging> <sort_order translate="label"> <label>Sort order</label> @@ -492,7 +492,7 @@ <sort_order>100</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </sort_order> <title translate="label"> <label>Title</label> @@ -509,7 +509,7 @@ <source_model>adminhtml/system_config_source_yesno</source_model> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </residence_delivery> <sallowspecific translate="label"> <label>Ship to applicable countries</label> @@ -519,7 +519,7 @@ <source_model>adminhtml/system_config_source_shipping_allspecificcountries</source_model> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </sallowspecific> <specificcountry translate="label"> <label>Ship to Specific countries</label> @@ -528,7 +528,7 @@ <source_model>adminhtml/system_config_source_country</source_model> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </specificcountry> <showmethod translate="label"> <label>Show method if not applicable</label> @@ -537,7 +537,7 @@ <source_model>adminhtml/system_config_source_yesno</source_model> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </showmethod> <specificerrmsg translate="label"> <label>Displayed Error Message</label> @@ -564,7 +564,7 @@ <sort_order>3</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </access_license_number> <active translate="label"> <label>Enabled</label> @@ -573,7 +573,7 @@ <sort_order>1</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </active> <allowed_methods translate="label"> <label>Allowed methods</label> @@ -582,7 +582,7 @@ <sort_order>17</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </allowed_methods> <container translate="label"> <label>Container</label> @@ -591,7 +591,7 @@ <sort_order>5</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </container> <free_shipping_enable translate="label"> <label>Free shipping with minimum order amount</label> @@ -600,7 +600,7 @@ <sort_order>21</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </free_shipping_enable> <free_shipping_subtotal translate="label"> <label>Minimum order amount for free shipping</label> @@ -608,7 +608,7 @@ <sort_order>22</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </free_shipping_subtotal> <dest_type translate="label"> <label>Destination type</label> @@ -617,7 +617,7 @@ <sort_order>6</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </dest_type> <free_method translate="label"> <label>Free method</label> @@ -627,7 +627,7 @@ <sort_order>20</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </free_method> <gateway_url translate="label"> <label>Gateway URL</label> @@ -635,7 +635,7 @@ <sort_order>4</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </gateway_url> <gateway_xml_url translate="label"> <label>Gateway XML URL</label> @@ -643,7 +643,7 @@ <sort_order>3</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </gateway_xml_url> <handling_type translate="label"> <label>Calculate Handling Fee</label> @@ -669,7 +669,7 @@ <sort_order>13</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </handling_fee> <max_package_weight translate="label"> <label>Maximum Package Weight (Please consult your shipping carrier for maximum supported shipping weight)</label> @@ -677,7 +677,7 @@ <sort_order>8</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </max_package_weight> <origin_shipment translate="label"> <label>Origin of the shipment</label> @@ -686,7 +686,7 @@ <sort_order>3</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </origin_shipment> <password translate="label"> <label>Password</label> @@ -695,7 +695,7 @@ <sort_order>3</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </password> <pickup translate="label"> <label>Pickup method</label> @@ -704,7 +704,7 @@ <sort_order>8</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </pickup> <sort_order translate="label"> <label>Sort order</label> @@ -712,7 +712,7 @@ <sort_order>100</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </sort_order> <title translate="label"> <label>Title</label> @@ -728,7 +728,7 @@ <sort_order>6</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </tracking_xml_url> <type translate="label"> <label>UPS type</label> @@ -737,7 +737,7 @@ <sort_order>2</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </type> <unit_of_measure translate="label"> <label>Weight Unit</label> @@ -746,7 +746,7 @@ <sort_order>6</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </unit_of_measure> <username translate="label"> <label>UserId</label> @@ -755,7 +755,7 @@ <sort_order>3</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </username> <negotiated_active translate="label"> <label>Enable Negotiated Rates</label> @@ -764,7 +764,7 @@ <sort_order>4</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </negotiated_active> <shipper_number translate="label"> <label>Shipper Number</label> @@ -772,7 +772,7 @@ <sort_order>5</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> <comment>Required for negotiated rates; 6-character UPS</comment> </shipper_number> <sallowspecific translate="label"> @@ -783,7 +783,7 @@ <source_model>adminhtml/system_config_source_shipping_allspecificcountries</source_model> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </sallowspecific> <specificcountry translate="label"> <label>Ship to Specific countries</label> @@ -792,7 +792,7 @@ <source_model>adminhtml/system_config_source_country</source_model> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </specificcountry> <showmethod translate="label"> <label>Show method if not applicable</label> @@ -801,7 +801,7 @@ <source_model>adminhtml/system_config_source_yesno</source_model> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </showmethod> <specificerrmsg translate="label"> <label>Displayed Error Message</label> @@ -828,7 +828,7 @@ <sort_order>1</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </active> <allowed_methods translate="label"> <label>Allowed methods</label> @@ -837,7 +837,7 @@ <sort_order>17</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </allowed_methods> <container translate="label"> <label>Container</label> @@ -846,7 +846,7 @@ <sort_order>4</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </container> <free_shipping_enable translate="label"> <label>Free shipping with minimum order amount</label> @@ -855,7 +855,7 @@ <sort_order>21</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </free_shipping_enable> <free_shipping_subtotal translate="label"> <label>Minimum order amount for free shipping</label> @@ -863,7 +863,7 @@ <sort_order>22</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </free_shipping_subtotal> <free_method translate="label"> <label>Free method</label> @@ -873,7 +873,7 @@ <sort_order>20</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </free_method> <gateway_url translate="label"> <label>Gateway URL</label> @@ -881,7 +881,7 @@ <sort_order>2</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </gateway_url> <handling_type translate="label"> <label>Calculate Handling Fee</label> @@ -907,7 +907,7 @@ <sort_order>13</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </handling_fee> <max_package_weight translate="label"> <label>Maximum Package Weight (Please consult your shipping carrier for maximum supported shipping weight)</label> @@ -915,7 +915,7 @@ <sort_order>8</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </max_package_weight> <machinable translate="label"> <label>Machinable</label> @@ -924,7 +924,7 @@ <sort_order>6</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </machinable> <size translate="label"> <label>Size</label> @@ -933,7 +933,7 @@ <sort_order>5</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </size> <sort_order translate="label"> <label>Sort order</label> @@ -941,7 +941,7 @@ <sort_order>100</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </sort_order> <title translate="label"> <label>Title</label> @@ -958,7 +958,7 @@ <sort_order>3</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </userid> <sallowspecific translate="label"> <label>Ship to applicable countries</label> @@ -968,7 +968,7 @@ <source_model>adminhtml/system_config_source_shipping_allspecificcountries</source_model> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </sallowspecific> <specificcountry translate="label"> <label>Ship to Specific countries</label> @@ -977,7 +977,7 @@ <source_model>adminhtml/system_config_source_country</source_model> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </specificcountry> <showmethod translate="label"> <label>Show method if not applicable</label> @@ -986,7 +986,7 @@ <source_model>adminhtml/system_config_source_yesno</source_model> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> - <show_in_store>1</show_in_store> + <show_in_store>0</show_in_store> </showmethod> <specificerrmsg translate="label"> <label>Displayed Error Message</label> diff --git a/app/design/adminhtml/default/default/layout/main.xml b/app/design/adminhtml/default/default/layout/main.xml index 028d82c70d..e02882287e 100644 --- a/app/design/adminhtml/default/default/layout/main.xml +++ b/app/design/adminhtml/default/default/layout/main.xml @@ -74,7 +74,7 @@ Default layout, loads most of the pages <action method="addCss"><name>reset.css</name></action> <action method="addCss"><name>boxes.css</name></action> - <action method="addItem"><type>skin_css</type><name>iestyles.css</name><params/><if>IE</if></action> + <action method="addItem"><type>skin_css</type><name>iestyles.css</name><params/><if>lt IE 8</if></action> <action method="addItem"><type>skin_css</type><name>below_ie7.css</name><params/><if>lt IE 7</if></action> <action method="addItem"><type>skin_css</type><name>ie7.css</name><params/><if>IE 7</if></action> diff --git a/app/design/adminhtml/default/default/template/bundle/sales/creditmemo/create/items/renderer.phtml b/app/design/adminhtml/default/default/template/bundle/sales/creditmemo/create/items/renderer.phtml index 89e4e58c21..33621f4cfd 100644 --- a/app/design/adminhtml/default/default/template/bundle/sales/creditmemo/create/items/renderer.phtml +++ b/app/design/adminhtml/default/default/template/bundle/sales/creditmemo/create/items/renderer.phtml @@ -1,395 +1,409 @@ -<?php -/** - * 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_default - * @package Mage_Bundle - * @copyright Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com) - * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) - */ -?> -<?php -/** - * @see Mage_Bundle_Block_Adminhtml_Sales_Order_Items_Renderer - */ -?> - -<?php $_item = $this->getItem() ?> -<?php $items = $this->getChilds($_item); ?> -<?php $_count = count ($items) ?> -<?php $_index = 0 ?> - -<?php $_prevOptionId = '' ?> - -<?php if($this->getOrderOptions() || $_item->getDescription()): ?> - <?php $_showlastRow = true ?> -<?php else: ?> - <?php $_showlastRow = false ?> -<?php endif; ?> - -<?php foreach ($items as $_item): ?> - <?php $this->setPriceDataObject($_item) ?> - <?php $attributes = $this->getSelectionAttributes($_item) ?> - <?php if ($_item->getOrderItem()->getParentItem()): ?> - <?php if ($_prevOptionId != $attributes['option_id']): ?> - <tr> - <td><div class="option-label"><?php echo $attributes['option_label'] ?></div></td> - <td> </td> - <td> </td> - <td> </td> - <td> </td> - <td> </td> - <td> </td> - <td> </td> - <td class="last"> </td> - </tr> - <?php $_prevOptionId = $attributes['option_id'] ?> - <?php endif; ?> - <?php endif; ?> - <tr<?php echo (++$_index==$_count && !$_showlastRow)?' class="border"':'' ?>> - <?php if (!$_item->getOrderItem()->getParentItem()): ?> - <td><h5 class="title"><?php echo $this->htmlEscape($_item->getName()) ?></h5> - <div> - <strong><?php echo $this->helper('sales')->__('SKU') ?>:</strong> - <?php echo implode('<br />', Mage::helper('catalog')->splitSku($_item->getSku())); ?> - </div> - </td> - <?php else: ?> - <td><div class="option-value"><?php echo $this->getValueHtml($_item)?></div></td> - <?php endif; ?> - <td class="a-right"> - <?php if ($this->canShowPriceInfo($_item)): ?> - <?php if ($this->helper('tax')->displayCartBothPrices() || $this->helper('tax')->displayCartPriceExclTax()): ?> - <span class="price-excl-tax"> - <?php if ($this->helper('tax')->displayCartBothPrices()): ?> - <span class="label"><?php echo $this->__('Excl. Tax'); ?>:</span> - <?php endif; ?> - - <?php if (Mage::helper('weee')->typeOfDisplay($_item, array(0, 1, 4), 'sales')): ?> - <?php - echo $this->displayPrices( - $_item->getBasePrice()+$_item->getBaseWeeeTaxAppliedAmount()+$_item->getBaseWeeeTaxDisposition(), - $_item->getPrice()+$_item->getWeeeTaxAppliedAmount()+$_item->getWeeeTaxDisposition() - ); - ?> - <?php else: ?> - <?php echo $this->displayPrices($_item->getBasePrice(), $_item->getPrice()) ?> - <?php endif; ?> - - - <?php if (Mage::helper('weee')->getApplied($_item)): ?> - <br /> - <?php if (Mage::helper('weee')->typeOfDisplay($_item, 1, 'sales')): ?> - <small> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount'], $tax['amount']); ?></span> - <?php endforeach; ?> - </small> - <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><small><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount_incl_tax'], $tax['amount_incl_tax']); ?></small></span> - <?php endforeach; ?> - <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 4, 'sales')): ?> - <small> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount_incl_tax'], $tax['amount_incl_tax']); ?></span> - <?php endforeach; ?> - </small> - <?php endif; ?> - - <?php if (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> - <br /> - <span class="nobr"><?php echo Mage::helper('weee')->__('Total'); ?>:<br /> - <?php - echo $this->displayPrices( - $_item->getBasePrice()+$_item->getBaseWeeeTaxAppliedAmount()+$_item->getBaseWeeeTaxDisposition(), - $_item->getPrice()+$_item->getWeeeTaxAppliedAmount()+$_item->getWeeeTaxDisposition() - ); - ?> - </span> - <?php endif; ?> - <?php endif; ?> - </span> - <br /> - <?php endif; ?> - <?php if ($this->helper('tax')->displayCartBothPrices() || $this->helper('tax')->displayCartPriceInclTax()): ?> - <span class="price-incl-tax"> - <?php if ($this->helper('tax')->displayCartBothPrices()): ?> - <span class="label"><?php echo $this->__('Incl. Tax'); ?>:</span> - <?php endif; ?> - <?php $_incl = $this->helper('checkout')->getPriceInclTax($_item); ?> - <?php $_baseIncl = $this->helper('checkout')->getBasePriceInclTax($_item); ?> - - <?php if (Mage::helper('weee')->typeOfDisplay($_item, array(0, 1, 4), 'sales')): ?> - <?php echo $this->displayPrices($_baseIncl+$_item->getBaseWeeeTaxAppliedAmount(), $_incl+$_item->getWeeeTaxAppliedAmount()); ?> - <?php else: ?> - <?php echo $this->displayPrices($_baseIncl-$_item->getBaseWeeeTaxDisposition(), $_incl-$_item->getWeeeTaxDisposition()) ?> - <?php endif; ?> - - <?php if (Mage::helper('weee')->getApplied($_item)): ?> - <br /> - <?php if (Mage::helper('weee')->typeOfDisplay($_item, 1, 'sales')): ?> - <small> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount'], $tax['amount']); ?></span> - <?php endforeach; ?> - </small> - <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><small><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount_incl_tax'], $tax['amount_incl_tax']); ?></small></span> - <?php endforeach; ?> - <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 4, 'sales')): ?> - <small> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount_incl_tax'], $tax['amount_incl_tax']); ?></span> - <?php endforeach; ?> - </small> - <?php endif; ?> - - <?php if (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> - <br /> - <span class="nobr"><?php echo Mage::helper('weee')->__('Total'); ?>:<br /> <?php echo $this->displayPrices($_baseIncl+$_item->getBaseWeeeTaxAppliedAmount(), $_incl+$_item->getWeeeTaxAppliedAmount()); ?></span> - <?php endif; ?> - <?php endif; ?> - </span> - <?php endif; ?> - <?php else: ?> -   - <?php endif; ?> - </td> - <td> - <?php if ($this->canShowPriceInfo($_item)): ?> - <table cellspacing="0" class="qty-table"> - <tr> - <td><?php echo Mage::helper('sales')->__('Ordered') ?></td> - <td><strong><?php echo $_item->getOrderItem()->getQtyOrdered()*1 ?></strong></td> - </tr> - <?php if ((float) $_item->getOrderItem()->getQtyInvoiced()): ?> - <tr> - <td><?php echo Mage::helper('sales')->__('Invoiced') ?></td> - <td><strong><?php echo $_item->getOrderItem()->getQtyInvoiced()*1 ?></strong></td> - </tr> - <?php endif; ?> - <?php if ((float) $_item->getOrderItem()->getQtyShipped() && $this->isShipmentSeparately($_item)): ?> - <tr> - <td><?php echo Mage::helper('sales')->__('Shipped') ?></td> - <td><strong><?php echo $_item->getOrderItem()->getQtyShipped()*1 ?></strong></td> - </tr> - <?php endif; ?> - <?php if ((float) $_item->getOrderItem()->getQtyRefunded()): ?> - <tr> - <td><?php echo Mage::helper('sales')->__('Refunded') ?></td> - <td><strong><?php echo $_item->getOrderItem()->getQtyRefunded()*1 ?></strong></td> - </tr> - <?php endif; ?> - <?php if ((float) $_item->getOrderItem()->getQtyCanceled()): ?> - <tr> - <td><?php echo Mage::helper('sales')->__('Canceled') ?></td> - <td><strong><?php echo $_item->getOrderItem()->getQtyCanceled()*1 ?></strong></td> - </tr> - <?php endif; ?> - </table> - <?php elseif ($this->isShipmentSeparately($_item)): ?> - <table cellspacing="0" class="qty-table"> - <tr> - <td><?php echo Mage::helper('sales')->__('Ordered') ?></td> - <td><strong><?php echo $_item->getOrderItem()->getQtyOrdered()*1 ?></strong></td> - </tr> - <?php if ((float) $_item->getOrderItem()->getQtyShipped()): ?> - <tr> - <td><?php echo Mage::helper('sales')->__('Shipped') ?></td> - <td><strong><?php echo $_item->getOrderItem()->getQtyShipped()*1 ?></strong></td> - </tr> - <?php endif; ?> - </table> - <?php else: ?> -   - <?php endif; ?> - </td> - <td class="a-center"> - <?php if ($this->canShowPriceInfo($_item)): ?> - <?php if ($this->canReturnToStock()) : ?> - <input type="checkbox" name="creditmemo[items][<?php echo $_item->getOrderItemId() ?>][back_to_stock]" value="1"<?php if ($_item->getBackToStock()):?> checked="checked"<?php endif;?> /> - <?php endif; ?> - <?php else: ?> -   - <?php endif; ?> - </td> - <td class="a-center"> - <?php if ($this->canShowPriceInfo($_item)): ?> - <?php if ($this->canEditQty()) : ?> - <input type="text" class="input-text qty-input" name="creditmemo[items][<?php echo $_item->getOrderItemId() ?>][qty]" value="<?php echo $_item->getQty()*1 ?>" /> - <?php else: ?> - <?php echo $_item->getQty()*1 ?> - <?php endif; ?> - <?php else: ?> -   - <?php endif; ?> - </td> - <td class="a-right"> - <?php if ($this->canShowPriceInfo($_item)): ?> - <?php if ($this->helper('tax')->displayCartBothPrices() || $this->helper('tax')->displayCartPriceExclTax()): ?> - <span class="price-excl-tax"> - <?php if ($this->helper('tax')->displayCartBothPrices()): ?> - <span class="label"><?php echo $this->__('Excl. Tax'); ?>:</span> - <?php endif; ?> - - <?php if (Mage::helper('weee')->typeOfDisplay($_item, array(0, 1, 4), 'sales')): ?> - <?php - echo $this->displayPrices( - $_item->getBaseRowTotal()+$_item->getBaseWeeeTaxAppliedRowAmount()+$_item->getBaseWeeeTaxRowDisposition(), - $_item->getRowTotal()+$_item->getWeeeTaxAppliedRowAmount()+$_item->getWeeeTaxRowDisposition() - ); - ?> - <?php else: ?> - <?php echo $this->displayPrices($_item->getBaseRowTotal(), $_item->getRowTotal()) ?> - <?php endif; ?> - - - <?php if (Mage::helper('weee')->getApplied($_item)): ?> - <?php if (Mage::helper('weee')->typeOfDisplay($_item, 1, 'sales')): ?> - <small> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount'], $tax['row_amount']); ?></span> - <?php endforeach; ?> - </small> - <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><small><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount_incl_tax'], $tax['row_amount_incl_tax']); ?></small></span> - <?php endforeach; ?> - <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 4, 'sales')): ?> - <small> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount_incl_tax'], $tax['row_amount_incl_tax']); ?></span> - <?php endforeach; ?> - </small> - <?php endif; ?> - - <?php if (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> - <br /> - <span class="nobr"><?php echo Mage::helper('weee')->__('Total'); ?>:<br /> - <?php - echo $this->displayPrices( - $_item->getBaseRowTotal()+$_item->getBaseWeeeTaxAppliedRowAmount()+$_item->getBaseWeeeTaxRowDisposition(), - $_item->getRowTotal()+$_item->getWeeeTaxAppliedRowAmount()+$_item->getWeeeTaxRowDisposition() - ); - ?> - </span> - <?php endif; ?> - <?php endif; ?> - </span> - <br /> - <?php endif; ?> - <?php if ($this->helper('tax')->displayCartBothPrices() || $this->helper('tax')->displayCartPriceInclTax()): ?> - <span class="price-incl-tax"> - <?php if ($this->helper('tax')->displayCartBothPrices()): ?> - <span class="label"><?php echo $this->__('Incl. Tax'); ?>:</span> - <?php endif; ?> - <?php $_incl = $this->helper('checkout')->getSubtotalInclTax($_item); ?> - <?php $_baseIncl = $this->helper('checkout')->getBaseSubtotalInclTax($_item); ?> - <?php if (Mage::helper('weee')->typeOfDisplay($_item, array(0, 1, 4), 'sales')): ?> - <?php echo $this->displayPrices($_baseIncl+$_item->getBaseWeeeTaxAppliedRowAmount(), $_incl+$_item->getWeeeTaxAppliedRowAmount()); ?> - <?php else: ?> - <?php echo $this->displayPrices($_baseIncl-$_item->getBaseWeeeTaxRowDisposition(), $_incl-$_item->getWeeeTaxRowDisposition()) ?> - <?php endif; ?> - - - <?php if (Mage::helper('weee')->getApplied($_item)): ?> - - <br /> - <?php if (Mage::helper('weee')->typeOfDisplay($_item, 1, 'sales')): ?> - <small> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount'], $tax['row_amount']); ?></span> - <?php endforeach; ?> - </small> - <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><small><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount_incl_tax'], $tax['row_amount_incl_tax']); ?></small></span> - <?php endforeach; ?> - <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 4, 'sales')): ?> - <small> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount_incl_tax'], $tax['row_amount_incl_tax']); ?></span> - <?php endforeach; ?> - </small> - <?php endif; ?> - - <?php if (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> - <br /><span class="nobr"><?php echo Mage::helper('weee')->__('Total'); ?>:<br /> <?php echo $this->displayPrices($_baseIncl+$_item->getBaseWeeeTaxAppliedRowAmount(), $_incl+$_item->getWeeeTaxAppliedRowAmount()); ?></span> - <?php endif; ?> - <?php endif; ?> - </span> - <?php endif; ?> - </span> - <?php else: ?> -   - <?php endif; ?> - </td> - <td class="a-right"> - <?php if ($this->canShowPriceInfo($_item)): ?> - <?php echo $this->displayPriceAttribute('tax_amount') ?> - <?php else: ?> -   - <?php endif; ?> - </td> - <td class="a-right"> - <?php if ($this->canShowPriceInfo($_item)): ?> - <?php echo $this->displayPriceAttribute('discount_amount') ?> - <?php else: ?> -   - <?php endif; ?> - </td> - <td class="a-right last"> - <?php if ($this->canShowPriceInfo($_item)): ?> - <?php echo $this->displayPrices( - $_item->getBaseRowTotal()+$_item->getBaseTaxAmount()-$_item->getBaseDiscountAmount()+$_item->getBaseWeeeTaxAppliedRowAmount(), - $_item->getRowTotal()+$_item->getTaxAmount()-$_item->getDiscountAmount()+$_item->getWeeeTaxAppliedRowAmount() - ) ?> - <?php else: ?> -   - <?php endif; ?> - </td> - </tr> -<?php endforeach; ?> -<?php if($_showlastRow): ?> - <tr class="border"> - <td> - <?php if ($this->getOrderOptions($_item->getOrderItem())): ?> - <dl class="item-options"> - <?php foreach ($this->getOrderOptions($_item->getOrderItem()) as $option): ?> - <dt><?php echo $option['label'] ?></dt> - <dd><?php echo $this->htmlEscape($option['value']); ?></dd> - <?php endforeach; ?> - </dl> - <?php else: ?> -   - <?php endif; ?> - <?php echo $_item->getDescription() ?> - </td> - <td> </td> - <td> </td> - <td> </td> - <td> </td> - <td> </td> - <td> </td> - <td> </td> - <td class="last"> </td> - </tr> -<?php endif; ?> +<?php +/** + * 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_default + * @package Mage_Bundle + * @copyright Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com) + * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) + */ +?> +<?php +/** + * @see Mage_Bundle_Block_Adminhtml_Sales_Order_Items_Renderer + */ +?> + +<?php $_item = $this->getItem() ?> +<?php $items = $this->getChilds($_item); ?> +<?php $_count = count ($items) ?> +<?php $_index = 0 ?> + +<?php $_prevOptionId = '' ?> + +<?php if($this->getOrderOptions() || $_item->getDescription()): ?> + <?php $_showlastRow = true ?> +<?php else: ?> + <?php $_showlastRow = false ?> +<?php endif; ?> + +<?php foreach ($items as $_item): ?> + <?php $this->setPriceDataObject($_item) ?> + <?php $attributes = $this->getSelectionAttributes($_item) ?> + <?php if ($_item->getOrderItem()->getParentItem()): ?> + <?php if ($_prevOptionId != $attributes['option_id']): ?> + <tr> + <td><div class="option-label"><?php echo $attributes['option_label'] ?></div></td> + <td> </td> + <td> </td> + <td> </td> + <td> </td> + <td> </td> + <td> </td> + <td> </td> + <td class="last"> </td> + </tr> + <?php $_prevOptionId = $attributes['option_id'] ?> + <?php endif; ?> + <?php endif; ?> + <tr<?php echo (++$_index==$_count && !$_showlastRow)?' class="border"':'' ?>> + <?php if (!$_item->getOrderItem()->getParentItem()): ?> + <td><h5 class="title"><?php echo $this->htmlEscape($_item->getName()) ?></h5> + <div> + <strong><?php echo $this->helper('sales')->__('SKU') ?>:</strong> + <?php echo implode('<br />', Mage::helper('catalog')->splitSku($_item->getSku())); ?> + </div> + </td> + <?php else: ?> + <td><div class="option-value"><?php echo $this->getValueHtml($_item)?></div></td> + <?php endif; ?> + <td class="a-right"> + <?php if ($this->canShowPriceInfo($_item)): ?> + <?php if ($this->helper('tax')->displayCartBothPrices() || $this->helper('tax')->displayCartPriceExclTax()): ?> + <span class="price-excl-tax"> + <?php if ($this->helper('tax')->displayCartBothPrices()): ?> + <span class="label"><?php echo $this->__('Excl. Tax'); ?>:</span> + <?php endif; ?> + + <?php if (Mage::helper('weee')->typeOfDisplay($_item, array(0, 1, 4), 'sales')): ?> + <?php + echo $this->displayPrices( + $_item->getBasePrice()+$_item->getBaseWeeeTaxAppliedAmount()+$_item->getBaseWeeeTaxDisposition(), + $_item->getPrice()+$_item->getWeeeTaxAppliedAmount()+$_item->getWeeeTaxDisposition() + ); + ?> + <?php else: ?> + <?php echo $this->displayPrices($_item->getBasePrice(), $_item->getPrice()) ?> + <?php endif; ?> + + + <?php if (Mage::helper('weee')->getApplied($_item)): ?> + <br /> + <?php if (Mage::helper('weee')->typeOfDisplay($_item, 1, 'sales')): ?> + <small> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount'], $tax['amount']); ?></span> + <?php endforeach; ?> + </small> + <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><small><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount_incl_tax'], $tax['amount_incl_tax']); ?></small></span> + <?php endforeach; ?> + <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 4, 'sales')): ?> + <small> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount_incl_tax'], $tax['amount_incl_tax']); ?></span> + <?php endforeach; ?> + </small> + <?php endif; ?> + + <?php if (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> + <br /> + <span class="nobr"><?php echo Mage::helper('weee')->__('Total'); ?>:<br /> + <?php + echo $this->displayPrices( + $_item->getBasePrice()+$_item->getBaseWeeeTaxAppliedAmount()+$_item->getBaseWeeeTaxDisposition(), + $_item->getPrice()+$_item->getWeeeTaxAppliedAmount()+$_item->getWeeeTaxDisposition() + ); + ?> + </span> + <?php endif; ?> + <?php endif; ?> + </span> + <br /> + <?php endif; ?> + <?php if ($this->helper('tax')->displayCartBothPrices() || $this->helper('tax')->displayCartPriceInclTax()): ?> + <span class="price-incl-tax"> + <?php if ($this->helper('tax')->displayCartBothPrices()): ?> + <span class="label"><?php echo $this->__('Incl. Tax'); ?>:</span> + <?php endif; ?> + <?php $_incl = $this->helper('checkout')->getPriceInclTax($_item); ?> + <?php $_baseIncl = $this->helper('checkout')->getBasePriceInclTax($_item); ?> + + <?php if (Mage::helper('weee')->typeOfDisplay($_item, array(0, 1, 4), 'sales')): ?> + <?php echo $this->displayPrices($_baseIncl+$_item->getBaseWeeeTaxAppliedAmount(), $_incl+$_item->getWeeeTaxAppliedAmount()); ?> + <?php else: ?> + <?php echo $this->displayPrices($_baseIncl-$_item->getBaseWeeeTaxDisposition(), $_incl-$_item->getWeeeTaxDisposition()) ?> + <?php endif; ?> + + <?php if (Mage::helper('weee')->getApplied($_item)): ?> + <br /> + <?php if (Mage::helper('weee')->typeOfDisplay($_item, 1, 'sales')): ?> + <small> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount'], $tax['amount']); ?></span> + <?php endforeach; ?> + </small> + <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><small><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount_incl_tax'], $tax['amount_incl_tax']); ?></small></span> + <?php endforeach; ?> + <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 4, 'sales')): ?> + <small> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount_incl_tax'], $tax['amount_incl_tax']); ?></span> + <?php endforeach; ?> + </small> + <?php endif; ?> + + <?php if (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> + <br /> + <span class="nobr"><?php echo Mage::helper('weee')->__('Total'); ?>:<br /> <?php echo $this->displayPrices($_baseIncl+$_item->getBaseWeeeTaxAppliedAmount(), $_incl+$_item->getWeeeTaxAppliedAmount()); ?></span> + <?php endif; ?> + <?php endif; ?> + </span> + <?php endif; ?> + <?php else: ?> +   + <?php endif; ?> + </td> + <td> + <?php if ($this->canShowPriceInfo($_item)): ?> + <table cellspacing="0" class="qty-table"> + <tr> + <td><?php echo Mage::helper('sales')->__('Ordered') ?></td> + <td><strong><?php echo $_item->getOrderItem()->getQtyOrdered()*1 ?></strong></td> + </tr> + <?php if ((float) $_item->getOrderItem()->getQtyInvoiced()): ?> + <tr> + <td><?php echo Mage::helper('sales')->__('Invoiced') ?></td> + <td><strong><?php echo $_item->getOrderItem()->getQtyInvoiced()*1 ?></strong></td> + </tr> + <?php endif; ?> + <?php if ((float) $_item->getOrderItem()->getQtyShipped() && $this->isShipmentSeparately($_item)): ?> + <tr> + <td><?php echo Mage::helper('sales')->__('Shipped') ?></td> + <td><strong><?php echo $_item->getOrderItem()->getQtyShipped()*1 ?></strong></td> + </tr> + <?php endif; ?> + <?php if ((float) $_item->getOrderItem()->getQtyRefunded()): ?> + <tr> + <td><?php echo Mage::helper('sales')->__('Refunded') ?></td> + <td><strong><?php echo $_item->getOrderItem()->getQtyRefunded()*1 ?></strong></td> + </tr> + <?php endif; ?> + <?php if ((float) $_item->getOrderItem()->getQtyCanceled()): ?> + <tr> + <td><?php echo Mage::helper('sales')->__('Canceled') ?></td> + <td><strong><?php echo $_item->getOrderItem()->getQtyCanceled()*1 ?></strong></td> + </tr> + <?php endif; ?> + </table> + <?php elseif ($this->isShipmentSeparately($_item)): ?> + <table cellspacing="0" class="qty-table"> + <tr> + <td><?php echo Mage::helper('sales')->__('Ordered') ?></td> + <td><strong><?php echo $_item->getOrderItem()->getQtyOrdered()*1 ?></strong></td> + </tr> + <?php if ((float) $_item->getOrderItem()->getQtyShipped()): ?> + <tr> + <td><?php echo Mage::helper('sales')->__('Shipped') ?></td> + <td><strong><?php echo $_item->getOrderItem()->getQtyShipped()*1 ?></strong></td> + </tr> + <?php endif; ?> + </table> + <?php else: ?> +   + <?php endif; ?> + </td> + <td class="a-center"> + <?php if ($this->canShowPriceInfo($_item)): ?> + <?php if ($this->canReturnToStock()) : ?> + <input type="checkbox" name="creditmemo[items][<?php echo $_item->getOrderItemId() ?>][back_to_stock]" value="1"<?php if ($_item->getBackToStock()):?> checked="checked"<?php endif;?> /> + <?php endif; ?> + <?php else: ?> +   + <?php endif; ?> + </td> + <td class="a-center"> + <?php if ($this->canShowPriceInfo($_item)): ?> + <?php if ($this->canEditQty()) : ?> + <input type="text" class="input-text qty-input" name="creditmemo[items][<?php echo $_item->getOrderItemId() ?>][qty]" value="<?php echo $_item->getQty()*1 ?>" /> + <?php else: ?> + <?php echo $_item->getQty()*1 ?> + <?php endif; ?> + <?php else: ?> +   + <?php endif; ?> + </td> + <td class="a-right"> + <?php if ($this->canShowPriceInfo($_item)): ?> + <?php if ($this->helper('tax')->displayCartBothPrices() || $this->helper('tax')->displayCartPriceExclTax()): ?> + <span class="price-excl-tax"> + <?php if ($this->helper('tax')->displayCartBothPrices()): ?> + <span class="label"><?php echo $this->__('Excl. Tax'); ?>:</span> + <?php endif; ?> + + <?php if (Mage::helper('weee')->typeOfDisplay($_item, array(0, 1, 4), 'sales')): ?> + <?php + echo $this->displayPrices( + $_item->getBaseRowTotal()+$_item->getBaseWeeeTaxAppliedRowAmount()+$_item->getBaseWeeeTaxRowDisposition(), + $_item->getRowTotal()+$_item->getWeeeTaxAppliedRowAmount()+$_item->getWeeeTaxRowDisposition() + ); + ?> + <?php else: ?> + <?php echo $this->displayPrices($_item->getBaseRowTotal(), $_item->getRowTotal()) ?> + <?php endif; ?> + + + <?php if (Mage::helper('weee')->getApplied($_item)): ?> + <?php if (Mage::helper('weee')->typeOfDisplay($_item, 1, 'sales')): ?> + <small> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount'], $tax['row_amount']); ?></span> + <?php endforeach; ?> + </small> + <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><small><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount_incl_tax'], $tax['row_amount_incl_tax']); ?></small></span> + <?php endforeach; ?> + <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 4, 'sales')): ?> + <small> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount_incl_tax'], $tax['row_amount_incl_tax']); ?></span> + <?php endforeach; ?> + </small> + <?php endif; ?> + + <?php if (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> + <br /> + <span class="nobr"><?php echo Mage::helper('weee')->__('Total'); ?>:<br /> + <?php + echo $this->displayPrices( + $_item->getBaseRowTotal()+$_item->getBaseWeeeTaxAppliedRowAmount()+$_item->getBaseWeeeTaxRowDisposition(), + $_item->getRowTotal()+$_item->getWeeeTaxAppliedRowAmount()+$_item->getWeeeTaxRowDisposition() + ); + ?> + </span> + <?php endif; ?> + <?php endif; ?> + </span> + <br /> + <?php endif; ?> + <?php if ($this->helper('tax')->displayCartBothPrices() || $this->helper('tax')->displayCartPriceInclTax()): ?> + <span class="price-incl-tax"> + <?php if ($this->helper('tax')->displayCartBothPrices()): ?> + <span class="label"><?php echo $this->__('Incl. Tax'); ?>:</span> + <?php endif; ?> + <?php $_incl = $this->helper('checkout')->getSubtotalInclTax($_item); ?> + <?php $_baseIncl = $this->helper('checkout')->getBaseSubtotalInclTax($_item); ?> + <?php if (Mage::helper('weee')->typeOfDisplay($_item, array(0, 1, 4), 'sales')): ?> + <?php echo $this->displayPrices($_baseIncl+$_item->getBaseWeeeTaxAppliedRowAmount(), $_incl+$_item->getWeeeTaxAppliedRowAmount()); ?> + <?php else: ?> + <?php echo $this->displayPrices($_baseIncl-$_item->getBaseWeeeTaxRowDisposition(), $_incl-$_item->getWeeeTaxRowDisposition()) ?> + <?php endif; ?> + + + <?php if (Mage::helper('weee')->getApplied($_item)): ?> + + <br /> + <?php if (Mage::helper('weee')->typeOfDisplay($_item, 1, 'sales')): ?> + <small> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount'], $tax['row_amount']); ?></span> + <?php endforeach; ?> + </small> + <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><small><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount_incl_tax'], $tax['row_amount_incl_tax']); ?></small></span> + <?php endforeach; ?> + <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 4, 'sales')): ?> + <small> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount_incl_tax'], $tax['row_amount_incl_tax']); ?></span> + <?php endforeach; ?> + </small> + <?php endif; ?> + + <?php if (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> + <br /><span class="nobr"><?php echo Mage::helper('weee')->__('Total'); ?>:<br /> <?php echo $this->displayPrices($_baseIncl+$_item->getBaseWeeeTaxAppliedRowAmount(), $_incl+$_item->getWeeeTaxAppliedRowAmount()); ?></span> + <?php endif; ?> + <?php endif; ?> + </span> + <?php endif; ?> + </span> + <?php else: ?> +   + <?php endif; ?> + </td> + <td class="a-right"> + <?php if ($this->canShowPriceInfo($_item)): ?> + <?php echo $this->displayPriceAttribute('tax_amount') ?> + <?php else: ?> +   + <?php endif; ?> + </td> + <td class="a-right"> + <?php if ($this->canShowPriceInfo($_item)): ?> + <?php echo $this->displayPriceAttribute('discount_amount') ?> + <?php else: ?> +   + <?php endif; ?> + </td> + <td class="a-right last"> + <?php if ($this->canShowPriceInfo($_item)): ?> + <?php echo $this->displayPrices( + $_item->getBaseRowTotal()+$_item->getBaseTaxAmount()-$_item->getBaseDiscountAmount()+$_item->getBaseWeeeTaxAppliedRowAmount(), + $_item->getRowTotal()+$_item->getTaxAmount()-$_item->getDiscountAmount()+$_item->getWeeeTaxAppliedRowAmount() + ) ?> + <?php else: ?> +   + <?php endif; ?> + </td> + </tr> +<?php endforeach; ?> +<?php if($_showlastRow): ?> + <tr class="border"> + <td> + <?php if ($this->getOrderOptions($_item->getOrderItem())): ?> + <dl class="item-options"> + <?php foreach ($this->getOrderOptions($_item->getOrderItem()) as $option): ?> + <dt><?php echo $option['label'] ?></dt> + <dd> + <?php if (isset($option['custom_view']) && $option['custom_view']): ?> + <?php echo $option['value'];?> + <?php else: ?> + <?php echo Mage::helper('core/string')->truncate($option['value'], 55, '', $_remainder);?> + <?php if ($_remainder):?> + ... <span id="<?php echo $_id = 'id' . uniqid()?>"><?php echo $_remainder ?></span> + <script type="text/javascript"> + $('<?php echo $_id ?>').hide(); + $('<?php echo $_id ?>').up().observe('mouseover', function(){$('<?php echo $_id ?>').show();}); + $('<?php echo $_id ?>').up().observe('mouseout', function(){$('<?php echo $_id ?>').hide();}); + </script> + <?php endif;?> + <?php endif;?> + </dd> + <?php endforeach; ?> + </dl> + <?php else: ?> +   + <?php endif; ?> + <?php echo $_item->getDescription() ?> + </td> + <td> </td> + <td> </td> + <td> </td> + <td> </td> + <td> </td> + <td> </td> + <td> </td> + <td class="last"> </td> + </tr> +<?php endif; ?> diff --git a/app/design/adminhtml/default/default/template/bundle/sales/creditmemo/view/items/renderer.phtml b/app/design/adminhtml/default/default/template/bundle/sales/creditmemo/view/items/renderer.phtml index 3bc6d6fe29..962a852f98 100644 --- a/app/design/adminhtml/default/default/template/bundle/sales/creditmemo/view/items/renderer.phtml +++ b/app/design/adminhtml/default/default/template/bundle/sales/creditmemo/view/items/renderer.phtml @@ -1,327 +1,341 @@ -<?php -/** - * 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_default - * @package Mage_Bundle - * @copyright Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com) - * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) - */ -?> -<?php -/** - * @see Mage_Bundle_Block_Adminhtml_Sales_Order_Items_Renderer - */ -?> - -<?php $_item = $this->getItem() ?> -<?php $items = $this->getChilds($_item); ?> -<?php $_count = count ($items) ?> -<?php $_index = 0 ?> - -<?php $_prevOptionId = '' ?> - -<?php if($this->getOrderOptions() || $_item->getDescription()): ?> - <?php $_showlastRow = true ?> -<?php else: ?> - <?php $_showlastRow = false ?> -<?php endif; ?> - -<?php foreach ($items as $_item): ?> - <?php $this->setPriceDataObject($_item) ?> - <?php $attributes = $this->getSelectionAttributes($_item) ?> - <?php if ($_item->getOrderItem()->getParentItem()): ?> - <?php if ($_prevOptionId != $attributes['option_id']): ?> - <tr> - <td><div class="option-label"><?php echo $attributes['option_label'] ?></div></td> - <td> </td> - <td> </td> - <td> </td> - <td> </td> - <td> </td> - <td class="last"> </td> - </tr> - <?php $_prevOptionId = $attributes['option_id'] ?> - <?php endif; ?> - <?php endif; ?> - <tr<?php echo (++$_index==$_count && !$_showlastRow)?' class="border"':'' ?>> - <?php if (!$_item->getOrderItem()->getParentItem()): ?> - <td><h5 class="title"><?php echo $this->htmlEscape($_item->getName()) ?></h5> - <div> - <strong><?php echo $this->helper('sales')->__('SKU') ?>:</strong> - <?php echo implode('<br />', Mage::helper('catalog')->splitSku($_item->getSku())); ?> - </div> - </td> - <?php else: ?> - <td><div class="option-value"><?php echo $this->getValueHtml($_item)?></div></td> - <?php endif; ?> - <td class="a-right"> - <?php if ($this->canShowPriceInfo($_item)): ?> - <?php if ($this->helper('tax')->displayCartBothPrices() || $this->helper('tax')->displayCartPriceExclTax()): ?> - <span class="price-excl-tax"> - <?php if ($this->helper('tax')->displayCartBothPrices()): ?> - <span class="label"><?php echo $this->__('Excl. Tax'); ?>:</span> - <?php endif; ?> - - <?php if (Mage::helper('weee')->typeOfDisplay($_item, array(0, 1, 4), 'sales')): ?> - <?php - echo $this->displayPrices( - $_item->getBasePrice()+$_item->getBaseWeeeTaxAppliedAmount()+$_item->getBaseWeeeTaxDisposition(), - $_item->getPrice()+$_item->getWeeeTaxAppliedAmount()+$_item->getWeeeTaxDisposition() - ); - ?> - <?php else: ?> - <?php echo $this->displayPrices($_item->getBasePrice(), $_item->getPrice()) ?> - <?php endif; ?> - - - <?php if (Mage::helper('weee')->getApplied($_item)): ?> - <br /> - <?php if (Mage::helper('weee')->typeOfDisplay($_item, 1, 'sales')): ?> - <small> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount'], $tax['amount']); ?></span> - <?php endforeach; ?> - </small> - <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><small><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount_incl_tax'], $tax['amount_incl_tax']); ?></small></span> - <?php endforeach; ?> - <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 4, 'sales')): ?> - <small> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount_incl_tax'], $tax['amount_incl_tax']); ?></span> - <?php endforeach; ?> - </small> - <?php endif; ?> - - <?php if (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> - <br /> - <span class="nobr"><?php echo Mage::helper('weee')->__('Total'); ?>:<br /> - <?php - echo $this->displayPrices( - $_item->getBasePrice()+$_item->getBaseWeeeTaxAppliedAmount()+$_item->getBaseWeeeTaxDisposition(), - $_item->getPrice()+$_item->getWeeeTaxAppliedAmount()+$_item->getWeeeTaxDisposition() - ); - ?> - </span> - <?php endif; ?> - <?php endif; ?> - </span> - <br /> - <?php endif; ?> - <?php if ($this->helper('tax')->displayCartBothPrices() || $this->helper('tax')->displayCartPriceInclTax()): ?> - <span class="price-incl-tax"> - <?php if ($this->helper('tax')->displayCartBothPrices()): ?> - <span class="label"><?php echo $this->__('Incl. Tax'); ?>:</span> - <?php endif; ?> - <?php $_incl = $this->helper('checkout')->getPriceInclTax($_item); ?> - <?php $_baseIncl = $this->helper('checkout')->getBasePriceInclTax($_item); ?> - - <?php if (Mage::helper('weee')->typeOfDisplay($_item, array(0, 1, 4), 'sales')): ?> - <?php echo $this->displayPrices($_baseIncl+$_item->getBaseWeeeTaxAppliedAmount(), $_incl+$_item->getWeeeTaxAppliedAmount()); ?> - <?php else: ?> - <?php echo $this->displayPrices($_baseIncl-$_item->getBaseWeeeTaxDisposition(), $_incl-$_item->getWeeeTaxDisposition()) ?> - <?php endif; ?> - - <?php if (Mage::helper('weee')->getApplied($_item)): ?> - <br /> - <?php if (Mage::helper('weee')->typeOfDisplay($_item, 1, 'sales')): ?> - <small> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount'], $tax['amount']); ?></span> - <?php endforeach; ?> - </small> - <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><small><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount_incl_tax'], $tax['amount_incl_tax']); ?></small></span> - <?php endforeach; ?> - <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 4, 'sales')): ?> - <small> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount_incl_tax'], $tax['amount_incl_tax']); ?></span> - <?php endforeach; ?> - </small> - <?php endif; ?> - - <?php if (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> - <br /> - <span class="nobr"><?php echo Mage::helper('weee')->__('Total'); ?>:<br /> <?php echo $this->displayPrices($_baseIncl+$_item->getBaseWeeeTaxAppliedAmount(), $_incl+$_item->getWeeeTaxAppliedAmount()); ?></span> - <?php endif; ?> - <?php endif; ?> - </span> - <?php endif; ?> - <?php else: ?> -   - <?php endif; ?> - </td> - <td class="a-center"> - <?php if ($this->canShowPriceInfo($_item)): ?> - <?php echo $_item->getQty()*1 ?> - <?php else: ?> -   - <?php endif; ?> - </td> - <td class="a-right"> - <?php if ($this->canShowPriceInfo($_item)): ?> - <?php if ($this->helper('tax')->displayCartBothPrices() || $this->helper('tax')->displayCartPriceExclTax()): ?> - <span class="price-excl-tax"> - <?php if ($this->helper('tax')->displayCartBothPrices()): ?> - <span class="label"><?php echo $this->__('Excl. Tax'); ?>:</span> - <?php endif; ?> - - <?php if (Mage::helper('weee')->typeOfDisplay($_item, array(0, 1, 4), 'sales')): ?> - <?php - echo $this->displayPrices( - $_item->getBaseRowTotal()+$_item->getBaseWeeeTaxAppliedRowAmount()+$_item->getBaseWeeeTaxRowDisposition(), - $_item->getRowTotal()+$_item->getWeeeTaxAppliedRowAmount()+$_item->getWeeeTaxRowDisposition() - ); - ?> - <?php else: ?> - <?php echo $this->displayPrices($_item->getBaseRowTotal(), $_item->getRowTotal()) ?> - <?php endif; ?> - - - <?php if (Mage::helper('weee')->getApplied($_item)): ?> - <?php if (Mage::helper('weee')->typeOfDisplay($_item, 1, 'sales')): ?> - <small> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount'], $tax['row_amount']); ?></span> - <?php endforeach; ?> - </small> - <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><small><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount_incl_tax'], $tax['row_amount_incl_tax']); ?></small></span> - <?php endforeach; ?> - <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 4, 'sales')): ?> - <small> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount_incl_tax'], $tax['row_amount_incl_tax']); ?></span> - <?php endforeach; ?> - </small> - <?php endif; ?> - - <?php if (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> - <br /> - <span class="nobr"><?php echo Mage::helper('weee')->__('Total'); ?>:<br /> - <?php - echo $this->displayPrices( - $_item->getBaseRowTotal()+$_item->getBaseWeeeTaxAppliedRowAmount()+$_item->getBaseWeeeTaxRowDisposition(), - $_item->getRowTotal()+$_item->getWeeeTaxAppliedRowAmount()+$_item->getWeeeTaxRowDisposition() - ); - ?> - </span> - <?php endif; ?> - <?php endif; ?> - </span> - <br /> - <?php endif; ?> - <?php if ($this->helper('tax')->displayCartBothPrices() || $this->helper('tax')->displayCartPriceInclTax()): ?> - <span class="price-incl-tax"> - <?php if ($this->helper('tax')->displayCartBothPrices()): ?> - <span class="label"><?php echo $this->__('Incl. Tax'); ?>:</span> - <?php endif; ?> - <?php $_incl = $this->helper('checkout')->getSubtotalInclTax($_item); ?> - <?php $_baseIncl = $this->helper('checkout')->getBaseSubtotalInclTax($_item); ?> - <?php if (Mage::helper('weee')->typeOfDisplay($_item, array(0, 1, 4), 'sales')): ?> - <?php echo $this->displayPrices($_baseIncl+$_item->getBaseWeeeTaxAppliedRowAmount(), $_incl+$_item->getWeeeTaxAppliedRowAmount()); ?> - <?php else: ?> - <?php echo $this->displayPrices($_baseIncl-$_item->getBaseWeeeTaxRowDisposition(), $_incl-$_item->getWeeeTaxRowDisposition()) ?> - <?php endif; ?> - - - <?php if (Mage::helper('weee')->getApplied($_item)): ?> - - <br /> - <?php if (Mage::helper('weee')->typeOfDisplay($_item, 1, 'sales')): ?> - <small> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount'], $tax['row_amount']); ?></span> - <?php endforeach; ?> - </small> - <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><small><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount_incl_tax'], $tax['row_amount_incl_tax']); ?></small></span> - <?php endforeach; ?> - <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 4, 'sales')): ?> - <small> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount_incl_tax'], $tax['row_amount_incl_tax']); ?></span> - <?php endforeach; ?> - </small> - <?php endif; ?> - - <?php if (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> - <br /><span class="nobr"><?php echo Mage::helper('weee')->__('Total'); ?>:<br /> <?php echo $this->displayPrices($_baseIncl+$_item->getBaseWeeeTaxAppliedRowAmount(), $_incl+$_item->getWeeeTaxAppliedRowAmount()); ?></span> - <?php endif; ?> - <?php endif; ?> - </span> - <?php endif; ?> - </span> - <?php else: ?> -   - <?php endif; ?> - </td> - <td class="a-right"> - <?php if ($this->canShowPriceInfo($_item)): ?> - <?php echo $this->displayPriceAttribute('tax_amount') ?> - <?php else: ?> -   - <?php endif; ?> - </td> - <td class="a-right"> - <?php if ($this->canShowPriceInfo($_item)): ?> - <?php echo $this->displayPriceAttribute('discount_amount') ?> - <?php else: ?> -   - <?php endif; ?> - </td> - <td class="a-right last"> - <?php if ($this->canShowPriceInfo($_item)): ?> - <?php echo $this->displayPrices( - $_item->getBaseRowTotal()-$_item->getBaseDiscountAmount()+$_item->getBaseTaxAmount()+$_item->getBaseWeeeTaxAppliedRowAmount(), - $_item->getRowTotal()-$_item->getDiscountAmount()+$_item->getTaxAmount()+$_item->getWeeeTaxAppliedRowAmount() - ) ?> - <?php else: ?> -   - <?php endif; ?> - </td> - </tr> -<?php endforeach; ?> -<?php if($_showlastRow): ?> - <tr class="border"> - <td> - <?php if ($this->getOrderOptions()): ?> - <dl class="item-options"> - <?php foreach ($this->getOrderOptions() as $option): ?> - <dt><?php echo $option['label'] ?></dt> - <dd><?php echo $this->htmlEscape($option['value']); ?></dd> - <?php endforeach; ?> - </dl> - <?php endif; ?> - <?php echo $this->getItem()->getDescription() ?> - </td> - <td> </td> - <td> </td> - <td> </td> - <td> </td> - <td> </td> - <td class="last"> </td> - </tr> -<?php endif; ?> +<?php +/** + * 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_default + * @package Mage_Bundle + * @copyright Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com) + * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) + */ +?> +<?php +/** + * @see Mage_Bundle_Block_Adminhtml_Sales_Order_Items_Renderer + */ +?> + +<?php $_item = $this->getItem() ?> +<?php $items = $this->getChilds($_item); ?> +<?php $_count = count ($items) ?> +<?php $_index = 0 ?> + +<?php $_prevOptionId = '' ?> + +<?php if($this->getOrderOptions() || $_item->getDescription()): ?> + <?php $_showlastRow = true ?> +<?php else: ?> + <?php $_showlastRow = false ?> +<?php endif; ?> + +<?php foreach ($items as $_item): ?> + <?php $this->setPriceDataObject($_item) ?> + <?php $attributes = $this->getSelectionAttributes($_item) ?> + <?php if ($_item->getOrderItem()->getParentItem()): ?> + <?php if ($_prevOptionId != $attributes['option_id']): ?> + <tr> + <td><div class="option-label"><?php echo $attributes['option_label'] ?></div></td> + <td> </td> + <td> </td> + <td> </td> + <td> </td> + <td> </td> + <td class="last"> </td> + </tr> + <?php $_prevOptionId = $attributes['option_id'] ?> + <?php endif; ?> + <?php endif; ?> + <tr<?php echo (++$_index==$_count && !$_showlastRow)?' class="border"':'' ?>> + <?php if (!$_item->getOrderItem()->getParentItem()): ?> + <td><h5 class="title"><?php echo $this->htmlEscape($_item->getName()) ?></h5> + <div> + <strong><?php echo $this->helper('sales')->__('SKU') ?>:</strong> + <?php echo implode('<br />', Mage::helper('catalog')->splitSku($_item->getSku())); ?> + </div> + </td> + <?php else: ?> + <td><div class="option-value"><?php echo $this->getValueHtml($_item)?></div></td> + <?php endif; ?> + <td class="a-right"> + <?php if ($this->canShowPriceInfo($_item)): ?> + <?php if ($this->helper('tax')->displayCartBothPrices() || $this->helper('tax')->displayCartPriceExclTax()): ?> + <span class="price-excl-tax"> + <?php if ($this->helper('tax')->displayCartBothPrices()): ?> + <span class="label"><?php echo $this->__('Excl. Tax'); ?>:</span> + <?php endif; ?> + + <?php if (Mage::helper('weee')->typeOfDisplay($_item, array(0, 1, 4), 'sales')): ?> + <?php + echo $this->displayPrices( + $_item->getBasePrice()+$_item->getBaseWeeeTaxAppliedAmount()+$_item->getBaseWeeeTaxDisposition(), + $_item->getPrice()+$_item->getWeeeTaxAppliedAmount()+$_item->getWeeeTaxDisposition() + ); + ?> + <?php else: ?> + <?php echo $this->displayPrices($_item->getBasePrice(), $_item->getPrice()) ?> + <?php endif; ?> + + + <?php if (Mage::helper('weee')->getApplied($_item)): ?> + <br /> + <?php if (Mage::helper('weee')->typeOfDisplay($_item, 1, 'sales')): ?> + <small> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount'], $tax['amount']); ?></span> + <?php endforeach; ?> + </small> + <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><small><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount_incl_tax'], $tax['amount_incl_tax']); ?></small></span> + <?php endforeach; ?> + <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 4, 'sales')): ?> + <small> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount_incl_tax'], $tax['amount_incl_tax']); ?></span> + <?php endforeach; ?> + </small> + <?php endif; ?> + + <?php if (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> + <br /> + <span class="nobr"><?php echo Mage::helper('weee')->__('Total'); ?>:<br /> + <?php + echo $this->displayPrices( + $_item->getBasePrice()+$_item->getBaseWeeeTaxAppliedAmount()+$_item->getBaseWeeeTaxDisposition(), + $_item->getPrice()+$_item->getWeeeTaxAppliedAmount()+$_item->getWeeeTaxDisposition() + ); + ?> + </span> + <?php endif; ?> + <?php endif; ?> + </span> + <br /> + <?php endif; ?> + <?php if ($this->helper('tax')->displayCartBothPrices() || $this->helper('tax')->displayCartPriceInclTax()): ?> + <span class="price-incl-tax"> + <?php if ($this->helper('tax')->displayCartBothPrices()): ?> + <span class="label"><?php echo $this->__('Incl. Tax'); ?>:</span> + <?php endif; ?> + <?php $_incl = $this->helper('checkout')->getPriceInclTax($_item); ?> + <?php $_baseIncl = $this->helper('checkout')->getBasePriceInclTax($_item); ?> + + <?php if (Mage::helper('weee')->typeOfDisplay($_item, array(0, 1, 4), 'sales')): ?> + <?php echo $this->displayPrices($_baseIncl+$_item->getBaseWeeeTaxAppliedAmount(), $_incl+$_item->getWeeeTaxAppliedAmount()); ?> + <?php else: ?> + <?php echo $this->displayPrices($_baseIncl-$_item->getBaseWeeeTaxDisposition(), $_incl-$_item->getWeeeTaxDisposition()) ?> + <?php endif; ?> + + <?php if (Mage::helper('weee')->getApplied($_item)): ?> + <br /> + <?php if (Mage::helper('weee')->typeOfDisplay($_item, 1, 'sales')): ?> + <small> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount'], $tax['amount']); ?></span> + <?php endforeach; ?> + </small> + <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><small><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount_incl_tax'], $tax['amount_incl_tax']); ?></small></span> + <?php endforeach; ?> + <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 4, 'sales')): ?> + <small> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount_incl_tax'], $tax['amount_incl_tax']); ?></span> + <?php endforeach; ?> + </small> + <?php endif; ?> + + <?php if (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> + <br /> + <span class="nobr"><?php echo Mage::helper('weee')->__('Total'); ?>:<br /> <?php echo $this->displayPrices($_baseIncl+$_item->getBaseWeeeTaxAppliedAmount(), $_incl+$_item->getWeeeTaxAppliedAmount()); ?></span> + <?php endif; ?> + <?php endif; ?> + </span> + <?php endif; ?> + <?php else: ?> +   + <?php endif; ?> + </td> + <td class="a-center"> + <?php if ($this->canShowPriceInfo($_item)): ?> + <?php echo $_item->getQty()*1 ?> + <?php else: ?> +   + <?php endif; ?> + </td> + <td class="a-right"> + <?php if ($this->canShowPriceInfo($_item)): ?> + <?php if ($this->helper('tax')->displayCartBothPrices() || $this->helper('tax')->displayCartPriceExclTax()): ?> + <span class="price-excl-tax"> + <?php if ($this->helper('tax')->displayCartBothPrices()): ?> + <span class="label"><?php echo $this->__('Excl. Tax'); ?>:</span> + <?php endif; ?> + + <?php if (Mage::helper('weee')->typeOfDisplay($_item, array(0, 1, 4), 'sales')): ?> + <?php + echo $this->displayPrices( + $_item->getBaseRowTotal()+$_item->getBaseWeeeTaxAppliedRowAmount()+$_item->getBaseWeeeTaxRowDisposition(), + $_item->getRowTotal()+$_item->getWeeeTaxAppliedRowAmount()+$_item->getWeeeTaxRowDisposition() + ); + ?> + <?php else: ?> + <?php echo $this->displayPrices($_item->getBaseRowTotal(), $_item->getRowTotal()) ?> + <?php endif; ?> + + + <?php if (Mage::helper('weee')->getApplied($_item)): ?> + <?php if (Mage::helper('weee')->typeOfDisplay($_item, 1, 'sales')): ?> + <small> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount'], $tax['row_amount']); ?></span> + <?php endforeach; ?> + </small> + <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><small><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount_incl_tax'], $tax['row_amount_incl_tax']); ?></small></span> + <?php endforeach; ?> + <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 4, 'sales')): ?> + <small> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount_incl_tax'], $tax['row_amount_incl_tax']); ?></span> + <?php endforeach; ?> + </small> + <?php endif; ?> + + <?php if (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> + <br /> + <span class="nobr"><?php echo Mage::helper('weee')->__('Total'); ?>:<br /> + <?php + echo $this->displayPrices( + $_item->getBaseRowTotal()+$_item->getBaseWeeeTaxAppliedRowAmount()+$_item->getBaseWeeeTaxRowDisposition(), + $_item->getRowTotal()+$_item->getWeeeTaxAppliedRowAmount()+$_item->getWeeeTaxRowDisposition() + ); + ?> + </span> + <?php endif; ?> + <?php endif; ?> + </span> + <br /> + <?php endif; ?> + <?php if ($this->helper('tax')->displayCartBothPrices() || $this->helper('tax')->displayCartPriceInclTax()): ?> + <span class="price-incl-tax"> + <?php if ($this->helper('tax')->displayCartBothPrices()): ?> + <span class="label"><?php echo $this->__('Incl. Tax'); ?>:</span> + <?php endif; ?> + <?php $_incl = $this->helper('checkout')->getSubtotalInclTax($_item); ?> + <?php $_baseIncl = $this->helper('checkout')->getBaseSubtotalInclTax($_item); ?> + <?php if (Mage::helper('weee')->typeOfDisplay($_item, array(0, 1, 4), 'sales')): ?> + <?php echo $this->displayPrices($_baseIncl+$_item->getBaseWeeeTaxAppliedRowAmount(), $_incl+$_item->getWeeeTaxAppliedRowAmount()); ?> + <?php else: ?> + <?php echo $this->displayPrices($_baseIncl-$_item->getBaseWeeeTaxRowDisposition(), $_incl-$_item->getWeeeTaxRowDisposition()) ?> + <?php endif; ?> + + + <?php if (Mage::helper('weee')->getApplied($_item)): ?> + + <br /> + <?php if (Mage::helper('weee')->typeOfDisplay($_item, 1, 'sales')): ?> + <small> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount'], $tax['row_amount']); ?></span> + <?php endforeach; ?> + </small> + <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><small><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount_incl_tax'], $tax['row_amount_incl_tax']); ?></small></span> + <?php endforeach; ?> + <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 4, 'sales')): ?> + <small> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount_incl_tax'], $tax['row_amount_incl_tax']); ?></span> + <?php endforeach; ?> + </small> + <?php endif; ?> + + <?php if (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> + <br /><span class="nobr"><?php echo Mage::helper('weee')->__('Total'); ?>:<br /> <?php echo $this->displayPrices($_baseIncl+$_item->getBaseWeeeTaxAppliedRowAmount(), $_incl+$_item->getWeeeTaxAppliedRowAmount()); ?></span> + <?php endif; ?> + <?php endif; ?> + </span> + <?php endif; ?> + </span> + <?php else: ?> +   + <?php endif; ?> + </td> + <td class="a-right"> + <?php if ($this->canShowPriceInfo($_item)): ?> + <?php echo $this->displayPriceAttribute('tax_amount') ?> + <?php else: ?> +   + <?php endif; ?> + </td> + <td class="a-right"> + <?php if ($this->canShowPriceInfo($_item)): ?> + <?php echo $this->displayPriceAttribute('discount_amount') ?> + <?php else: ?> +   + <?php endif; ?> + </td> + <td class="a-right last"> + <?php if ($this->canShowPriceInfo($_item)): ?> + <?php echo $this->displayPrices( + $_item->getBaseRowTotal()-$_item->getBaseDiscountAmount()+$_item->getBaseTaxAmount()+$_item->getBaseWeeeTaxAppliedRowAmount(), + $_item->getRowTotal()-$_item->getDiscountAmount()+$_item->getTaxAmount()+$_item->getWeeeTaxAppliedRowAmount() + ) ?> + <?php else: ?> +   + <?php endif; ?> + </td> + </tr> +<?php endforeach; ?> +<?php if($_showlastRow): ?> + <tr class="border"> + <td> + <?php if ($this->getOrderOptions()): ?> + <dl class="item-options"> + <?php foreach ($this->getOrderOptions() as $option): ?> + <dt><?php echo $option['label'] ?></dt> + <dd> + <?php if (isset($option['custom_view']) && $option['custom_view']): ?> + <?php echo $option['value'];?> + <?php else: ?> + <?php echo Mage::helper('core/string')->truncate($option['value'], 55, '', $_remainder);?> + <?php if ($_remainder):?> + ... <span id="<?php echo $_id = 'id' . uniqid()?>"><?php echo $_remainder ?></span> + <script type="text/javascript"> + $('<?php echo $_id ?>').hide(); + $('<?php echo $_id ?>').up().observe('mouseover', function(){$('<?php echo $_id ?>').show();}); + $('<?php echo $_id ?>').up().observe('mouseout', function(){$('<?php echo $_id ?>').hide();}); + </script> + <?php endif;?> + <?php endif;?> + </dd> + <?php endforeach; ?> + </dl> + <?php endif; ?> + <?php echo $this->getItem()->getDescription() ?> + </td> + <td> </td> + <td> </td> + <td> </td> + <td> </td> + <td> </td> + <td class="last"> </td> + </tr> +<?php endif; ?> diff --git a/app/design/adminhtml/default/default/template/bundle/sales/invoice/create/items/renderer.phtml b/app/design/adminhtml/default/default/template/bundle/sales/invoice/create/items/renderer.phtml index 2a91d10ef9..f521d37597 100644 --- a/app/design/adminhtml/default/default/template/bundle/sales/invoice/create/items/renderer.phtml +++ b/app/design/adminhtml/default/default/template/bundle/sales/invoice/create/items/renderer.phtml @@ -1,384 +1,398 @@ -<?php -/** - * 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_default - * @package Mage_Bundle - * @copyright Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com) - * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) - */ -?> -<?php -/** - * @see Mage_Bundle_Block_Adminhtml_Sales_Order_Items_Renderer - */ -?> - -<?php $_item = $this->getItem() ?> -<?php $items = $this->getChilds($_item); ?> -<?php $_count = count ($items) ?> -<?php $_index = 0 ?> - -<?php $_prevOptionId = '' ?> - -<?php if($this->getOrderOptions() || $_item->getDescription()): ?> - <?php $_showlastRow = true ?> -<?php else: ?> - <?php $_showlastRow = false ?> -<?php endif; ?> - -<?php foreach ($items as $_item): ?> - <?php $this->setPriceDataObject($_item) ?> - <?php if ($_item->getOrderItem()->getParentItem()): ?> - <?php $attributes = $this->getSelectionAttributes($_item) ?> - <?php if ($_prevOptionId != $attributes['option_id']): ?> - <tr> - <td><div class="option-label"><?php echo $attributes['option_label'] ?></div></td> - <td> </td> - <td> </td> - <td> </td> - <td> </td> - <td> </td> - <td> </td> - <td class="last"> </td> - </tr> - <?php $_prevOptionId = $attributes['option_id'] ?> - <?php endif; ?> - <?php endif; ?> - <tr<?php echo (++$_index==$_count && !$_showlastRow)?' class="border"':'' ?>> - <?php if (!$_item->getOrderItem()->getParentItem()): ?> - <td><h5 class="title"><?php echo $this->htmlEscape($_item->getName()) ?></h5> - <div> - <strong><?php echo $this->helper('sales')->__('SKU') ?>:</strong> - <?php echo implode('<br />', Mage::helper('catalog')->splitSku($_item->getSku())); ?> - </div> - </td> - <?php else: ?> - <td><div class="option-value"><?php echo $this->getValueHtml($_item)?></div></td> - <?php endif; ?> - <td class="a-right"> - <?php if ($this->canShowPriceInfo($_item)): ?> - <?php if ($this->helper('tax')->displayCartBothPrices() || $this->helper('tax')->displayCartPriceExclTax()): ?> - <span class="price-excl-tax"> - <?php if ($this->helper('tax')->displayCartBothPrices()): ?> - <span class="label"><?php echo $this->__('Excl. Tax'); ?>:</span> - <?php endif; ?> - - <?php if (Mage::helper('weee')->typeOfDisplay($_item, array(0, 1, 4), 'sales')): ?> - <?php - echo $this->displayPrices( - $_item->getBasePrice()+$_item->getBaseWeeeTaxAppliedAmount()+$_item->getBaseWeeeTaxDisposition(), - $_item->getPrice()+$_item->getWeeeTaxAppliedAmount()+$_item->getWeeeTaxDisposition() - ); - ?> - <?php else: ?> - <?php echo $this->displayPrices($_item->getBasePrice(), $_item->getPrice()) ?> - <?php endif; ?> - - - <?php if (Mage::helper('weee')->getApplied($_item)): ?> - <br /> - <?php if (Mage::helper('weee')->typeOfDisplay($_item, 1, 'sales')): ?> - <small> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount'], $tax['amount']); ?></span> - <?php endforeach; ?> - </small> - <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><small><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount_incl_tax'], $tax['amount_incl_tax']); ?></small></span> - <?php endforeach; ?> - <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 4, 'sales')): ?> - <small> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount_incl_tax'], $tax['amount_incl_tax']); ?></span> - <?php endforeach; ?> - </small> - <?php endif; ?> - - <?php if (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> - <br /> - <span class="nobr"><?php echo Mage::helper('weee')->__('Total'); ?>:<br /> - <?php - echo $this->displayPrices( - $_item->getBasePrice()+$_item->getBaseWeeeTaxAppliedAmount()+$_item->getBaseWeeeTaxDisposition(), - $_item->getPrice()+$_item->getWeeeTaxAppliedAmount()+$_item->getWeeeTaxDisposition() - ); - ?> - </span> - <?php endif; ?> - <?php endif; ?> - </span> - <br /> - <?php endif; ?> - <?php if ($this->helper('tax')->displayCartBothPrices() || $this->helper('tax')->displayCartPriceInclTax()): ?> - <span class="price-incl-tax"> - <?php if ($this->helper('tax')->displayCartBothPrices()): ?> - <span class="label"><?php echo $this->__('Incl. Tax'); ?>:</span> - <?php endif; ?> - <?php $_incl = $this->helper('checkout')->getPriceInclTax($_item); ?> - <?php $_baseIncl = $this->helper('checkout')->getBasePriceInclTax($_item); ?> - - <?php if (Mage::helper('weee')->typeOfDisplay($_item, array(0, 1, 4), 'sales')): ?> - <?php echo $this->displayPrices($_baseIncl+$_item->getBaseWeeeTaxAppliedAmount(), $_incl+$_item->getWeeeTaxAppliedAmount()); ?> - <?php else: ?> - <?php echo $this->displayPrices($_baseIncl-$_item->getBaseWeeeTaxDisposition(), $_incl-$_item->getWeeeTaxDisposition()) ?> - <?php endif; ?> - - <?php if (Mage::helper('weee')->getApplied($_item)): ?> - <br /> - <?php if (Mage::helper('weee')->typeOfDisplay($_item, 1, 'sales')): ?> - <small> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount'], $tax['amount']); ?></span> - <?php endforeach; ?> - </small> - <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><small><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount_incl_tax'], $tax['amount_incl_tax']); ?></small></span> - <?php endforeach; ?> - <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 4, 'sales')): ?> - <small> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount_incl_tax'], $tax['amount_incl_tax']); ?></span> - <?php endforeach; ?> - </small> - <?php endif; ?> - - <?php if (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> - <br /> - <span class="nobr"><?php echo Mage::helper('weee')->__('Total'); ?>:<br /> <?php echo $this->displayPrices($_baseIncl+$_item->getBaseWeeeTaxAppliedAmount(), $_incl+$_item->getWeeeTaxAppliedAmount()); ?></span> - <?php endif; ?> - <?php endif; ?> - </span> - <?php endif; ?> - <?php else: ?> -   - <?php endif; ?> - </td> - <td> - <?php if ($this->canShowPriceInfo($_item)): ?> - <table cellspacing="0" class="qty-table"> - <tr> - <td><?php echo Mage::helper('sales')->__('Ordered') ?></td> - <td><strong><?php echo $_item->getOrderItem()->getQtyOrdered()*1 ?></strong></td> - </tr> - <?php if ((float) $_item->getOrderItem()->getQtyInvoiced()): ?> - <tr> - <td><?php echo Mage::helper('sales')->__('Invoiced') ?></td> - <td><strong><?php echo $_item->getOrderItem()->getQtyInvoiced()*1 ?></strong></td> - </tr> - <?php endif; ?> - <?php if ((float) $_item->getOrderItem()->getQtyShipped() && $this->isShipmentSeparately($_item)): ?> - <tr> - <td><?php echo Mage::helper('sales')->__('Shipped') ?></td> - <td><strong><?php echo $_item->getOrderItem()->getQtyShipped()*1 ?></strong></td> - </tr> - <?php endif; ?> - <?php if ((float) $_item->getOrderItem()->getQtyRefunded()): ?> - <tr> - <td><?php echo Mage::helper('sales')->__('Refunded') ?></td> - <td><strong><?php echo $_item->getOrderItem()->getQtyRefunded()*1 ?></strong></td> - </tr> - <?php endif; ?> - <?php if ((float) $_item->getOrderItem()->getQtyCanceled()): ?> - <tr> - <td><?php echo Mage::helper('sales')->__('Canceled') ?></td> - <td><strong><?php echo $_item->getOrderItem()->getQtyCanceled()*1 ?></strong></td> - </tr> - <?php endif; ?> - </table> - <?php elseif ($this->isShipmentSeparately($_item)): ?> - <table cellspacing="0" class="qty-table"> - <tr> - <td><?php echo Mage::helper('sales')->__('Ordered') ?></td> - <td><strong><?php echo $_item->getOrderItem()->getQtyOrdered()*1 ?></strong></td> - </tr> - <?php if ((float) $_item->getOrderItem()->getQtyShipped()): ?> - <tr> - <td><?php echo Mage::helper('sales')->__('Shipped') ?></td> - <td><strong><?php echo $_item->getOrderItem()->getQtyShipped()*1 ?></strong></td> - </tr> - <?php endif; ?> - </table> - <?php else: ?> -   - <?php endif; ?> - </td> - <td class="a-center"> - <?php if ($this->canShowPriceInfo($_item)): ?> - <?php if ($this->canEditQty()) : ?> - <input type="text" class="input-text qty-input" name="invoice[items][<?php echo $_item->getOrderItemId() ?>]" value="<?php echo $_item->getQty()*1 ?>" /> - <?php else : ?> - <?php echo $_item->getQty()*1 ?> - <?php endif; ?> - <?php else: ?> -   - <?php endif; ?> - </td> - <td class="a-right"> - <?php if ($this->canShowPriceInfo($_item)): ?> - <?php if ($this->helper('tax')->displayCartBothPrices() || $this->helper('tax')->displayCartPriceExclTax()): ?> - <span class="price-excl-tax"> - <?php if ($this->helper('tax')->displayCartBothPrices()): ?> - <span class="label"><?php echo $this->__('Excl. Tax'); ?>:</span> - <?php endif; ?> - - <?php if (Mage::helper('weee')->typeOfDisplay($_item, array(0, 1, 4), 'sales')): ?> - <?php - echo $this->displayPrices( - $_item->getBaseRowTotal()+$_item->getBaseWeeeTaxAppliedRowAmount()+$_item->getBaseWeeeTaxRowDisposition(), - $_item->getRowTotal()+$_item->getWeeeTaxAppliedRowAmount()+$_item->getWeeeTaxRowDisposition() - ); - ?> - <?php else: ?> - <?php echo $this->displayPrices($_item->getBaseRowTotal(), $_item->getRowTotal()) ?> - <?php endif; ?> - - - <?php if (Mage::helper('weee')->getApplied($_item)): ?> - <?php if (Mage::helper('weee')->typeOfDisplay($_item, 1, 'sales')): ?> - <small> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount'], $tax['row_amount']); ?></span> - <?php endforeach; ?> - </small> - <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><small><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount_incl_tax'], $tax['row_amount_incl_tax']); ?></small></span> - <?php endforeach; ?> - <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 4, 'sales')): ?> - <small> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount_incl_tax'], $tax['row_amount_incl_tax']); ?></span> - <?php endforeach; ?> - </small> - <?php endif; ?> - - <?php if (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> - <br /> - <span class="nobr"><?php echo Mage::helper('weee')->__('Total'); ?>:<br /> - <?php - echo $this->displayPrices( - $_item->getBaseRowTotal()+$_item->getBaseWeeeTaxAppliedRowAmount()+$_item->getBaseWeeeTaxRowDisposition(), - $_item->getRowTotal()+$_item->getWeeeTaxAppliedRowAmount()+$_item->getWeeeTaxRowDisposition() - ); - ?> - </span> - <?php endif; ?> - <?php endif; ?> - </span> - <br /> - <?php endif; ?> - <?php if ($this->helper('tax')->displayCartBothPrices() || $this->helper('tax')->displayCartPriceInclTax()): ?> - <span class="price-incl-tax"> - <?php if ($this->helper('tax')->displayCartBothPrices()): ?> - <span class="label"><?php echo $this->__('Incl. Tax'); ?>:</span> - <?php endif; ?> - <?php $_incl = $this->helper('checkout')->getSubtotalInclTax($_item); ?> - <?php $_baseIncl = $this->helper('checkout')->getBaseSubtotalInclTax($_item); ?> - <?php if (Mage::helper('weee')->typeOfDisplay($_item, array(0, 1, 4), 'sales')): ?> - <?php echo $this->displayPrices($_baseIncl+$_item->getBaseWeeeTaxAppliedRowAmount(), $_incl+$_item->getWeeeTaxAppliedRowAmount()); ?> - <?php else: ?> - <?php echo $this->displayPrices($_baseIncl-$_item->getBaseWeeeTaxRowDisposition(), $_incl-$_item->getWeeeTaxRowDisposition()) ?> - <?php endif; ?> - - - <?php if (Mage::helper('weee')->getApplied($_item)): ?> - - <br /> - <?php if (Mage::helper('weee')->typeOfDisplay($_item, 1, 'sales')): ?> - <small> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount'], $tax['row_amount']); ?></span> - <?php endforeach; ?> - </small> - <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><small><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount_incl_tax'], $tax['row_amount_incl_tax']); ?></small></span> - <?php endforeach; ?> - <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 4, 'sales')): ?> - <small> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount_incl_tax'], $tax['row_amount_incl_tax']); ?></span> - <?php endforeach; ?> - </small> - <?php endif; ?> - - <?php if (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> - <br /><span class="nobr"><?php echo Mage::helper('weee')->__('Total'); ?>:<br /> <?php echo $this->displayPrices($_baseIncl+$_item->getBaseWeeeTaxAppliedRowAmount(), $_incl+$_item->getWeeeTaxAppliedRowAmount()); ?></span> - <?php endif; ?> - <?php endif; ?> - </span> - <?php endif; ?> - </span> - <?php else: ?> -   - <?php endif; ?> - </td> - <td class="a-right"> - <?php if ($this->canShowPriceInfo($_item)): ?> - <?php echo $this->displayPriceAttribute('tax_amount') ?> - <?php else: ?> -   - <?php endif; ?> - </td> - <td class="a-right"> - <?php if ($this->canShowPriceInfo($_item)): ?> - <?php echo $this->displayPriceAttribute('discount_amount') ?> - <?php else: ?> -   - <?php endif; ?> - </td> - <td class="a-right last"> - <?php if ($this->canShowPriceInfo($_item)): ?> - <?php echo $this->displayPrices( - $_item->getBaseRowTotal()+$_item->getBaseTaxAmount()-$_item->getBaseDiscountAmount()+$_item->getBaseWeeeTaxAppliedRowAmount(), - $_item->getRowTotal()+$_item->getTaxAmount()-$_item->getDiscountAmount()+$_item->getWeeeTaxAppliedRowAmount() - ) ?> - <?php else: ?> -   - <?php endif; ?> - </td> - </tr> -<?php endforeach; ?> -<?php if($_showlastRow): ?> - <tr class="border"> - <td> - <?php if ($this->getOrderOptions($_item->getOrderItem())): ?> - <dl class="item-options"> - <?php foreach ($this->getOrderOptions($_item->getOrderItem()) as $option): ?> - <dt><?php echo $option['label'] ?></dt> - <dd><?php echo $this->htmlEscape($option['value']); ?></dd> - <?php endforeach; ?> - </dl> - <?php else: ?> -   - <?php endif; ?> - <?php echo $_item->getDescription() ?> - </td> - <td> </td> - <td> </td> - <td> </td> - <td> </td> - <td> </td> - <td> </td> - <td class="last"> </td> - </tr> -<?php endif; ?> +<?php +/** + * 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_default + * @package Mage_Bundle + * @copyright Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com) + * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) + */ +?> +<?php +/** + * @see Mage_Bundle_Block_Adminhtml_Sales_Order_Items_Renderer + */ +?> + +<?php $_item = $this->getItem() ?> +<?php $items = $this->getChilds($_item); ?> +<?php $_count = count ($items) ?> +<?php $_index = 0 ?> + +<?php $_prevOptionId = '' ?> + +<?php if($this->getOrderOptions() || $_item->getDescription()): ?> + <?php $_showlastRow = true ?> +<?php else: ?> + <?php $_showlastRow = false ?> +<?php endif; ?> + +<?php foreach ($items as $_item): ?> + <?php $this->setPriceDataObject($_item) ?> + <?php if ($_item->getOrderItem()->getParentItem()): ?> + <?php $attributes = $this->getSelectionAttributes($_item) ?> + <?php if ($_prevOptionId != $attributes['option_id']): ?> + <tr> + <td><div class="option-label"><?php echo $attributes['option_label'] ?></div></td> + <td> </td> + <td> </td> + <td> </td> + <td> </td> + <td> </td> + <td> </td> + <td class="last"> </td> + </tr> + <?php $_prevOptionId = $attributes['option_id'] ?> + <?php endif; ?> + <?php endif; ?> + <tr<?php echo (++$_index==$_count && !$_showlastRow)?' class="border"':'' ?>> + <?php if (!$_item->getOrderItem()->getParentItem()): ?> + <td><h5 class="title"><?php echo $this->htmlEscape($_item->getName()) ?></h5> + <div> + <strong><?php echo $this->helper('sales')->__('SKU') ?>:</strong> + <?php echo implode('<br />', Mage::helper('catalog')->splitSku($_item->getSku())); ?> + </div> + </td> + <?php else: ?> + <td><div class="option-value"><?php echo $this->getValueHtml($_item)?></div></td> + <?php endif; ?> + <td class="a-right"> + <?php if ($this->canShowPriceInfo($_item)): ?> + <?php if ($this->helper('tax')->displayCartBothPrices() || $this->helper('tax')->displayCartPriceExclTax()): ?> + <span class="price-excl-tax"> + <?php if ($this->helper('tax')->displayCartBothPrices()): ?> + <span class="label"><?php echo $this->__('Excl. Tax'); ?>:</span> + <?php endif; ?> + + <?php if (Mage::helper('weee')->typeOfDisplay($_item, array(0, 1, 4), 'sales')): ?> + <?php + echo $this->displayPrices( + $_item->getBasePrice()+$_item->getBaseWeeeTaxAppliedAmount()+$_item->getBaseWeeeTaxDisposition(), + $_item->getPrice()+$_item->getWeeeTaxAppliedAmount()+$_item->getWeeeTaxDisposition() + ); + ?> + <?php else: ?> + <?php echo $this->displayPrices($_item->getBasePrice(), $_item->getPrice()) ?> + <?php endif; ?> + + + <?php if (Mage::helper('weee')->getApplied($_item)): ?> + <br /> + <?php if (Mage::helper('weee')->typeOfDisplay($_item, 1, 'sales')): ?> + <small> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount'], $tax['amount']); ?></span> + <?php endforeach; ?> + </small> + <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><small><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount_incl_tax'], $tax['amount_incl_tax']); ?></small></span> + <?php endforeach; ?> + <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 4, 'sales')): ?> + <small> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount_incl_tax'], $tax['amount_incl_tax']); ?></span> + <?php endforeach; ?> + </small> + <?php endif; ?> + + <?php if (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> + <br /> + <span class="nobr"><?php echo Mage::helper('weee')->__('Total'); ?>:<br /> + <?php + echo $this->displayPrices( + $_item->getBasePrice()+$_item->getBaseWeeeTaxAppliedAmount()+$_item->getBaseWeeeTaxDisposition(), + $_item->getPrice()+$_item->getWeeeTaxAppliedAmount()+$_item->getWeeeTaxDisposition() + ); + ?> + </span> + <?php endif; ?> + <?php endif; ?> + </span> + <br /> + <?php endif; ?> + <?php if ($this->helper('tax')->displayCartBothPrices() || $this->helper('tax')->displayCartPriceInclTax()): ?> + <span class="price-incl-tax"> + <?php if ($this->helper('tax')->displayCartBothPrices()): ?> + <span class="label"><?php echo $this->__('Incl. Tax'); ?>:</span> + <?php endif; ?> + <?php $_incl = $this->helper('checkout')->getPriceInclTax($_item); ?> + <?php $_baseIncl = $this->helper('checkout')->getBasePriceInclTax($_item); ?> + + <?php if (Mage::helper('weee')->typeOfDisplay($_item, array(0, 1, 4), 'sales')): ?> + <?php echo $this->displayPrices($_baseIncl+$_item->getBaseWeeeTaxAppliedAmount(), $_incl+$_item->getWeeeTaxAppliedAmount()); ?> + <?php else: ?> + <?php echo $this->displayPrices($_baseIncl-$_item->getBaseWeeeTaxDisposition(), $_incl-$_item->getWeeeTaxDisposition()) ?> + <?php endif; ?> + + <?php if (Mage::helper('weee')->getApplied($_item)): ?> + <br /> + <?php if (Mage::helper('weee')->typeOfDisplay($_item, 1, 'sales')): ?> + <small> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount'], $tax['amount']); ?></span> + <?php endforeach; ?> + </small> + <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><small><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount_incl_tax'], $tax['amount_incl_tax']); ?></small></span> + <?php endforeach; ?> + <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 4, 'sales')): ?> + <small> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount_incl_tax'], $tax['amount_incl_tax']); ?></span> + <?php endforeach; ?> + </small> + <?php endif; ?> + + <?php if (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> + <br /> + <span class="nobr"><?php echo Mage::helper('weee')->__('Total'); ?>:<br /> <?php echo $this->displayPrices($_baseIncl+$_item->getBaseWeeeTaxAppliedAmount(), $_incl+$_item->getWeeeTaxAppliedAmount()); ?></span> + <?php endif; ?> + <?php endif; ?> + </span> + <?php endif; ?> + <?php else: ?> +   + <?php endif; ?> + </td> + <td> + <?php if ($this->canShowPriceInfo($_item)): ?> + <table cellspacing="0" class="qty-table"> + <tr> + <td><?php echo Mage::helper('sales')->__('Ordered') ?></td> + <td><strong><?php echo $_item->getOrderItem()->getQtyOrdered()*1 ?></strong></td> + </tr> + <?php if ((float) $_item->getOrderItem()->getQtyInvoiced()): ?> + <tr> + <td><?php echo Mage::helper('sales')->__('Invoiced') ?></td> + <td><strong><?php echo $_item->getOrderItem()->getQtyInvoiced()*1 ?></strong></td> + </tr> + <?php endif; ?> + <?php if ((float) $_item->getOrderItem()->getQtyShipped() && $this->isShipmentSeparately($_item)): ?> + <tr> + <td><?php echo Mage::helper('sales')->__('Shipped') ?></td> + <td><strong><?php echo $_item->getOrderItem()->getQtyShipped()*1 ?></strong></td> + </tr> + <?php endif; ?> + <?php if ((float) $_item->getOrderItem()->getQtyRefunded()): ?> + <tr> + <td><?php echo Mage::helper('sales')->__('Refunded') ?></td> + <td><strong><?php echo $_item->getOrderItem()->getQtyRefunded()*1 ?></strong></td> + </tr> + <?php endif; ?> + <?php if ((float) $_item->getOrderItem()->getQtyCanceled()): ?> + <tr> + <td><?php echo Mage::helper('sales')->__('Canceled') ?></td> + <td><strong><?php echo $_item->getOrderItem()->getQtyCanceled()*1 ?></strong></td> + </tr> + <?php endif; ?> + </table> + <?php elseif ($this->isShipmentSeparately($_item)): ?> + <table cellspacing="0" class="qty-table"> + <tr> + <td><?php echo Mage::helper('sales')->__('Ordered') ?></td> + <td><strong><?php echo $_item->getOrderItem()->getQtyOrdered()*1 ?></strong></td> + </tr> + <?php if ((float) $_item->getOrderItem()->getQtyShipped()): ?> + <tr> + <td><?php echo Mage::helper('sales')->__('Shipped') ?></td> + <td><strong><?php echo $_item->getOrderItem()->getQtyShipped()*1 ?></strong></td> + </tr> + <?php endif; ?> + </table> + <?php else: ?> +   + <?php endif; ?> + </td> + <td class="a-center"> + <?php if ($this->canShowPriceInfo($_item)): ?> + <?php if ($this->canEditQty()) : ?> + <input type="text" class="input-text qty-input" name="invoice[items][<?php echo $_item->getOrderItemId() ?>]" value="<?php echo $_item->getQty()*1 ?>" /> + <?php else : ?> + <?php echo $_item->getQty()*1 ?> + <?php endif; ?> + <?php else: ?> +   + <?php endif; ?> + </td> + <td class="a-right"> + <?php if ($this->canShowPriceInfo($_item)): ?> + <?php if ($this->helper('tax')->displayCartBothPrices() || $this->helper('tax')->displayCartPriceExclTax()): ?> + <span class="price-excl-tax"> + <?php if ($this->helper('tax')->displayCartBothPrices()): ?> + <span class="label"><?php echo $this->__('Excl. Tax'); ?>:</span> + <?php endif; ?> + + <?php if (Mage::helper('weee')->typeOfDisplay($_item, array(0, 1, 4), 'sales')): ?> + <?php + echo $this->displayPrices( + $_item->getBaseRowTotal()+$_item->getBaseWeeeTaxAppliedRowAmount()+$_item->getBaseWeeeTaxRowDisposition(), + $_item->getRowTotal()+$_item->getWeeeTaxAppliedRowAmount()+$_item->getWeeeTaxRowDisposition() + ); + ?> + <?php else: ?> + <?php echo $this->displayPrices($_item->getBaseRowTotal(), $_item->getRowTotal()) ?> + <?php endif; ?> + + + <?php if (Mage::helper('weee')->getApplied($_item)): ?> + <?php if (Mage::helper('weee')->typeOfDisplay($_item, 1, 'sales')): ?> + <small> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount'], $tax['row_amount']); ?></span> + <?php endforeach; ?> + </small> + <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><small><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount_incl_tax'], $tax['row_amount_incl_tax']); ?></small></span> + <?php endforeach; ?> + <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 4, 'sales')): ?> + <small> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount_incl_tax'], $tax['row_amount_incl_tax']); ?></span> + <?php endforeach; ?> + </small> + <?php endif; ?> + + <?php if (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> + <br /> + <span class="nobr"><?php echo Mage::helper('weee')->__('Total'); ?>:<br /> + <?php + echo $this->displayPrices( + $_item->getBaseRowTotal()+$_item->getBaseWeeeTaxAppliedRowAmount()+$_item->getBaseWeeeTaxRowDisposition(), + $_item->getRowTotal()+$_item->getWeeeTaxAppliedRowAmount()+$_item->getWeeeTaxRowDisposition() + ); + ?> + </span> + <?php endif; ?> + <?php endif; ?> + </span> + <br /> + <?php endif; ?> + <?php if ($this->helper('tax')->displayCartBothPrices() || $this->helper('tax')->displayCartPriceInclTax()): ?> + <span class="price-incl-tax"> + <?php if ($this->helper('tax')->displayCartBothPrices()): ?> + <span class="label"><?php echo $this->__('Incl. Tax'); ?>:</span> + <?php endif; ?> + <?php $_incl = $this->helper('checkout')->getSubtotalInclTax($_item); ?> + <?php $_baseIncl = $this->helper('checkout')->getBaseSubtotalInclTax($_item); ?> + <?php if (Mage::helper('weee')->typeOfDisplay($_item, array(0, 1, 4), 'sales')): ?> + <?php echo $this->displayPrices($_baseIncl+$_item->getBaseWeeeTaxAppliedRowAmount(), $_incl+$_item->getWeeeTaxAppliedRowAmount()); ?> + <?php else: ?> + <?php echo $this->displayPrices($_baseIncl-$_item->getBaseWeeeTaxRowDisposition(), $_incl-$_item->getWeeeTaxRowDisposition()) ?> + <?php endif; ?> + + + <?php if (Mage::helper('weee')->getApplied($_item)): ?> + + <br /> + <?php if (Mage::helper('weee')->typeOfDisplay($_item, 1, 'sales')): ?> + <small> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount'], $tax['row_amount']); ?></span> + <?php endforeach; ?> + </small> + <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><small><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount_incl_tax'], $tax['row_amount_incl_tax']); ?></small></span> + <?php endforeach; ?> + <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 4, 'sales')): ?> + <small> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount_incl_tax'], $tax['row_amount_incl_tax']); ?></span> + <?php endforeach; ?> + </small> + <?php endif; ?> + + <?php if (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> + <br /><span class="nobr"><?php echo Mage::helper('weee')->__('Total'); ?>:<br /> <?php echo $this->displayPrices($_baseIncl+$_item->getBaseWeeeTaxAppliedRowAmount(), $_incl+$_item->getWeeeTaxAppliedRowAmount()); ?></span> + <?php endif; ?> + <?php endif; ?> + </span> + <?php endif; ?> + </span> + <?php else: ?> +   + <?php endif; ?> + </td> + <td class="a-right"> + <?php if ($this->canShowPriceInfo($_item)): ?> + <?php echo $this->displayPriceAttribute('tax_amount') ?> + <?php else: ?> +   + <?php endif; ?> + </td> + <td class="a-right"> + <?php if ($this->canShowPriceInfo($_item)): ?> + <?php echo $this->displayPriceAttribute('discount_amount') ?> + <?php else: ?> +   + <?php endif; ?> + </td> + <td class="a-right last"> + <?php if ($this->canShowPriceInfo($_item)): ?> + <?php echo $this->displayPrices( + $_item->getBaseRowTotal()+$_item->getBaseTaxAmount()-$_item->getBaseDiscountAmount()+$_item->getBaseWeeeTaxAppliedRowAmount(), + $_item->getRowTotal()+$_item->getTaxAmount()-$_item->getDiscountAmount()+$_item->getWeeeTaxAppliedRowAmount() + ) ?> + <?php else: ?> +   + <?php endif; ?> + </td> + </tr> +<?php endforeach; ?> +<?php if($_showlastRow): ?> + <tr class="border"> + <td> + <?php if ($this->getOrderOptions($_item->getOrderItem())): ?> + <dl class="item-options"> + <?php foreach ($this->getOrderOptions($_item->getOrderItem()) as $option): ?> + <dt><?php echo $option['label'] ?></dt> + <dd> + <?php if (isset($option['custom_view']) && $option['custom_view']): ?> + <?php echo $option['value'];?> + <?php else: ?> + <?php echo Mage::helper('core/string')->truncate($option['value'], 55, '', $_remainder);?> + <?php if ($_remainder):?> + ... <span id="<?php echo $_id = 'id' . uniqid()?>"><?php echo $_remainder ?></span> + <script type="text/javascript"> + $('<?php echo $_id ?>').hide(); + $('<?php echo $_id ?>').up().observe('mouseover', function(){$('<?php echo $_id ?>').show();}); + $('<?php echo $_id ?>').up().observe('mouseout', function(){$('<?php echo $_id ?>').hide();}); + </script> + <?php endif;?> + <?php endif;?> + </dd> + <?php endforeach; ?> + </dl> + <?php else: ?> +   + <?php endif; ?> + <?php echo $_item->getDescription() ?> + </td> + <td> </td> + <td> </td> + <td> </td> + <td> </td> + <td> </td> + <td> </td> + <td class="last"> </td> + </tr> +<?php endif; ?> diff --git a/app/design/adminhtml/default/default/template/bundle/sales/invoice/view/items/renderer.phtml b/app/design/adminhtml/default/default/template/bundle/sales/invoice/view/items/renderer.phtml index fcfb192ad6..17410aa371 100644 --- a/app/design/adminhtml/default/default/template/bundle/sales/invoice/view/items/renderer.phtml +++ b/app/design/adminhtml/default/default/template/bundle/sales/invoice/view/items/renderer.phtml @@ -1,326 +1,340 @@ -<?php -/** - * 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_default - * @package Mage_Bundle - * @copyright Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com) - * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) - */ -?> -<?php -/** - * @see Mage_Bundle_Block_Adminhtml_Sales_Order_Items_Renderer - */ -?> - -<?php $_item = $this->getItem() ?> -<?php $items = $this->getChilds($_item); ?> -<?php $_count = count ($items) ?> -<?php $_index = 0 ?> - -<?php $_prevOptionId = '' ?> - -<?php if($this->getOrderOptions() || $_item->getDescription()): ?> - <?php $_showlastRow = true ?> -<?php else: ?> - <?php $_showlastRow = false ?> -<?php endif; ?> - -<?php foreach ($items as $_item): ?> - <?php $this->setPriceDataObject($_item) ?> - <?php if ($_item->getOrderItem()->getParentItem()): ?> - <?php $attributes = $this->getSelectionAttributes($_item) ?> - <?php if ($_prevOptionId != $attributes['option_id']): ?> - <tr> - <td><div class="option-label"><?php echo $attributes['option_label'] ?></div></td> - <td> </td> - <td> </td> - <td> </td> - <td> </td> - <td> </td> - <td class="last"> </td> - </tr> - <?php $_prevOptionId = $attributes['option_id'] ?> - <?php endif; ?> - <?php endif; ?> - <tr<?php echo (++$_index==$_count && !$_showlastRow)?' class="border"':'' ?>> - <?php if (!$_item->getOrderItem()->getParentItem()): ?> - <td><h5 class="title"><?php echo $this->htmlEscape($_item->getName()) ?></h5> - <div> - <strong><?php echo $this->helper('sales')->__('SKU') ?>:</strong> - <?php echo implode('<br />', Mage::helper('catalog')->splitSku($_item->getSku())); ?> - </div> - <?php else: ?> - <td><div class="option-value"><?php echo $this->getValueHtml($_item)?></div></td> - <?php endif; ?> - <td class="a-right"> - <?php if ($this->canShowPriceInfo($_item)): ?> - <?php if ($this->helper('tax')->displayCartBothPrices() || $this->helper('tax')->displayCartPriceExclTax()): ?> - <span class="price-excl-tax"> - <?php if ($this->helper('tax')->displayCartBothPrices()): ?> - <span class="label"><?php echo $this->__('Excl. Tax'); ?>:</span> - <?php endif; ?> - - <?php if (Mage::helper('weee')->typeOfDisplay($_item, array(0, 1, 4), 'sales')): ?> - <?php - echo $this->displayPrices( - $_item->getBasePrice()+$_item->getBaseWeeeTaxAppliedAmount()+$_item->getBaseWeeeTaxDisposition(), - $_item->getPrice()+$_item->getWeeeTaxAppliedAmount()+$_item->getWeeeTaxDisposition() - ); - ?> - <?php else: ?> - <?php echo $this->displayPrices($_item->getBasePrice(), $_item->getPrice()) ?> - <?php endif; ?> - - - <?php if (Mage::helper('weee')->getApplied($_item)): ?> - <br /> - <?php if (Mage::helper('weee')->typeOfDisplay($_item, 1, 'sales')): ?> - <small> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount'], $tax['amount']); ?></span> - <?php endforeach; ?> - </small> - <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><small><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount_incl_tax'], $tax['amount_incl_tax']); ?></small></span> - <?php endforeach; ?> - <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 4, 'sales')): ?> - <small> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount_incl_tax'], $tax['amount_incl_tax']); ?></span> - <?php endforeach; ?> - </small> - <?php endif; ?> - - <?php if (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> - <br /> - <span class="nobr"><?php echo Mage::helper('weee')->__('Total'); ?>:<br /> - <?php - echo $this->displayPrices( - $_item->getBasePrice()+$_item->getBaseWeeeTaxAppliedAmount()+$_item->getBaseWeeeTaxDisposition(), - $_item->getPrice()+$_item->getWeeeTaxAppliedAmount()+$_item->getWeeeTaxDisposition() - ); - ?> - </span> - <?php endif; ?> - <?php endif; ?> - </span> - <br /> - <?php endif; ?> - <?php if ($this->helper('tax')->displayCartBothPrices() || $this->helper('tax')->displayCartPriceInclTax()): ?> - <span class="price-incl-tax"> - <?php if ($this->helper('tax')->displayCartBothPrices()): ?> - <span class="label"><?php echo $this->__('Incl. Tax'); ?>:</span> - <?php endif; ?> - <?php $_incl = $this->helper('checkout')->getPriceInclTax($_item); ?> - <?php $_baseIncl = $this->helper('checkout')->getBasePriceInclTax($_item); ?> - - <?php if (Mage::helper('weee')->typeOfDisplay($_item, array(0, 1, 4), 'sales')): ?> - <?php echo $this->displayPrices($_baseIncl+$_item->getBaseWeeeTaxAppliedAmount(), $_incl+$_item->getWeeeTaxAppliedAmount()); ?> - <?php else: ?> - <?php echo $this->displayPrices($_baseIncl-$_item->getBaseWeeeTaxDisposition(), $_incl-$_item->getWeeeTaxDisposition()) ?> - <?php endif; ?> - - <?php if (Mage::helper('weee')->getApplied($_item)): ?> - <br /> - <?php if (Mage::helper('weee')->typeOfDisplay($_item, 1, 'sales')): ?> - <small> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount'], $tax['amount']); ?></span> - <?php endforeach; ?> - </small> - <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><small><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount_incl_tax'], $tax['amount_incl_tax']); ?></small></span> - <?php endforeach; ?> - <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 4, 'sales')): ?> - <small> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount_incl_tax'], $tax['amount_incl_tax']); ?></span> - <?php endforeach; ?> - </small> - <?php endif; ?> - - <?php if (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> - <br /> - <span class="nobr"><?php echo Mage::helper('weee')->__('Total'); ?>:<br /> <?php echo $this->displayPrices($_baseIncl+$_item->getBaseWeeeTaxAppliedAmount(), $_incl+$_item->getWeeeTaxAppliedAmount()); ?></span> - <?php endif; ?> - <?php endif; ?> - </span> - <?php endif; ?> - <?php else: ?> -   - <?php endif; ?> - </td> - <td class="a-center"> - <?php if ($this->canShowPriceInfo($_item)): ?> - <?php echo $_item->getQty()*1 ?> - <?php else: ?> -   - <?php endif; ?> - </td> - <td class="a-right"> - <?php if ($this->canShowPriceInfo($_item)): ?> - <?php if ($this->helper('tax')->displayCartBothPrices() || $this->helper('tax')->displayCartPriceExclTax()): ?> - <span class="price-excl-tax"> - <?php if ($this->helper('tax')->displayCartBothPrices()): ?> - <span class="label"><?php echo $this->__('Excl. Tax'); ?>:</span> - <?php endif; ?> - - <?php if (Mage::helper('weee')->typeOfDisplay($_item, array(0, 1, 4), 'sales')): ?> - <?php - echo $this->displayPrices( - $_item->getBaseRowTotal()+$_item->getBaseWeeeTaxAppliedRowAmount()+$_item->getBaseWeeeTaxRowDisposition(), - $_item->getRowTotal()+$_item->getWeeeTaxAppliedRowAmount()+$_item->getWeeeTaxRowDisposition() - ); - ?> - <?php else: ?> - <?php echo $this->displayPrices($_item->getBaseRowTotal(), $_item->getRowTotal()) ?> - <?php endif; ?> - - - <?php if (Mage::helper('weee')->getApplied($_item)): ?> - <?php if (Mage::helper('weee')->typeOfDisplay($_item, 1, 'sales')): ?> - <small> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount'], $tax['row_amount']); ?></span> - <?php endforeach; ?> - </small> - <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><small><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount_incl_tax'], $tax['row_amount_incl_tax']); ?></small></span> - <?php endforeach; ?> - <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 4, 'sales')): ?> - <small> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount_incl_tax'], $tax['row_amount_incl_tax']); ?></span> - <?php endforeach; ?> - </small> - <?php endif; ?> - - <?php if (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> - <br /> - <span class="nobr"><?php echo Mage::helper('weee')->__('Total'); ?>:<br /> - <?php - echo $this->displayPrices( - $_item->getBaseRowTotal()+$_item->getBaseWeeeTaxAppliedRowAmount()+$_item->getBaseWeeeTaxRowDisposition(), - $_item->getRowTotal()+$_item->getWeeeTaxAppliedRowAmount()+$_item->getWeeeTaxRowDisposition() - ); - ?> - </span> - <?php endif; ?> - <?php endif; ?> - </span> - <br /> - <?php endif; ?> - <?php if ($this->helper('tax')->displayCartBothPrices() || $this->helper('tax')->displayCartPriceInclTax()): ?> - <span class="price-incl-tax"> - <?php if ($this->helper('tax')->displayCartBothPrices()): ?> - <span class="label"><?php echo $this->__('Incl. Tax'); ?>:</span> - <?php endif; ?> - <?php $_incl = $this->helper('checkout')->getSubtotalInclTax($_item); ?> - <?php $_baseIncl = $this->helper('checkout')->getBaseSubtotalInclTax($_item); ?> - <?php if (Mage::helper('weee')->typeOfDisplay($_item, array(0, 1, 4), 'sales')): ?> - <?php echo $this->displayPrices($_baseIncl+$_item->getBaseWeeeTaxAppliedRowAmount(), $_incl+$_item->getWeeeTaxAppliedRowAmount()); ?> - <?php else: ?> - <?php echo $this->displayPrices($_baseIncl-$_item->getBaseWeeeTaxRowDisposition(), $_incl-$_item->getWeeeTaxRowDisposition()) ?> - <?php endif; ?> - - - <?php if (Mage::helper('weee')->getApplied($_item)): ?> - - <br /> - <?php if (Mage::helper('weee')->typeOfDisplay($_item, 1, 'sales')): ?> - <small> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount'], $tax['row_amount']); ?></span> - <?php endforeach; ?> - </small> - <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><small><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount_incl_tax'], $tax['row_amount_incl_tax']); ?></small></span> - <?php endforeach; ?> - <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 4, 'sales')): ?> - <small> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount_incl_tax'], $tax['row_amount_incl_tax']); ?></span> - <?php endforeach; ?> - </small> - <?php endif; ?> - - <?php if (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> - <br /><span class="nobr"><?php echo Mage::helper('weee')->__('Total'); ?>:<br /> <?php echo $this->displayPrices($_baseIncl+$_item->getBaseWeeeTaxAppliedRowAmount(), $_incl+$_item->getWeeeTaxAppliedRowAmount()); ?></span> - <?php endif; ?> - <?php endif; ?> - </span> - <?php endif; ?> - </span> - <?php else: ?> -   - <?php endif; ?> - </td> - <td class="a-right"> - <?php if ($this->canShowPriceInfo($_item)): ?> - <?php echo $this->displayPriceAttribute('tax_amount') ?> - <?php else: ?> -   - <?php endif; ?> - </td> - <td class="a-right"> - <?php if ($this->canShowPriceInfo($_item)): ?> - <?php echo $this->displayPriceAttribute('discount_amount') ?> - <?php else: ?> -   - <?php endif; ?> - </td> - <td class="a-right last"> - <?php if ($this->canShowPriceInfo($_item)): ?> - <?php echo $this->displayPrices( - $_item->getBaseRowTotal()+$_item->getBaseTaxAmount()-$_item->getBaseDiscountAmount()+$_item->getBaseWeeeTaxAppliedRowAmount(), - $_item->getRowTotal()+$_item->getTaxAmount()-$_item->getDiscountAmount()+$_item->getWeeeTaxAppliedRowAmount() - ) ?> - <?php else: ?> -   - <?php endif; ?> - </td> - </tr> -<?php endforeach; ?> -<?php if($_showlastRow): ?> - <tr class="border"> - <td> - <?php if ($this->getOrderOptions()): ?> - <dl class="item-options"> - <?php foreach ($this->getOrderOptions() as $option): ?> - <dt><?php echo $option['label'] ?></dt> - <dd><?php echo $this->htmlEscape($option['value']); ?></dd> - <?php endforeach; ?> - </dl> - <?php endif; ?> - <?php echo $this->getItem()->getDescription() ?> - </td> - <td> </td> - <td> </td> - <td> </td> - <td> </td> - <td> </td> - <td class="last"> </td> - </tr> -<?php endif; ?> +<?php +/** + * 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_default + * @package Mage_Bundle + * @copyright Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com) + * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) + */ +?> +<?php +/** + * @see Mage_Bundle_Block_Adminhtml_Sales_Order_Items_Renderer + */ +?> + +<?php $_item = $this->getItem() ?> +<?php $items = $this->getChilds($_item); ?> +<?php $_count = count ($items) ?> +<?php $_index = 0 ?> + +<?php $_prevOptionId = '' ?> + +<?php if($this->getOrderOptions() || $_item->getDescription()): ?> + <?php $_showlastRow = true ?> +<?php else: ?> + <?php $_showlastRow = false ?> +<?php endif; ?> + +<?php foreach ($items as $_item): ?> + <?php $this->setPriceDataObject($_item) ?> + <?php if ($_item->getOrderItem()->getParentItem()): ?> + <?php $attributes = $this->getSelectionAttributes($_item) ?> + <?php if ($_prevOptionId != $attributes['option_id']): ?> + <tr> + <td><div class="option-label"><?php echo $attributes['option_label'] ?></div></td> + <td> </td> + <td> </td> + <td> </td> + <td> </td> + <td> </td> + <td class="last"> </td> + </tr> + <?php $_prevOptionId = $attributes['option_id'] ?> + <?php endif; ?> + <?php endif; ?> + <tr<?php echo (++$_index==$_count && !$_showlastRow)?' class="border"':'' ?>> + <?php if (!$_item->getOrderItem()->getParentItem()): ?> + <td><h5 class="title"><?php echo $this->htmlEscape($_item->getName()) ?></h5> + <div> + <strong><?php echo $this->helper('sales')->__('SKU') ?>:</strong> + <?php echo implode('<br />', Mage::helper('catalog')->splitSku($_item->getSku())); ?> + </div> + <?php else: ?> + <td><div class="option-value"><?php echo $this->getValueHtml($_item)?></div></td> + <?php endif; ?> + <td class="a-right"> + <?php if ($this->canShowPriceInfo($_item)): ?> + <?php if ($this->helper('tax')->displayCartBothPrices() || $this->helper('tax')->displayCartPriceExclTax()): ?> + <span class="price-excl-tax"> + <?php if ($this->helper('tax')->displayCartBothPrices()): ?> + <span class="label"><?php echo $this->__('Excl. Tax'); ?>:</span> + <?php endif; ?> + + <?php if (Mage::helper('weee')->typeOfDisplay($_item, array(0, 1, 4), 'sales')): ?> + <?php + echo $this->displayPrices( + $_item->getBasePrice()+$_item->getBaseWeeeTaxAppliedAmount()+$_item->getBaseWeeeTaxDisposition(), + $_item->getPrice()+$_item->getWeeeTaxAppliedAmount()+$_item->getWeeeTaxDisposition() + ); + ?> + <?php else: ?> + <?php echo $this->displayPrices($_item->getBasePrice(), $_item->getPrice()) ?> + <?php endif; ?> + + + <?php if (Mage::helper('weee')->getApplied($_item)): ?> + <br /> + <?php if (Mage::helper('weee')->typeOfDisplay($_item, 1, 'sales')): ?> + <small> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount'], $tax['amount']); ?></span> + <?php endforeach; ?> + </small> + <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><small><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount_incl_tax'], $tax['amount_incl_tax']); ?></small></span> + <?php endforeach; ?> + <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 4, 'sales')): ?> + <small> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount_incl_tax'], $tax['amount_incl_tax']); ?></span> + <?php endforeach; ?> + </small> + <?php endif; ?> + + <?php if (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> + <br /> + <span class="nobr"><?php echo Mage::helper('weee')->__('Total'); ?>:<br /> + <?php + echo $this->displayPrices( + $_item->getBasePrice()+$_item->getBaseWeeeTaxAppliedAmount()+$_item->getBaseWeeeTaxDisposition(), + $_item->getPrice()+$_item->getWeeeTaxAppliedAmount()+$_item->getWeeeTaxDisposition() + ); + ?> + </span> + <?php endif; ?> + <?php endif; ?> + </span> + <br /> + <?php endif; ?> + <?php if ($this->helper('tax')->displayCartBothPrices() || $this->helper('tax')->displayCartPriceInclTax()): ?> + <span class="price-incl-tax"> + <?php if ($this->helper('tax')->displayCartBothPrices()): ?> + <span class="label"><?php echo $this->__('Incl. Tax'); ?>:</span> + <?php endif; ?> + <?php $_incl = $this->helper('checkout')->getPriceInclTax($_item); ?> + <?php $_baseIncl = $this->helper('checkout')->getBasePriceInclTax($_item); ?> + + <?php if (Mage::helper('weee')->typeOfDisplay($_item, array(0, 1, 4), 'sales')): ?> + <?php echo $this->displayPrices($_baseIncl+$_item->getBaseWeeeTaxAppliedAmount(), $_incl+$_item->getWeeeTaxAppliedAmount()); ?> + <?php else: ?> + <?php echo $this->displayPrices($_baseIncl-$_item->getBaseWeeeTaxDisposition(), $_incl-$_item->getWeeeTaxDisposition()) ?> + <?php endif; ?> + + <?php if (Mage::helper('weee')->getApplied($_item)): ?> + <br /> + <?php if (Mage::helper('weee')->typeOfDisplay($_item, 1, 'sales')): ?> + <small> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount'], $tax['amount']); ?></span> + <?php endforeach; ?> + </small> + <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><small><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount_incl_tax'], $tax['amount_incl_tax']); ?></small></span> + <?php endforeach; ?> + <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 4, 'sales')): ?> + <small> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount_incl_tax'], $tax['amount_incl_tax']); ?></span> + <?php endforeach; ?> + </small> + <?php endif; ?> + + <?php if (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> + <br /> + <span class="nobr"><?php echo Mage::helper('weee')->__('Total'); ?>:<br /> <?php echo $this->displayPrices($_baseIncl+$_item->getBaseWeeeTaxAppliedAmount(), $_incl+$_item->getWeeeTaxAppliedAmount()); ?></span> + <?php endif; ?> + <?php endif; ?> + </span> + <?php endif; ?> + <?php else: ?> +   + <?php endif; ?> + </td> + <td class="a-center"> + <?php if ($this->canShowPriceInfo($_item)): ?> + <?php echo $_item->getQty()*1 ?> + <?php else: ?> +   + <?php endif; ?> + </td> + <td class="a-right"> + <?php if ($this->canShowPriceInfo($_item)): ?> + <?php if ($this->helper('tax')->displayCartBothPrices() || $this->helper('tax')->displayCartPriceExclTax()): ?> + <span class="price-excl-tax"> + <?php if ($this->helper('tax')->displayCartBothPrices()): ?> + <span class="label"><?php echo $this->__('Excl. Tax'); ?>:</span> + <?php endif; ?> + + <?php if (Mage::helper('weee')->typeOfDisplay($_item, array(0, 1, 4), 'sales')): ?> + <?php + echo $this->displayPrices( + $_item->getBaseRowTotal()+$_item->getBaseWeeeTaxAppliedRowAmount()+$_item->getBaseWeeeTaxRowDisposition(), + $_item->getRowTotal()+$_item->getWeeeTaxAppliedRowAmount()+$_item->getWeeeTaxRowDisposition() + ); + ?> + <?php else: ?> + <?php echo $this->displayPrices($_item->getBaseRowTotal(), $_item->getRowTotal()) ?> + <?php endif; ?> + + + <?php if (Mage::helper('weee')->getApplied($_item)): ?> + <?php if (Mage::helper('weee')->typeOfDisplay($_item, 1, 'sales')): ?> + <small> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount'], $tax['row_amount']); ?></span> + <?php endforeach; ?> + </small> + <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><small><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount_incl_tax'], $tax['row_amount_incl_tax']); ?></small></span> + <?php endforeach; ?> + <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 4, 'sales')): ?> + <small> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount_incl_tax'], $tax['row_amount_incl_tax']); ?></span> + <?php endforeach; ?> + </small> + <?php endif; ?> + + <?php if (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> + <br /> + <span class="nobr"><?php echo Mage::helper('weee')->__('Total'); ?>:<br /> + <?php + echo $this->displayPrices( + $_item->getBaseRowTotal()+$_item->getBaseWeeeTaxAppliedRowAmount()+$_item->getBaseWeeeTaxRowDisposition(), + $_item->getRowTotal()+$_item->getWeeeTaxAppliedRowAmount()+$_item->getWeeeTaxRowDisposition() + ); + ?> + </span> + <?php endif; ?> + <?php endif; ?> + </span> + <br /> + <?php endif; ?> + <?php if ($this->helper('tax')->displayCartBothPrices() || $this->helper('tax')->displayCartPriceInclTax()): ?> + <span class="price-incl-tax"> + <?php if ($this->helper('tax')->displayCartBothPrices()): ?> + <span class="label"><?php echo $this->__('Incl. Tax'); ?>:</span> + <?php endif; ?> + <?php $_incl = $this->helper('checkout')->getSubtotalInclTax($_item); ?> + <?php $_baseIncl = $this->helper('checkout')->getBaseSubtotalInclTax($_item); ?> + <?php if (Mage::helper('weee')->typeOfDisplay($_item, array(0, 1, 4), 'sales')): ?> + <?php echo $this->displayPrices($_baseIncl+$_item->getBaseWeeeTaxAppliedRowAmount(), $_incl+$_item->getWeeeTaxAppliedRowAmount()); ?> + <?php else: ?> + <?php echo $this->displayPrices($_baseIncl-$_item->getBaseWeeeTaxRowDisposition(), $_incl-$_item->getWeeeTaxRowDisposition()) ?> + <?php endif; ?> + + + <?php if (Mage::helper('weee')->getApplied($_item)): ?> + + <br /> + <?php if (Mage::helper('weee')->typeOfDisplay($_item, 1, 'sales')): ?> + <small> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount'], $tax['row_amount']); ?></span> + <?php endforeach; ?> + </small> + <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><small><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount_incl_tax'], $tax['row_amount_incl_tax']); ?></small></span> + <?php endforeach; ?> + <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 4, 'sales')): ?> + <small> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount_incl_tax'], $tax['row_amount_incl_tax']); ?></span> + <?php endforeach; ?> + </small> + <?php endif; ?> + + <?php if (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> + <br /><span class="nobr"><?php echo Mage::helper('weee')->__('Total'); ?>:<br /> <?php echo $this->displayPrices($_baseIncl+$_item->getBaseWeeeTaxAppliedRowAmount(), $_incl+$_item->getWeeeTaxAppliedRowAmount()); ?></span> + <?php endif; ?> + <?php endif; ?> + </span> + <?php endif; ?> + </span> + <?php else: ?> +   + <?php endif; ?> + </td> + <td class="a-right"> + <?php if ($this->canShowPriceInfo($_item)): ?> + <?php echo $this->displayPriceAttribute('tax_amount') ?> + <?php else: ?> +   + <?php endif; ?> + </td> + <td class="a-right"> + <?php if ($this->canShowPriceInfo($_item)): ?> + <?php echo $this->displayPriceAttribute('discount_amount') ?> + <?php else: ?> +   + <?php endif; ?> + </td> + <td class="a-right last"> + <?php if ($this->canShowPriceInfo($_item)): ?> + <?php echo $this->displayPrices( + $_item->getBaseRowTotal()+$_item->getBaseTaxAmount()-$_item->getBaseDiscountAmount()+$_item->getBaseWeeeTaxAppliedRowAmount(), + $_item->getRowTotal()+$_item->getTaxAmount()-$_item->getDiscountAmount()+$_item->getWeeeTaxAppliedRowAmount() + ) ?> + <?php else: ?> +   + <?php endif; ?> + </td> + </tr> +<?php endforeach; ?> +<?php if($_showlastRow): ?> + <tr class="border"> + <td> + <?php if ($this->getOrderOptions()): ?> + <dl class="item-options"> + <?php foreach ($this->getOrderOptions() as $option): ?> + <dt><?php echo $option['label'] ?></dt> + <dd> + <?php if (isset($option['custom_view']) && $option['custom_view']): ?> + <?php echo $option['value'];?> + <?php else: ?> + <?php echo Mage::helper('core/string')->truncate($option['value'], 55, '', $_remainder);?> + <?php if ($_remainder):?> + ... <span id="<?php echo $_id = 'id' . uniqid()?>"><?php echo $_remainder ?></span> + <script type="text/javascript"> + $('<?php echo $_id ?>').hide(); + $('<?php echo $_id ?>').up().observe('mouseover', function(){$('<?php echo $_id ?>').show();}); + $('<?php echo $_id ?>').up().observe('mouseout', function(){$('<?php echo $_id ?>').hide();}); + </script> + <?php endif;?> + <?php endif;?> + </dd> + <?php endforeach; ?> + </dl> + <?php endif; ?> + <?php echo $this->getItem()->getDescription() ?> + </td> + <td> </td> + <td> </td> + <td> </td> + <td> </td> + <td> </td> + <td class="last"> </td> + </tr> +<?php endif; ?> diff --git a/app/design/adminhtml/default/default/template/bundle/sales/order/view/items/renderer.phtml b/app/design/adminhtml/default/default/template/bundle/sales/order/view/items/renderer.phtml index 8d67db12c6..550237b656 100644 --- a/app/design/adminhtml/default/default/template/bundle/sales/order/view/items/renderer.phtml +++ b/app/design/adminhtml/default/default/template/bundle/sales/order/view/items/renderer.phtml @@ -1,459 +1,463 @@ -<?php -/** - * 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_default - * @package Mage_Bundle - * @copyright Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com) - * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) - */ -?> -<?php -/** - * @see Mage_Bundle_Block_Adminhtml_Sales_Order_View_Items_Renderer - */ -?> - -<?php $_item = $this->getItem() ?> -<?php $items = array_merge(array($_item), $_item->getChildrenItems()); ?> -<?php $_count = count ($items) ?> -<?php $_index = 0 ?> - -<?php $_prevOptionId = '' ?> - -<?php if($this->getOrderOptions() || $_item->getDescription() || $this->canDisplayGiftmessage()): ?> - <?php $_showlastRow = true ?> -<?php else: ?> - <?php $_showlastRow = false ?> -<?php endif; ?> - -<?php foreach ($items as $_item): ?> - <?php $this->setPriceDataObject($_item) ?> - <?php $attributes = $this->getSelectionAttributes($_item) ?> - <?php if ($_item->getParentItem()): ?> - <?php if ($_prevOptionId != $attributes['option_id']): ?> - <tr> - <td><div class="option-label"><?php echo $attributes['option_label'] ?></div></td> - <td> </td> - <td> </td> - <td> </td> - <td> </td> - <td> </td> - <td> </td> - <td> </td> - <td> </td> - <td class="last"> </td> - </tr> - <?php $_prevOptionId = $attributes['option_id'] ?> - <?php endif; ?> - <?php endif; ?> - <tr<?php echo (++$_index==$_count && !$_showlastRow)?' class="border"':'' ?>> - <?php if (!$_item->getParentItem()): ?> - <td> - <h5 class="title"><?php echo $this->htmlEscape($_item->getName()) ?></h5> - <div> - <strong><?php echo $this->helper('sales')->__('SKU') ?>:</strong> - <?php echo implode('<br />', Mage::helper('catalog')->splitSku($_item->getSku())); ?> - </div> - </td> - <?php else: ?> - <td><div class="option-value"><?php echo $this->getValueHtml($_item)?></div></td> - <?php endif; ?> - <td class="a-center"> - <?php if ($this->canShowPriceInfo($_item)): ?> - <?php echo $_item->getStatus() ?> - <?php else: ?> -   - <?php endif; ?> - </td> - <td class="a-right"> - <?php if ($this->canShowPriceInfo($_item)): ?> - <?php echo $this->displayPriceAttribute('original_price') ?> - <?php else: ?> -   - <?php endif; ?> - </td> - <td class="a-right"> - <?php if ($this->canShowPriceInfo($_item)): ?> - <?php if ($this->helper('tax')->displayCartBothPrices() || $this->helper('tax')->displayCartPriceExclTax()): ?> - <span class="price-excl-tax"> - <?php if ($this->helper('tax')->displayCartBothPrices()): ?> - <span class="label"><?php echo $this->__('Excl. Tax'); ?>:</span> - <?php endif; ?> - - <?php if (Mage::helper('weee')->typeOfDisplay($_item, array(0, 1, 4), 'sales')): ?> - <?php - echo $this->displayPrices( - $_item->getBasePrice()+$_item->getBaseWeeeTaxAppliedAmount()+$_item->getBaseWeeeTaxDisposition(), - $_item->getPrice()+$_item->getWeeeTaxAppliedAmount()+$_item->getWeeeTaxDisposition() - ); - ?> - <?php else: ?> - <?php echo $this->displayPrices($_item->getBasePrice(), $_item->getPrice()) ?> - <?php endif; ?> - - - <?php if (Mage::helper('weee')->getApplied($_item)): ?> - <br /> - <?php if (Mage::helper('weee')->typeOfDisplay($_item, 1, 'sales')): ?> - <small> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount'], $tax['amount']); ?></span> - <?php endforeach; ?> - </small> - <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><small><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount_incl_tax'], $tax['amount_incl_tax']); ?></small></span> - <?php endforeach; ?> - <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 4, 'sales')): ?> - <small> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount_incl_tax'], $tax['amount_incl_tax']); ?></span> - <?php endforeach; ?> - </small> - <?php endif; ?> - - <?php if (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> - <br /> - <span class="nobr"><?php echo Mage::helper('weee')->__('Total'); ?>:<br /> - <?php - echo $this->displayPrices( - $_item->getBasePrice()+$_item->getBaseWeeeTaxAppliedAmount()+$_item->getBaseWeeeTaxDisposition(), - $_item->getPrice()+$_item->getWeeeTaxAppliedAmount()+$_item->getWeeeTaxDisposition() - ); - ?> - </span> - <?php endif; ?> - <?php endif; ?> - </span> - <br /> - <?php endif; ?> - <?php if ($this->helper('tax')->displayCartBothPrices() || $this->helper('tax')->displayCartPriceInclTax()): ?> - <span class="price-incl-tax"> - <?php if ($this->helper('tax')->displayCartBothPrices()): ?> - <span class="label"><?php echo $this->__('Incl. Tax'); ?>:</span> - <?php endif; ?> - <?php $_incl = $this->helper('checkout')->getPriceInclTax($_item); ?> - <?php $_baseIncl = $this->helper('checkout')->getBasePriceInclTax($_item); ?> - - <?php if (Mage::helper('weee')->typeOfDisplay($_item, array(0, 1, 4), 'sales')): ?> - <?php echo $this->displayPrices($_baseIncl+$_item->getBaseWeeeTaxAppliedAmount(), $_incl+$_item->getWeeeTaxAppliedAmount()); ?> - <?php else: ?> - <?php echo $this->displayPrices($_baseIncl-$_item->getBaseWeeeTaxDisposition(), $_incl-$_item->getWeeeTaxDisposition()) ?> - <?php endif; ?> - - <?php if (Mage::helper('weee')->getApplied($_item)): ?> - <br /> - <?php if (Mage::helper('weee')->typeOfDisplay($_item, 1, 'sales')): ?> - <small> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount'], $tax['amount']); ?></span> - <?php endforeach; ?> - </small> - <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><small><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount_incl_tax'], $tax['amount_incl_tax']); ?></small></span> - <?php endforeach; ?> - <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 4, 'sales')): ?> - <small> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount_incl_tax'], $tax['amount_incl_tax']); ?></span> - <?php endforeach; ?> - </small> - <?php endif; ?> - - <?php if (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> - <br /> - <span class="nobr"><?php echo Mage::helper('weee')->__('Total'); ?>:<br /> <?php echo $this->displayPrices($_baseIncl+$_item->getBaseWeeeTaxAppliedAmount(), $_incl+$_item->getWeeeTaxAppliedAmount()); ?></span> - <?php endif; ?> - <?php endif; ?> - </span> - <?php endif; ?> - <?php else: ?> -   - <?php endif; ?> - </td> - <td> - <?php if ($this->canShowPriceInfo($_item)): ?> - <table cellspacing="0" class="qty-table"> - <tr> - <td><?php echo Mage::helper('sales')->__('Ordered') ?></td> - <td><strong><?php echo $_item->getQtyOrdered()*1 ?></strong></td> - </tr> - <?php if ((float) $_item->getQtyInvoiced()): ?> - <tr> - <td><?php echo Mage::helper('sales')->__('Invoiced') ?></td> - <td><strong><?php echo $_item->getQtyInvoiced()*1 ?></strong></td> - </tr> - <?php endif; ?> - <?php if ((float) $_item->getQtyShipped() && $this->isShipmentSeparately($_item)): ?> - <tr> - <td><?php echo Mage::helper('sales')->__('Shipped') ?></td> - <td><strong><?php echo $_item->getQtyShipped()*1 ?></strong></td> - </tr> - <?php endif; ?> - <?php if ((float) $_item->getQtyRefunded()): ?> - <tr> - <td><?php echo Mage::helper('sales')->__('Refunded') ?></td> - <td><strong><?php echo $_item->getQtyRefunded()*1 ?></strong></td> - </tr> - <?php endif; ?> - <?php if ((float) $_item->getQtyCanceled()): ?> - <tr> - <td><?php echo Mage::helper('sales')->__('Canceled') ?></td> - <td><strong><?php echo $_item->getQtyCanceled()*1 ?></strong></td> - </tr> - <?php endif; ?> - </table> - <?php elseif ($this->isShipmentSeparately($_item)): ?> - <table cellspacing="0" class="qty-table"> - <tr> - <td><?php echo Mage::helper('sales')->__('Ordered') ?></td> - <td><strong><?php echo $_item->getQtyOrdered()*1 ?></strong></td> - </tr> - <?php if ((float) $_item->getQtyShipped()): ?> - <tr> - <td><?php echo Mage::helper('sales')->__('Shipped') ?></td> - <td><strong><?php echo $_item->getQtyShipped()*1 ?></strong></td> - </tr> - <?php endif; ?> - </table> - <?php else: ?> -   - <?php endif; ?> - </td> - <td class="a-right"> - <?php if ($this->canShowPriceInfo($_item)): ?> - <?php if ($this->helper('tax')->displayCartBothPrices() || $this->helper('tax')->displayCartPriceExclTax()): ?> - <span class="price-excl-tax"> - <?php if ($this->helper('tax')->displayCartBothPrices()): ?> - <span class="label"><?php echo $this->__('Excl. Tax'); ?>:</span> - <?php endif; ?> - - <?php if (Mage::helper('weee')->typeOfDisplay($_item, array(0, 1, 4), 'sales')): ?> - <?php - echo $this->displayPrices( - $_item->getBaseRowTotal()+$_item->getBaseWeeeTaxAppliedRowAmount()+$_item->getBaseWeeeTaxRowDisposition(), - $_item->getRowTotal()+$_item->getWeeeTaxAppliedRowAmount()+$_item->getWeeeTaxRowDisposition() - ); - ?> - <?php else: ?> - <?php echo $this->displayPrices($_item->getBaseRowTotal(), $_item->getRowTotal()) ?> - <?php endif; ?> - - - <?php if (Mage::helper('weee')->getApplied($_item)): ?> - <?php if (Mage::helper('weee')->typeOfDisplay($_item, 1, 'sales')): ?> - <small> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount'], $tax['row_amount']); ?></span> - <?php endforeach; ?> - </small> - <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><small><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount_incl_tax'], $tax['row_amount_incl_tax']); ?></small></span> - <?php endforeach; ?> - <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 4, 'sales')): ?> - <small> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount_incl_tax'], $tax['row_amount_incl_tax']); ?></span> - <?php endforeach; ?> - </small> - <?php endif; ?> - - <?php if (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> - <br /> - <span class="nobr"><?php echo Mage::helper('weee')->__('Total'); ?>:<br /> - <?php - echo $this->displayPrices( - $_item->getBaseRowTotal()+$_item->getBaseWeeeTaxAppliedRowAmount()+$_item->getBaseWeeeTaxRowDisposition(), - $_item->getRowTotal()+$_item->getWeeeTaxAppliedRowAmount()+$_item->getWeeeTaxRowDisposition() - ); - ?> - </span> - <?php endif; ?> - <?php endif; ?> - </span> - <br /> - <?php endif; ?> - <?php if ($this->helper('tax')->displayCartBothPrices() || $this->helper('tax')->displayCartPriceInclTax()): ?> - <span class="price-incl-tax"> - <?php if ($this->helper('tax')->displayCartBothPrices()): ?> - <span class="label"><?php echo $this->__('Incl. Tax'); ?>:</span> - <?php endif; ?> - <?php $_incl = $this->helper('checkout')->getSubtotalInclTax($_item); ?> - <?php $_baseIncl = $this->helper('checkout')->getBaseSubtotalInclTax($_item); ?> - <?php if (Mage::helper('weee')->typeOfDisplay($_item, array(0, 1, 4), 'sales')): ?> - <?php echo $this->displayPrices($_baseIncl+$_item->getBaseWeeeTaxAppliedRowAmount(), $_incl+$_item->getWeeeTaxAppliedRowAmount()); ?> - <?php else: ?> - <?php echo $this->displayPrices($_baseIncl-$_item->getBaseWeeeTaxRowDisposition(), $_incl-$_item->getWeeeTaxRowDisposition()) ?> - <?php endif; ?> - - - <?php if (Mage::helper('weee')->getApplied($_item)): ?> - - <br /> - <?php if (Mage::helper('weee')->typeOfDisplay($_item, 1, 'sales')): ?> - <small> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount'], $tax['row_amount']); ?></span> - <?php endforeach; ?> - </small> - <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><small><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount_incl_tax'], $tax['row_amount_incl_tax']); ?></small></span> - <?php endforeach; ?> - <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 4, 'sales')): ?> - <small> - <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> - <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount_incl_tax'], $tax['row_amount_incl_tax']); ?></span> - <?php endforeach; ?> - </small> - <?php endif; ?> - - <?php if (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> - <br /><span class="nobr"><?php echo Mage::helper('weee')->__('Total'); ?>:<br /> <?php echo $this->displayPrices($_baseIncl+$_item->getBaseWeeeTaxAppliedRowAmount(), $_incl+$_item->getWeeeTaxAppliedRowAmount()); ?></span> - <?php endif; ?> - <?php endif; ?> - </span> - <?php endif; ?> - </span> - <?php else: ?> -   - <?php endif; ?> - </td> - <td class="a-right"> - <?php if ($this->canShowPriceInfo($_item)): ?> - <?php echo $this->displayPriceAttribute('tax_amount') ?> - <?php else: ?> -   - <?php endif; ?> - </td> - - <td class="a-right"> - <?php if ($this->canShowPriceInfo($_item)): ?> - <?php echo $this->displayTaxPercent($_item) ?> - <?php else: ?> -   - <?php endif; ?> - </td> - <td class="a-right"> - <?php if ($this->canShowPriceInfo($_item)): ?> - <?php echo $this->displayPriceAttribute('discount_amount') ?> - <?php else: ?> -   - <?php endif; ?> - </td> - <td class="a-right last"> - <?php if ($this->canShowPriceInfo($_item)): ?> - <?php echo $this->displayPrices( - $_item->getBaseRowTotal() - $_item->getBaseDiscountAmount() + $_item->getBaseTaxAmount() + $_item->getBaseWeeeTaxAppliedRowAmount(), - $_item->getRowTotal() - $_item->getDiscountAmount() + $_item->getTaxAmount() + $_item->getWeeeTaxAppliedRowAmount() - ); ?> - <?php else: ?> -   - <?php endif; ?> - </td> - </tr> -<?php endforeach; ?> -<?php if($_showlastRow): ?> - <tr<?php if (!$this->canDisplayGiftmessage()) echo ' class="border"' ?>> - <td> - <?php if ($this->getOrderOptions()): ?> - <dl class="item-options"> - <?php foreach ($this->getOrderOptions() as $option): ?> - <dt><?php echo $option['label'] ?>:</dt> - <dd> - <?php echo $this->htmlEscape(Mage::helper('core/string')->truncate($option['value'], 55, '', $_remainder));?> - <?php if ($_remainder):?> - ... <span id="<?php echo $_id = 'id' . uniqid()?>"><?php echo $this->htmlEscape($_remainder) ?></span> - <script type="text/javascript"> - $('<?php echo $_id ?>').hide(); - $('<?php echo $_id ?>').up().observe('mouseover', function(){$('<?php echo $_id ?>').show();}); - $('<?php echo $_id ?>').up().observe('mouseout', function(){$('<?php echo $_id ?>').hide();}); - </script> - <?php endif;?> - </dd> - <?php endforeach; ?> - </dl> - <?php else: ?> -   - <?php endif; ?> - <?php echo $_item->getDescription() ?> - </td> - <td> </td> - <td> </td> - <td> </td> - <td> </td> - <td> </td> - <td> </td> - <td> </td> - <td> </td> - <td class="last"> </td> - </tr> - <?php if ($this->canDisplayGiftmessage()): ?> - <tr class="border"> - <td class="giftmessage-single-item"> - <?php if ($this->canDisplayContainer()): ?> - <div id="<?php echo $this->getHtmlId() ?>" class="item-container"> - <?php endif; ?> - <div class="item-text"> - <a class="action-link" href="#" onclick="return giftMessagesController.toogleGiftMessage('<?php echo $this->getHtmlId() ?>')"> - <span class="default-text"> - <span class="add"<?php if($this->getMessage()->getId()): ?> style="display:none;"<?php endif;?>><?php echo $this->helper('sales')->__('Add Gift Messsage') ?></span> - <span class="edit"<?php if(!$this->getMessage()->getId()): ?> style="display:none;"<?php endif;?>><?php echo $this->helper('sales')->__('Edit Gift Messsage') ?></span></span><span class="close-text" style="display:none;"><?php echo $this->helper('sales')->__('Save and Close Message') ?></span></a> - </div> - <div class="gift-form" style="display:none;" id="<?php echo $this->getFieldId('edit') ?>"> - <form id="<?php echo $this->getFieldId('form') ?>" action="<?php echo $this->getSaveUrl() ?>"> - <div class="entry-edit"> - <fieldset> - <input type="hidden" id="<?php echo $this->getFieldId('type') ?>" name="<?php echo $this->getFieldName('type') ?>" value="order_item"/> - <span class="field-row"> - <label for="<?php echo $this->getFieldId('sender') ?>"><?php echo $this->helper('sales')->__('From Name') ?></label><br /> - <input class="input-text" type="text" id="<?php echo $this->getFieldId('sender') ?>" name="<?php echo $this->getFieldName('sender') ?>" value="<?php echo $this->htmlEscape($this->getMessage()->getSender()) ?>"/> - </span> - <span class="field-row"> - <label for="<?php echo $this->getFieldId('recipient') ?>"><?php echo $this->helper('sales')->__('To Name') ?></label><br /> - <input class="input-text" type="text" id="<?php echo $this->getFieldId('recipient') ?>" name="<?php echo $this->getFieldName('recipient') ?>" value="<?php echo $this->htmlEscape($this->getMessage()->getRecipient()) ?>"/> - </span> - <div class="clear"></div> - <span class="field-row last"> - <label for="<?php echo $this->getFieldId('message') ?>"><?php echo $this->helper('sales')->__('Gift Message') ?></label><br /> - <textarea id="<?php echo $this->getFieldId('message') ?>" onchange="giftMessagesController.toogleRequired('<?php echo $this->getFieldId('message') ?>', ['<?php echo $this->getFieldId('sender') ?>','<?php echo $this->getFieldId('recipient') ?>']);" name="<?php echo $this->getFieldName('message') ?>" rows="3" cols="5"><?php echo $this->htmlEscape($this->getMessage()->getMessage()) ?></textarea> - </span> - </fieldset> - </div> - </form> - </div> - <?php if ($this->canDisplayContainer()): ?> - </div> - <?php endif ?> - </td> - <td> </td> - <td> </td> - <td> </td> - <td> </td> - <td> </td> - <td> </td> - <td> </td> - <td> </td> - <td class="last"> </td> - </tr> - <?php endif; ?> -<?php endif; ?> +<?php +/** + * 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_default + * @package Mage_Bundle + * @copyright Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com) + * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) + */ +?> +<?php +/** + * @see Mage_Bundle_Block_Adminhtml_Sales_Order_View_Items_Renderer + */ +?> + +<?php $_item = $this->getItem() ?> +<?php $items = array_merge(array($_item), $_item->getChildrenItems()); ?> +<?php $_count = count ($items) ?> +<?php $_index = 0 ?> + +<?php $_prevOptionId = '' ?> + +<?php if($this->getOrderOptions() || $_item->getDescription() || $this->canDisplayGiftmessage()): ?> + <?php $_showlastRow = true ?> +<?php else: ?> + <?php $_showlastRow = false ?> +<?php endif; ?> + +<?php foreach ($items as $_item): ?> + <?php $this->setPriceDataObject($_item) ?> + <?php $attributes = $this->getSelectionAttributes($_item) ?> + <?php if ($_item->getParentItem()): ?> + <?php if ($_prevOptionId != $attributes['option_id']): ?> + <tr> + <td><div class="option-label"><?php echo $attributes['option_label'] ?></div></td> + <td> </td> + <td> </td> + <td> </td> + <td> </td> + <td> </td> + <td> </td> + <td> </td> + <td> </td> + <td class="last"> </td> + </tr> + <?php $_prevOptionId = $attributes['option_id'] ?> + <?php endif; ?> + <?php endif; ?> + <tr<?php echo (++$_index==$_count && !$_showlastRow)?' class="border"':'' ?>> + <?php if (!$_item->getParentItem()): ?> + <td> + <h5 class="title"><?php echo $this->htmlEscape($_item->getName()) ?></h5> + <div> + <strong><?php echo $this->helper('sales')->__('SKU') ?>:</strong> + <?php echo implode('<br />', Mage::helper('catalog')->splitSku($_item->getSku())); ?> + </div> + </td> + <?php else: ?> + <td><div class="option-value"><?php echo $this->getValueHtml($_item)?></div></td> + <?php endif; ?> + <td class="a-center"> + <?php if ($this->canShowPriceInfo($_item)): ?> + <?php echo $_item->getStatus() ?> + <?php else: ?> +   + <?php endif; ?> + </td> + <td class="a-right"> + <?php if ($this->canShowPriceInfo($_item)): ?> + <?php echo $this->displayPriceAttribute('original_price') ?> + <?php else: ?> +   + <?php endif; ?> + </td> + <td class="a-right"> + <?php if ($this->canShowPriceInfo($_item)): ?> + <?php if ($this->helper('tax')->displayCartBothPrices() || $this->helper('tax')->displayCartPriceExclTax()): ?> + <span class="price-excl-tax"> + <?php if ($this->helper('tax')->displayCartBothPrices()): ?> + <span class="label"><?php echo $this->__('Excl. Tax'); ?>:</span> + <?php endif; ?> + + <?php if (Mage::helper('weee')->typeOfDisplay($_item, array(0, 1, 4), 'sales')): ?> + <?php + echo $this->displayPrices( + $_item->getBasePrice()+$_item->getBaseWeeeTaxAppliedAmount()+$_item->getBaseWeeeTaxDisposition(), + $_item->getPrice()+$_item->getWeeeTaxAppliedAmount()+$_item->getWeeeTaxDisposition() + ); + ?> + <?php else: ?> + <?php echo $this->displayPrices($_item->getBasePrice(), $_item->getPrice()) ?> + <?php endif; ?> + + + <?php if (Mage::helper('weee')->getApplied($_item)): ?> + <br /> + <?php if (Mage::helper('weee')->typeOfDisplay($_item, 1, 'sales')): ?> + <small> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount'], $tax['amount']); ?></span> + <?php endforeach; ?> + </small> + <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><small><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount_incl_tax'], $tax['amount_incl_tax']); ?></small></span> + <?php endforeach; ?> + <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 4, 'sales')): ?> + <small> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount_incl_tax'], $tax['amount_incl_tax']); ?></span> + <?php endforeach; ?> + </small> + <?php endif; ?> + + <?php if (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> + <br /> + <span class="nobr"><?php echo Mage::helper('weee')->__('Total'); ?>:<br /> + <?php + echo $this->displayPrices( + $_item->getBasePrice()+$_item->getBaseWeeeTaxAppliedAmount()+$_item->getBaseWeeeTaxDisposition(), + $_item->getPrice()+$_item->getWeeeTaxAppliedAmount()+$_item->getWeeeTaxDisposition() + ); + ?> + </span> + <?php endif; ?> + <?php endif; ?> + </span> + <br /> + <?php endif; ?> + <?php if ($this->helper('tax')->displayCartBothPrices() || $this->helper('tax')->displayCartPriceInclTax()): ?> + <span class="price-incl-tax"> + <?php if ($this->helper('tax')->displayCartBothPrices()): ?> + <span class="label"><?php echo $this->__('Incl. Tax'); ?>:</span> + <?php endif; ?> + <?php $_incl = $this->helper('checkout')->getPriceInclTax($_item); ?> + <?php $_baseIncl = $this->helper('checkout')->getBasePriceInclTax($_item); ?> + + <?php if (Mage::helper('weee')->typeOfDisplay($_item, array(0, 1, 4), 'sales')): ?> + <?php echo $this->displayPrices($_baseIncl+$_item->getBaseWeeeTaxAppliedAmount(), $_incl+$_item->getWeeeTaxAppliedAmount()); ?> + <?php else: ?> + <?php echo $this->displayPrices($_baseIncl-$_item->getBaseWeeeTaxDisposition(), $_incl-$_item->getWeeeTaxDisposition()) ?> + <?php endif; ?> + + <?php if (Mage::helper('weee')->getApplied($_item)): ?> + <br /> + <?php if (Mage::helper('weee')->typeOfDisplay($_item, 1, 'sales')): ?> + <small> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount'], $tax['amount']); ?></span> + <?php endforeach; ?> + </small> + <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><small><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount_incl_tax'], $tax['amount_incl_tax']); ?></small></span> + <?php endforeach; ?> + <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 4, 'sales')): ?> + <small> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_amount_incl_tax'], $tax['amount_incl_tax']); ?></span> + <?php endforeach; ?> + </small> + <?php endif; ?> + + <?php if (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> + <br /> + <span class="nobr"><?php echo Mage::helper('weee')->__('Total'); ?>:<br /> <?php echo $this->displayPrices($_baseIncl+$_item->getBaseWeeeTaxAppliedAmount(), $_incl+$_item->getWeeeTaxAppliedAmount()); ?></span> + <?php endif; ?> + <?php endif; ?> + </span> + <?php endif; ?> + <?php else: ?> +   + <?php endif; ?> + </td> + <td> + <?php if ($this->canShowPriceInfo($_item)): ?> + <table cellspacing="0" class="qty-table"> + <tr> + <td><?php echo Mage::helper('sales')->__('Ordered') ?></td> + <td><strong><?php echo $_item->getQtyOrdered()*1 ?></strong></td> + </tr> + <?php if ((float) $_item->getQtyInvoiced()): ?> + <tr> + <td><?php echo Mage::helper('sales')->__('Invoiced') ?></td> + <td><strong><?php echo $_item->getQtyInvoiced()*1 ?></strong></td> + </tr> + <?php endif; ?> + <?php if ((float) $_item->getQtyShipped() && $this->isShipmentSeparately($_item)): ?> + <tr> + <td><?php echo Mage::helper('sales')->__('Shipped') ?></td> + <td><strong><?php echo $_item->getQtyShipped()*1 ?></strong></td> + </tr> + <?php endif; ?> + <?php if ((float) $_item->getQtyRefunded()): ?> + <tr> + <td><?php echo Mage::helper('sales')->__('Refunded') ?></td> + <td><strong><?php echo $_item->getQtyRefunded()*1 ?></strong></td> + </tr> + <?php endif; ?> + <?php if ((float) $_item->getQtyCanceled()): ?> + <tr> + <td><?php echo Mage::helper('sales')->__('Canceled') ?></td> + <td><strong><?php echo $_item->getQtyCanceled()*1 ?></strong></td> + </tr> + <?php endif; ?> + </table> + <?php elseif ($this->isShipmentSeparately($_item)): ?> + <table cellspacing="0" class="qty-table"> + <tr> + <td><?php echo Mage::helper('sales')->__('Ordered') ?></td> + <td><strong><?php echo $_item->getQtyOrdered()*1 ?></strong></td> + </tr> + <?php if ((float) $_item->getQtyShipped()): ?> + <tr> + <td><?php echo Mage::helper('sales')->__('Shipped') ?></td> + <td><strong><?php echo $_item->getQtyShipped()*1 ?></strong></td> + </tr> + <?php endif; ?> + </table> + <?php else: ?> +   + <?php endif; ?> + </td> + <td class="a-right"> + <?php if ($this->canShowPriceInfo($_item)): ?> + <?php if ($this->helper('tax')->displayCartBothPrices() || $this->helper('tax')->displayCartPriceExclTax()): ?> + <span class="price-excl-tax"> + <?php if ($this->helper('tax')->displayCartBothPrices()): ?> + <span class="label"><?php echo $this->__('Excl. Tax'); ?>:</span> + <?php endif; ?> + + <?php if (Mage::helper('weee')->typeOfDisplay($_item, array(0, 1, 4), 'sales')): ?> + <?php + echo $this->displayPrices( + $_item->getBaseRowTotal()+$_item->getBaseWeeeTaxAppliedRowAmount()+$_item->getBaseWeeeTaxRowDisposition(), + $_item->getRowTotal()+$_item->getWeeeTaxAppliedRowAmount()+$_item->getWeeeTaxRowDisposition() + ); + ?> + <?php else: ?> + <?php echo $this->displayPrices($_item->getBaseRowTotal(), $_item->getRowTotal()) ?> + <?php endif; ?> + + + <?php if (Mage::helper('weee')->getApplied($_item)): ?> + <?php if (Mage::helper('weee')->typeOfDisplay($_item, 1, 'sales')): ?> + <small> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount'], $tax['row_amount']); ?></span> + <?php endforeach; ?> + </small> + <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><small><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount_incl_tax'], $tax['row_amount_incl_tax']); ?></small></span> + <?php endforeach; ?> + <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 4, 'sales')): ?> + <small> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount_incl_tax'], $tax['row_amount_incl_tax']); ?></span> + <?php endforeach; ?> + </small> + <?php endif; ?> + + <?php if (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> + <br /> + <span class="nobr"><?php echo Mage::helper('weee')->__('Total'); ?>:<br /> + <?php + echo $this->displayPrices( + $_item->getBaseRowTotal()+$_item->getBaseWeeeTaxAppliedRowAmount()+$_item->getBaseWeeeTaxRowDisposition(), + $_item->getRowTotal()+$_item->getWeeeTaxAppliedRowAmount()+$_item->getWeeeTaxRowDisposition() + ); + ?> + </span> + <?php endif; ?> + <?php endif; ?> + </span> + <br /> + <?php endif; ?> + <?php if ($this->helper('tax')->displayCartBothPrices() || $this->helper('tax')->displayCartPriceInclTax()): ?> + <span class="price-incl-tax"> + <?php if ($this->helper('tax')->displayCartBothPrices()): ?> + <span class="label"><?php echo $this->__('Incl. Tax'); ?>:</span> + <?php endif; ?> + <?php $_incl = $this->helper('checkout')->getSubtotalInclTax($_item); ?> + <?php $_baseIncl = $this->helper('checkout')->getBaseSubtotalInclTax($_item); ?> + <?php if (Mage::helper('weee')->typeOfDisplay($_item, array(0, 1, 4), 'sales')): ?> + <?php echo $this->displayPrices($_baseIncl+$_item->getBaseWeeeTaxAppliedRowAmount(), $_incl+$_item->getWeeeTaxAppliedRowAmount()); ?> + <?php else: ?> + <?php echo $this->displayPrices($_baseIncl-$_item->getBaseWeeeTaxRowDisposition(), $_incl-$_item->getWeeeTaxRowDisposition()) ?> + <?php endif; ?> + + + <?php if (Mage::helper('weee')->getApplied($_item)): ?> + + <br /> + <?php if (Mage::helper('weee')->typeOfDisplay($_item, 1, 'sales')): ?> + <small> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount'], $tax['row_amount']); ?></span> + <?php endforeach; ?> + </small> + <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><small><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount_incl_tax'], $tax['row_amount_incl_tax']); ?></small></span> + <?php endforeach; ?> + <?php elseif (Mage::helper('weee')->typeOfDisplay($_item, 4, 'sales')): ?> + <small> + <?php foreach (Mage::helper('weee')->getApplied($_item) as $tax): ?> + <span class="nobr"><?php echo $tax['title']; ?>: <?php echo $this->displayPrices($tax['base_row_amount_incl_tax'], $tax['row_amount_incl_tax']); ?></span> + <?php endforeach; ?> + </small> + <?php endif; ?> + + <?php if (Mage::helper('weee')->typeOfDisplay($_item, 2, 'sales')): ?> + <br /><span class="nobr"><?php echo Mage::helper('weee')->__('Total'); ?>:<br /> <?php echo $this->displayPrices($_baseIncl+$_item->getBaseWeeeTaxAppliedRowAmount(), $_incl+$_item->getWeeeTaxAppliedRowAmount()); ?></span> + <?php endif; ?> + <?php endif; ?> + </span> + <?php endif; ?> + </span> + <?php else: ?> +   + <?php endif; ?> + </td> + <td class="a-right"> + <?php if ($this->canShowPriceInfo($_item)): ?> + <?php echo $this->displayPriceAttribute('tax_amount') ?> + <?php else: ?> +   + <?php endif; ?> + </td> + + <td class="a-right"> + <?php if ($this->canShowPriceInfo($_item)): ?> + <?php echo $this->displayTaxPercent($_item) ?> + <?php else: ?> +   + <?php endif; ?> + </td> + <td class="a-right"> + <?php if ($this->canShowPriceInfo($_item)): ?> + <?php echo $this->displayPriceAttribute('discount_amount') ?> + <?php else: ?> +   + <?php endif; ?> + </td> + <td class="a-right last"> + <?php if ($this->canShowPriceInfo($_item)): ?> + <?php echo $this->displayPrices( + $_item->getBaseRowTotal() - $_item->getBaseDiscountAmount() + $_item->getBaseTaxAmount() + $_item->getBaseWeeeTaxAppliedRowAmount(), + $_item->getRowTotal() - $_item->getDiscountAmount() + $_item->getTaxAmount() + $_item->getWeeeTaxAppliedRowAmount() + ); ?> + <?php else: ?> +   + <?php endif; ?> + </td> + </tr> +<?php endforeach; ?> +<?php if($_showlastRow): ?> + <tr<?php if (!$this->canDisplayGiftmessage()) echo ' class="border"' ?>> + <td> + <?php if ($this->getOrderOptions()): ?> + <dl class="item-options"> + <?php foreach ($this->getOrderOptions() as $option): ?> + <dt><?php echo $option['label'] ?>:</dt> + <dd> + <?php if (isset($option['custom_view']) && $option['custom_view']): ?> + <?php echo $option['value'];?> + <?php else: ?> + <?php echo Mage::helper('core/string')->truncate($option['value'], 55, '', $_remainder);?> + <?php if ($_remainder):?> + ... <span id="<?php echo $_id = 'id' . uniqid()?>"><?php echo $_remainder ?></span> + <script type="text/javascript"> + $('<?php echo $_id ?>').hide(); + $('<?php echo $_id ?>').up().observe('mouseover', function(){$('<?php echo $_id ?>').show();}); + $('<?php echo $_id ?>').up().observe('mouseout', function(){$('<?php echo $_id ?>').hide();}); + </script> + <?php endif;?> + <?php endif;?> + </dd> + <?php endforeach; ?> + </dl> + <?php else: ?> +   + <?php endif; ?> + <?php echo $_item->getDescription() ?> + </td> + <td> </td> + <td> </td> + <td> </td> + <td> </td> + <td> </td> + <td> </td> + <td> </td> + <td> </td> + <td class="last"> </td> + </tr> + <?php if ($this->canDisplayGiftmessage()): ?> + <tr class="border"> + <td class="giftmessage-single-item"> + <?php if ($this->canDisplayContainer()): ?> + <div id="<?php echo $this->getHtmlId() ?>" class="item-container"> + <?php endif; ?> + <div class="item-text"> + <a class="action-link" href="#" onclick="return giftMessagesController.toogleGiftMessage('<?php echo $this->getHtmlId() ?>')"> + <span class="default-text"> + <span class="add"<?php if($this->getMessage()->getId()): ?> style="display:none;"<?php endif;?>><?php echo $this->helper('sales')->__('Add Gift Messsage') ?></span> + <span class="edit"<?php if(!$this->getMessage()->getId()): ?> style="display:none;"<?php endif;?>><?php echo $this->helper('sales')->__('Edit Gift Messsage') ?></span></span><span class="close-text" style="display:none;"><?php echo $this->helper('sales')->__('Save and Close Message') ?></span></a> + </div> + <div class="gift-form" style="display:none;" id="<?php echo $this->getFieldId('edit') ?>"> + <form id="<?php echo $this->getFieldId('form') ?>" action="<?php echo $this->getSaveUrl() ?>"> + <div class="entry-edit"> + <fieldset> + <input type="hidden" id="<?php echo $this->getFieldId('type') ?>" name="<?php echo $this->getFieldName('type') ?>" value="order_item"/> + <span class="field-row"> + <label for="<?php echo $this->getFieldId('sender') ?>"><?php echo $this->helper('sales')->__('From Name') ?></label><br /> + <input class="input-text" type="text" id="<?php echo $this->getFieldId('sender') ?>" name="<?php echo $this->getFieldName('sender') ?>" value="<?php echo $this->htmlEscape($this->getMessage()->getSender()) ?>"/> + </span> + <span class="field-row"> + <label for="<?php echo $this->getFieldId('recipient') ?>"><?php echo $this->helper('sales')->__('To Name') ?></label><br /> + <input class="input-text" type="text" id="<?php echo $this->getFieldId('recipient') ?>" name="<?php echo $this->getFieldName('recipient') ?>" value="<?php echo $this->htmlEscape($this->getMessage()->getRecipient()) ?>"/> + </span> + <div class="clear"></div> + <span class="field-row last"> + <label for="<?php echo $this->getFieldId('message') ?>"><?php echo $this->helper('sales')->__('Gift Message') ?></label><br /> + <textarea id="<?php echo $this->getFieldId('message') ?>" onchange="giftMessagesController.toogleRequired('<?php echo $this->getFieldId('message') ?>', ['<?php echo $this->getFieldId('sender') ?>','<?php echo $this->getFieldId('recipient') ?>']);" name="<?php echo $this->getFieldName('message') ?>" rows="3" cols="5"><?php echo $this->htmlEscape($this->getMessage()->getMessage()) ?></textarea> + </span> + </fieldset> + </div> + </form> + </div> + <?php if ($this->canDisplayContainer()): ?> + </div> + <?php endif ?> + </td> + <td> </td> + <td> </td> + <td> </td> + <td> </td> + <td> </td> + <td> </td> + <td> </td> + <td> </td> + <td class="last"> </td> + </tr> + <?php endif; ?> +<?php endif; ?> diff --git a/app/design/adminhtml/default/default/template/bundle/sales/shipment/create/items/renderer.phtml b/app/design/adminhtml/default/default/template/bundle/sales/shipment/create/items/renderer.phtml index aa6c979ceb..c046af5e61 100644 --- a/app/design/adminhtml/default/default/template/bundle/sales/shipment/create/items/renderer.phtml +++ b/app/design/adminhtml/default/default/template/bundle/sales/shipment/create/items/renderer.phtml @@ -90,7 +90,21 @@ <dl class="item-options"> <?php foreach ($this->getOrderOptions($_item->getOrderItem()) as $option): ?> <dt><?php echo $option['label'] ?></dt> - <dd><?php echo $this->htmlEscape($option['value']); ?></dd> + <dd> + <?php if (isset($option['custom_view']) && $option['custom_view']): ?> + <?php echo $option['value'];?> + <?php else: ?> + <?php echo Mage::helper('core/string')->truncate($option['value'], 55, '', $_remainder);?> + <?php if ($_remainder):?> + ... <span id="<?php echo $_id = 'id' . uniqid()?>"><?php echo $_remainder ?></span> + <script type="text/javascript"> + $('<?php echo $_id ?>').hide(); + $('<?php echo $_id ?>').up().observe('mouseover', function(){$('<?php echo $_id ?>').show();}); + $('<?php echo $_id ?>').up().observe('mouseout', function(){$('<?php echo $_id ?>').hide();}); + </script> + <?php endif;?> + <?php endif;?> + </dd> <?php endforeach; ?> </dl> <?php else: ?> diff --git a/app/design/adminhtml/default/default/template/bundle/sales/shipment/view/items/renderer.phtml b/app/design/adminhtml/default/default/template/bundle/sales/shipment/view/items/renderer.phtml index 88f04ab7e4..f7466fa891 100644 --- a/app/design/adminhtml/default/default/template/bundle/sales/shipment/view/items/renderer.phtml +++ b/app/design/adminhtml/default/default/template/bundle/sales/shipment/view/items/renderer.phtml @@ -90,7 +90,21 @@ <dl class="item-options"> <?php foreach ($this->getOrderOptions($_item->getOrderItem()) as $option): ?> <dt><?php echo $option['label'] ?></dt> - <dd><?php echo $this->htmlEscape($option['value']); ?></dd> + <dd> + <?php if (isset($option['custom_view']) && $option['custom_view']): ?> + <?php echo $option['value'];?> + <?php else: ?> + <?php echo Mage::helper('core/string')->truncate($option['value'], 55, '', $_remainder);?> + <?php if ($_remainder):?> + ... <span id="<?php echo $_id = 'id' . uniqid()?>"><?php echo $_remainder ?></span> + <script type="text/javascript"> + $('<?php echo $_id ?>').hide(); + $('<?php echo $_id ?>').up().observe('mouseover', function(){$('<?php echo $_id ?>').show();}); + $('<?php echo $_id ?>').up().observe('mouseout', function(){$('<?php echo $_id ?>').hide();}); + </script> + <?php endif;?> + <?php endif;?> + </dd> <?php endforeach; ?> </dl> <?php endif; ?> diff --git a/app/design/adminhtml/default/default/template/catalog/category/edit/form.phtml b/app/design/adminhtml/default/default/template/catalog/category/edit/form.phtml index 918ead5c98..7b67dcbc8a 100644 --- a/app/design/adminhtml/default/default/template/catalog/category/edit/form.phtml +++ b/app/design/adminhtml/default/default/template/catalog/category/edit/form.phtml @@ -34,8 +34,8 @@ <?php if($this->getCategoryId()): ?> <?php echo $this->getDeleteButtonHtml() ?> <?php endif; ?> - <?php echo $this->getSaveButtonHtml() ?> <?php echo $this->getAdditionalButtonsHtml(); ?> + <?php echo $this->getSaveButtonHtml() ?> </td> </tr> </table> diff --git a/app/design/adminhtml/default/default/template/catalog/product/attribute/js.phtml b/app/design/adminhtml/default/default/template/catalog/product/attribute/js.phtml index 155d8a1fbc..2cbeb8e249 100644 --- a/app/design/adminhtml/default/default/template/catalog/product/attribute/js.phtml +++ b/app/design/adminhtml/default/default/template/catalog/product/attribute/js.phtml @@ -87,7 +87,7 @@ function bindAttributeInputType() if ($('frontend_input') && ($('frontend_input').value=='multiselect' || $('frontend_input').value=='gallery' - || $('frontend_input').value=='text')) { + || $('frontend_input').value=='textarea')) { if ($('used_for_sort_by')) { $('used_for_sort_by').disabled = true; } diff --git a/app/design/adminhtml/default/default/template/catalog/product/attribute/set/main.phtml b/app/design/adminhtml/default/default/template/catalog/product/attribute/set/main.phtml index fbbf961232..c3912b21b4 100644 --- a/app/design/adminhtml/default/default/template/catalog/product/attribute/set/main.phtml +++ b/app/design/adminhtml/default/default/template/catalog/product/attribute/set/main.phtml @@ -113,6 +113,7 @@ //this.ge.addListener('beforerender', editSet.editGroup); this.ge.addListener('beforeshow', editSet.editGroup); + this.ge.addListener('beforecomplete', editSet.beforeRenameGroup); //this.ge.addListener('startedit', editSet.editGroup); //------------------------------------------------------------- @@ -281,13 +282,10 @@ this.addGroup(); } else if( group_name != false && group_name != null && group_name != '' ) { - for (var i=0; i < TreePanels.root.childNodes.length; i++) { - if (TreePanels.root.childNodes[i].text.toLowerCase() == group_name.toLowerCase()) { - errorText = "<?php echo Mage::helper('catalog')->__('Attribute group with the \"/name/\" name already exists') ?>"; - alert(errorText.replace("/name/",group_name)); - return; - } + if (!editSet.validateGroupName(group_name, 0)) { + return; } + var newNode = new Ext.tree.TreeNode({ text : group_name, cls : 'folder', @@ -306,6 +304,21 @@ } }, + beforeRenameGroup : function(obj, after, before) { + return editSet.validateGroupName(after, obj.editNode.id); + }, + + validateGroupName : function(name, exceptNodeId) { + for (var i=0; i < TreePanels.root.childNodes.length; i++) { + if (TreePanels.root.childNodes[i].text.toLowerCase() == name.toLowerCase() && TreePanels.root.childNodes[i].id != exceptNodeId) { + errorText = "<?php echo Mage::helper('catalog')->__('Attribute group with the \"/name/\" name already exists') ?>"; + alert(errorText.replace("/name/",name)); + return false; + } + } + return true; + }, + save : function() { $('messages').update(); TreePanels.rebuildTrees(); diff --git a/app/design/adminhtml/default/default/template/catalog/product/edit/options/type/file.phtml b/app/design/adminhtml/default/default/template/catalog/product/edit/options/type/file.phtml index 73f90697d0..be6b9f1e75 100644 --- a/app/design/adminhtml/default/default/template/catalog/product/edit/options/type/file.phtml +++ b/app/design/adminhtml/default/default/template/catalog/product/edit/options/type/file.phtml @@ -39,7 +39,7 @@ OptionTemplateFile = '<table class="border" cellpadding="0" cellspacing="0">'+ '<td><?php echo $this->getPriceTypeSelectHtml() ?></td>'+ '<td><input type="text" class="input-text" name="product[options][{{option_id}}][sku]" value="{{sku}}"></td>'+ '<td><input class="input-text" type="text" name="product[options][{{option_id}}][file_extension]" value="{{file_extension}}"></td>'+ - '<td class="type-last last" nowrap><input class="input-text" type="text" name="product[options][{{option_id}}][image_size_x]" value="{{image_size_x}}"> <?php echo Mage::helper('catalog')->__('x') ?> <input class="input-text" type="text" name="product[options][{{option_id}}][image_size_y]" value="{{image_size_y}}"> <?php echo Mage::helper('catalog')->__('px.') ?></td>'+ + '<td class="type-last last" nowrap><input class="input-text" type="text" name="product[options][{{option_id}}][image_size_x]" value="{{image_size_x}}"> <?php echo Mage::helper('catalog')->__('x') ?> <input class="input-text" type="text" name="product[options][{{option_id}}][image_size_y]" value="{{image_size_y}}"> <?php echo Mage::helper('catalog')->__('px.') ?><br/><?php echo Mage::helper('catalog')->__('leave blank if its not an image') ?></td>'+ '</tr>'+ '</table>'; diff --git a/app/design/adminhtml/default/default/template/catalog/product/edit/super/config.phtml b/app/design/adminhtml/default/default/template/catalog/product/edit/super/config.phtml index c2a20b027c..1d83f59aee 100644 --- a/app/design/adminhtml/default/default/template/catalog/product/edit/super/config.phtml +++ b/app/design/adminhtml/default/default/template/catalog/product/edit/super/config.phtml @@ -74,22 +74,26 @@ <?php echo Mage::helper('catalog')->__('Price:') ?> <input id="__id___pricing" type="text" class="input-text attribute-price validate-number template no-display" value="'{{pricing_value}}'"/> </div> -<div class="attribute-values-container"> - <select class="attribute-price-type"> +<div class="attribute-values-container left"> +  <select class="attribute-price-type"> <option value="0"><?php echo Mage::helper('catalog')->__('Fixed') ?></option> <option value="1"><?php echo Mage::helper('catalog')->__('Percentage') ?></option> </select> </div> +<?php if ($this->getShowUseDefaultPrice()):?> +<div class="attribute-values-container"> +  <input id="__id___default" type="checkbox" class="attribute-use-default-value"> <label for="__id___default" class="normal"><?php echo Mage::helper('catalog')->__('Use Default Value') ?></label> +</div> +<?php endif;?> </div> </div> <div class="template no-display" id="<?php echo $this->getHtmlId() ?>_simple_pricing"> - <div class="attribute-values-container left v-middle">  <?php echo Mage::helper('catalog')->__('Price:') ?> <input type="text" class="input-text attribute-price validate-number"/> </div> <div class="attribute-values-container left v-middle"> - <select class="attribute-price-type"> +  <select class="attribute-price-type"> <option value="0"><?php echo Mage::helper('catalog')->__('Fixed') ?></option> <option value="1"><?php echo Mage::helper('catalog')->__('Percentage') ?></option> </select> diff --git a/app/design/adminhtml/default/default/template/catalog/product/helper/gallery.phtml b/app/design/adminhtml/default/default/template/catalog/product/helper/gallery.phtml index 6c66dcbb84..b59bd82f1d 100644 --- a/app/design/adminhtml/default/default/template/catalog/product/helper/gallery.phtml +++ b/app/design/adminhtml/default/default/template/catalog/product/helper/gallery.phtml @@ -67,7 +67,7 @@ $_block = $this; </thead> <tbody id="<?php echo $_block->getHtmlId() ?>_list"> <tr id="<?php echo $_block->getHtmlId() ?>_template" class="template no-display"> - <td class="cell-image"><div class="place-holder" onmouseover="<?php echo $_block->getJsObjectName(); ?>.loadImage('__file__')"><span><?php echo Mage::helper('catalog')->__('Roll Over for preview') ?></span></div><img src="#" width="100" style="display:none;" alt="" /></td> + <td class="cell-image"><div class="place-holder" onmouseover="<?php echo $_block->getJsObjectName(); ?>.loadImage('__file__')"><span><?php echo Mage::helper('catalog')->__('Roll Over for preview') ?></span></div><img src="<?php echo $this->getSkinUrl('images/spacer.gif')?>" width="100" style="display:none;" alt="" /></td> <td class="cell-label"><input type="text" class="input-text" onkeyup="<?php echo $_block->getJsObjectName(); ?>.updateImage('__file__')" onchange="<?php echo $_block->getJsObjectName(); ?>.updateImage('__file__')" /></td> <td class="cell-position"><input type="text" class="input-text validate-number" onkeyup="<?php echo $_block->getJsObjectName(); ?>.updateImage('__file__')" onchange="<?php echo $_block->getJsObjectName(); ?>.updateImage('__file__')" /></td> <?php foreach ($_block->getImageTypes() as $typeId=>$type): ?> diff --git a/app/design/adminhtml/default/default/template/downloadable/sales/items/column/downloadable/creditmemo/name.phtml b/app/design/adminhtml/default/default/template/downloadable/sales/items/column/downloadable/creditmemo/name.phtml index 4496922160..e88d03e2c3 100644 --- a/app/design/adminhtml/default/default/template/downloadable/sales/items/column/downloadable/creditmemo/name.phtml +++ b/app/design/adminhtml/default/default/template/downloadable/sales/items/column/downloadable/creditmemo/name.phtml @@ -33,14 +33,18 @@ <?php foreach ($this->getOrderOptions() as $_option): ?> <dt><?php echo $_option['label'] ?></dt> <dd> - <?php echo $this->htmlEscape(Mage::helper('core/string')->truncate($_option['value'], 55, '', $_remainder));?> - <?php if ($_remainder):?> - ... <span id="<?php echo $_id = 'id' . uniqid()?>"><?php echo $this->htmlEscape($_remainder) ?></span> - <script type="text/javascript"> - $('<?php echo $_id ?>').hide(); - $('<?php echo $_id ?>').up().observe('mouseover', function(){$('<?php echo $_id ?>').show();}); - $('<?php echo $_id ?>').up().observe('mouseout', function(){$('<?php echo $_id ?>').hide();}); - </script> + <?php if (isset($_option['custom_view']) && $_option['custom_view']): ?> + <?php echo $_option['value'];?> + <?php else: ?> + <?php echo Mage::helper('core/string')->truncate($_option['value'], 55, '', $_remainder);?> + <?php if ($_remainder):?> + ... <span id="<?php echo $_id = 'id' . uniqid()?>"><?php echo $_remainder ?></span> + <script type="text/javascript"> + $('<?php echo $_id ?>').hide(); + $('<?php echo $_id ?>').up().observe('mouseover', function(){$('<?php echo $_id ?>').show();}); + $('<?php echo $_id ?>').up().observe('mouseout', function(){$('<?php echo $_id ?>').hide();}); + </script> + <?php endif;?> <?php endif;?> </dd> <?php endforeach; ?> diff --git a/app/design/adminhtml/default/default/template/downloadable/sales/items/column/downloadable/invoice/name.phtml b/app/design/adminhtml/default/default/template/downloadable/sales/items/column/downloadable/invoice/name.phtml index 749b838283..a3f3ff9fcc 100644 --- a/app/design/adminhtml/default/default/template/downloadable/sales/items/column/downloadable/invoice/name.phtml +++ b/app/design/adminhtml/default/default/template/downloadable/sales/items/column/downloadable/invoice/name.phtml @@ -33,14 +33,18 @@ <?php foreach ($this->getOrderOptions() as $_option): ?> <dt><?php echo $_option['label'] ?></dt> <dd> - <?php echo $this->htmlEscape(Mage::helper('core/string')->truncate($_option['value'], 55, '', $_remainder));?> - <?php if ($_remainder):?> - ... <span id="<?php echo $_id = 'id' . uniqid()?>"><?php echo $this->htmlEscape($_remainder) ?></span> - <script type="text/javascript"> - $('<?php echo $_id ?>').hide(); - $('<?php echo $_id ?>').up().observe('mouseover', function(){$('<?php echo $_id ?>').show();}); - $('<?php echo $_id ?>').up().observe('mouseout', function(){$('<?php echo $_id ?>').hide();}); - </script> + <?php if (isset($_option['custom_view']) && $_option['custom_view']): ?> + <?php echo $_option['value'];?> + <?php else: ?> + <?php echo Mage::helper('core/string')->truncate($_option['value'], 55, '', $_remainder);?> + <?php if ($_remainder):?> + ... <span id="<?php echo $_id = 'id' . uniqid()?>"><?php echo $_remainder ?></span> + <script type="text/javascript"> + $('<?php echo $_id ?>').hide(); + $('<?php echo $_id ?>').up().observe('mouseover', function(){$('<?php echo $_id ?>').show();}); + $('<?php echo $_id ?>').up().observe('mouseout', function(){$('<?php echo $_id ?>').hide();}); + </script> + <?php endif;?> <?php endif;?> </dd> <?php endforeach; ?> diff --git a/app/design/adminhtml/default/default/template/downloadable/sales/items/column/downloadable/name.phtml b/app/design/adminhtml/default/default/template/downloadable/sales/items/column/downloadable/name.phtml index ea79b3c3f6..9db6f78a93 100644 --- a/app/design/adminhtml/default/default/template/downloadable/sales/items/column/downloadable/name.phtml +++ b/app/design/adminhtml/default/default/template/downloadable/sales/items/column/downloadable/name.phtml @@ -33,14 +33,18 @@ <?php foreach ($this->getOrderOptions() as $_option): ?> <dt><?php echo $_option['label'] ?></dt> <dd> - <?php echo $this->htmlEscape(Mage::helper('core/string')->truncate($_option['value'], 55, '', $_remainder));?> - <?php if ($_remainder):?> - ... <span id="<?php echo $_id = 'id' . uniqid()?>"><?php echo $this->htmlEscape($_remainder) ?></span> - <script type="text/javascript"> - $('<?php echo $_id ?>').hide(); - $('<?php echo $_id ?>').up().observe('mouseover', function(){$('<?php echo $_id ?>').show();}); - $('<?php echo $_id ?>').up().observe('mouseout', function(){$('<?php echo $_id ?>').hide();}); - </script> + <?php if (isset($_option['custom_view']) && $_option['custom_view']): ?> + <?php echo $_option['value'];?> + <?php else: ?> + <?php echo Mage::helper('core/string')->truncate($_option['value'], 55, '', $_remainder);?> + <?php if ($_remainder):?> + ... <span id="<?php echo $_id = 'id' . uniqid()?>"><?php echo $_remainder ?></span> + <script type="text/javascript"> + $('<?php echo $_id ?>').hide(); + $('<?php echo $_id ?>').up().observe('mouseover', function(){$('<?php echo $_id ?>').show();}); + $('<?php echo $_id ?>').up().observe('mouseout', function(){$('<?php echo $_id ?>').hide();}); + </script> + <?php endif;?> <?php endif;?> </dd> <?php endforeach; ?> diff --git a/app/design/adminhtml/default/default/template/login.phtml b/app/design/adminhtml/default/default/template/login.phtml index 351ee72056..22ad500e32 100644 --- a/app/design/adminhtml/default/default/template/login.phtml +++ b/app/design/adminhtml/default/default/template/login.phtml @@ -57,7 +57,7 @@ <div class="clear"></div> <div class="form-buttons"> <a class="left" href="<?php echo Mage::helper('adminhtml')->getUrl('adminhtml/index/forgotpassword') ?>"><?php echo Mage::helper('adminhtml')->__('Forgot your password?') ?></a> - <input type="submit" onclick="loginForm.submit()" class="form-button" value="<?php echo Mage::helper('adminhtml')->__('Login') ?>" title="<?php echo Mage::helper('adminhtml')->__('Login') ?>" /></div> + <input type="submit" class="form-button" value="<?php echo Mage::helper('adminhtml')->__('Login') ?>" title="<?php echo Mage::helper('adminhtml')->__('Login') ?>" /></div> </div> <p class="legal"><?php echo Mage::helper('adminhtml')->__('Magento is a trademark of Irubin Consulting Inc. DBA Varien. Copyright © %s Irubin Consulting Inc.', date('Y')) ?></p> </form> diff --git a/app/design/adminhtml/default/default/template/sales/items/column/name.phtml b/app/design/adminhtml/default/default/template/sales/items/column/name.phtml index 0765bbd418..39f18efb6a 100644 --- a/app/design/adminhtml/default/default/template/sales/items/column/name.phtml +++ b/app/design/adminhtml/default/default/template/sales/items/column/name.phtml @@ -38,8 +38,8 @@ <?php foreach ($this->getOrderOptions() as $_option): ?> <dt><?php echo $_option['label'] ?></dt> <dd> - <?php if (Mage::helper('catalog/product_options')->isHtmlFormattedOptionValue($_option['value'])): ?> - <?php echo $_option['value'];?> + <?php if (isset($_option['custom_view']) && $_option['custom_view']): ?> + <?php echo $this->getCustomizedOptionValue($_option); ?> <?php else: ?> <?php echo Mage::helper('core/string')->truncate($_option['value'], 55, '', $_remainder);?> <?php if ($_remainder):?> diff --git a/app/design/frontend/default/default/layout/amazonpayments.xml b/app/design/frontend/default/default/layout/amazonpayments.xml new file mode 100644 index 0000000000..f2d4073a6d --- /dev/null +++ b/app/design/frontend/default/default/layout/amazonpayments.xml @@ -0,0 +1,61 @@ +<?xml version="1.0"?> +<!-- +/** + * 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_default + * @package Mage + * @copyright Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com) + * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) + */ + +--> +<layout version="0.1.0"> + +<!-- +Shopping cart +--> + + <checkout_cart_index> + <!-- Mage_AmazonPayments --> + <reference name="checkout.cart.methods"> + <block type="amazonpayments/link_shortcut" name="checkout.cart.methods.amazon_cba" template="amazonpayments/link.phtml"/> + </reference> + </checkout_cart_index> + + <amazonpayments_cba_success> + <reference name="root"> + <action method="setTemplate"><template>page/2columns-right.phtml</template></action> + </reference> + <reference name="content"> + <block type="amazonpayments/cba_success" name="amazonpayments_cba_success" template="amazonpayments/cba/success.phtml"></block> + </reference> + </amazonpayments_cba_success> + + + <amazonpayments_asp_pay> + <reference name="root"> + <action method="setTemplate"><template>page/2columns-right.phtml</template></action> + </reference> + <reference name="content"> + <!-- block type="amazonpayments/asp_redirect" name="amazonpayments_asp_redirect" template="page/redirect.phtml" / --> + <block type="amazonpayments/asp_redirect" name="amazonpayments_asp_redirect" template="amazonpayments/asp/redirect.phtml" /> + </reference> + </amazonpayments_asp_pay> +</layout> \ No newline at end of file diff --git a/app/design/frontend/default/default/layout/catalog.xml b/app/design/frontend/default/default/layout/catalog.xml index 66289b5979..0e2db373f1 100644 --- a/app/design/frontend/default/default/layout/catalog.xml +++ b/app/design/frontend/default/default/layout/catalog.xml @@ -201,6 +201,7 @@ Product view <block type="catalog/product_view" name="product.info.addtocart" as="addtocart" template="catalog/product/view/addtocart.phtml"/> <block type="catalog/product_view" name="product.info.options.wrapper" as="product_options_wrapper" template="catalog/product/view/options/wrapper.phtml"> + <block type="core/template" name="options_js" template="catalog/product/view/options/js.phtml"/> <block type="catalog/product_view_options" name="product.info.options" as="product_options" template="catalog/product/view/options.phtml"> <action method="addOptionRenderer"><type>text</type><block>catalog/product_view_options_type_text</block><template>catalog/product/view/options/type/text.phtml</template></action> <action method="addOptionRenderer"><type>file</type><block>catalog/product_view_options_type_file</block><template>catalog/product/view/options/type/file.phtml</template></action> diff --git a/app/design/frontend/default/default/layout/page.xml b/app/design/frontend/default/default/layout/page.xml index eb5130e099..5a3d0f524e 100644 --- a/app/design/frontend/default/default/layout/page.xml +++ b/app/design/frontend/default/default/layout/page.xml @@ -52,7 +52,7 @@ Default layout, loads most of the pages <action method="addCss"><stylesheet>css/menu.css</stylesheet></action> <action method="addCss"><stylesheet>css/clears.css</stylesheet></action> - <action method="addItem"><type>skin_css</type><name>css/iestyles.css</name><params/><if>IE</if></action> + <action method="addItem"><type>skin_css</type><name>css/iestyles.css</name><params/><if>lt IE 8</if></action> <action method="addItem"><type>skin_css</type><name>css/ie7minus.css</name><params/><if>lt IE 7</if></action> <action method="addItem"><type>js</type><name>lib/ds-sleight.js</name><params/><if>lt IE 7</if></action> @@ -104,7 +104,7 @@ Default layout, loads most of the pages <action method="addCss"><stylesheet>css/menu.css</stylesheet></action> <action method="addCss"><stylesheet>css/clears.css</stylesheet></action> - <action method="addItem"><type>skin_css</type><name>css/iestyles.css</name><params/><if>IE</if></action> + <action method="addItem"><type>skin_css</type><name>css/iestyles.css</name><params/><if>lt IE 8</if></action> <action method="addItem"><type>skin_css</type><name>css/ie7minus.css</name><params/><if>lt IE 7</if></action> <action method="addItem"><type>js</type><name>lib/ds-sleight.js</name><params/><if>lt IE 7</if></action> diff --git a/app/design/frontend/default/default/template/amazonpayments/asp/form.phtml b/app/design/frontend/default/default/template/amazonpayments/asp/form.phtml new file mode 100644 index 0000000000..059143759a --- /dev/null +++ b/app/design/frontend/default/default/template/amazonpayments/asp/form.phtml @@ -0,0 +1,35 @@ +<?php +/** + * 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_default + * @package Mage_AmazonPayments + * @copyright Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com) + * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) + */ +?> +<fieldset class="form-list"> + <?php + $_code=$this->getMethodCode() ?> + <ul id="payment_form_<?php echo $_code ?>" style="display:none"> + <li> + <?php echo $this->__('Your billing address will be ignored and you will be redirected to Amazon Simple Pay website.') ?> + </li> + </ul> +</fieldset> \ No newline at end of file diff --git a/app/design/frontend/default/default/template/amazonpayments/asp/redirect.phtml b/app/design/frontend/default/default/template/amazonpayments/asp/redirect.phtml new file mode 100644 index 0000000000..3fdb3f0a20 --- /dev/null +++ b/app/design/frontend/default/default/template/amazonpayments/asp/redirect.phtml @@ -0,0 +1,37 @@ +<?php +/** + * 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_default + * @package Mage_AmazonPayments + * @copyright Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com) + * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) + */ +?> +<html> + <body> + <?php echo($this->__('You will be redirected to Amazon Simple Pay in a few seconds.')); ?> + <form id="amazonpayments_asp_checkout" method="POST" action="<?php echo($this->getRedirectUrl()); ?>"> + <?php foreach ($this->getRedirectParams() as $name => $value) { ?> + <input type="hidden" name="<?php echo($name); ?>" value="<?php echo($value); ?>"/> + <?php }?> + </form> + <script type="text/javascript">document.getElementById("amazonpayments_asp_checkout").submit();</script> + </body> +</html> diff --git a/app/design/frontend/default/default/template/amazonpayments/asp/shortcut.phtml b/app/design/frontend/default/default/template/amazonpayments/asp/shortcut.phtml new file mode 100644 index 0000000000..fcb5597618 --- /dev/null +++ b/app/design/frontend/default/default/template/amazonpayments/asp/shortcut.phtml @@ -0,0 +1,33 @@ +<?php +/** + * 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_blank + * @package Mage_AmazonPayments + * @copyright Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com) + * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) + */ + +/** + * @see Mage_AmazonPayments_Block_Link + */ +?> +<a href="<?php echo($this->getCheckoutUrl()); ?>"> + <img src="<?php echo($this->getButtonImageUrl()); ?>" border="0"> +</a> diff --git a/app/design/frontend/default/default/template/amazonpayments/cba/form.phtml b/app/design/frontend/default/default/template/amazonpayments/cba/form.phtml new file mode 100644 index 0000000000..75c294388f --- /dev/null +++ b/app/design/frontend/default/default/template/amazonpayments/cba/form.phtml @@ -0,0 +1,35 @@ +<?php +/** + * 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_default + * @package Mage_AmazonPayments + * @copyright Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com) + * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) + */ +?> +<fieldset class="form-list"> + <?php + $_code=$this->getMethodCode() ?> + <ul id="payment_form_<?php echo $_code ?>" style="display:none"> + <li> + <?php echo $this->__('Your billing address will be ignored and you will be redirected to Checkout by Amazon website.') ?> + </li> + </ul> +</fieldset> \ No newline at end of file diff --git a/app/design/frontend/default/default/template/amazonpayments/cba/success.phtml b/app/design/frontend/default/default/template/amazonpayments/cba/success.phtml new file mode 100644 index 0000000000..3aa71b7c3c --- /dev/null +++ b/app/design/frontend/default/default/template/amazonpayments/cba/success.phtml @@ -0,0 +1,35 @@ +<?php +/** + * 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_default + * @package Mage + * @copyright Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com) + * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) + */ +?> +SUCCESS +<div class="page-head"> + <h3><?php echo $this->__('Your order has been received') ?></h3> +</div> +<p><strong><?php echo $this->__('Thank you for your purchase!') ?></strong></p> +<p><?php echo $this->__('You will receive an order confirmation email with details of your order and a link to track its progress.') ?></p> +<div class="button-set"> + <button type="button" class="form-button" onclick="window.location='<?php #echo $this->getUrl() ?>'"><span><?php echo $this->__('Continue Shopping') ?></span></button> +</div> \ No newline at end of file diff --git a/app/design/frontend/default/default/template/amazonpayments/form.phtml b/app/design/frontend/default/default/template/amazonpayments/form.phtml new file mode 100644 index 0000000000..a82b2f28a6 --- /dev/null +++ b/app/design/frontend/default/default/template/amazonpayments/form.phtml @@ -0,0 +1,34 @@ +<?php +/** + * 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_default + * @package Mage_AmazonCheckout + * @copyright Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com) + * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) + */ +?> +<fieldset class="form-list"> + <?php $_code=$this->getMethodCode() ?> + <ul id="payment_form_<?php echo $_code ?>" style="display:none"> + <li> + <?php echo $this->__('You will be redirected to Amazon Checkout website when you place an order.') ?> + </li> + </ul> +</fieldset> \ No newline at end of file diff --git a/app/design/frontend/default/default/template/amazonpayments/link.phtml b/app/design/frontend/default/default/template/amazonpayments/link.phtml new file mode 100644 index 0000000000..2b82425b07 --- /dev/null +++ b/app/design/frontend/default/default/template/amazonpayments/link.phtml @@ -0,0 +1,49 @@ +<?php +/** + * 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_default + * @package Mage_AmazonPayments + * @copyright Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com) + * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) + */ + +/** + * @see Mage_AmazonPayments_Block_Link + */ +?> + +<li> + <?php if (!$this->getIsDisabled()): ?> + <?php if ($this->getIsOneClickEnabled()): ?> + <script type="text/javascript" src="https://images-na.ssl-images-amazon.com/images/G/01/cba/js/jquery.js"></script> + <script type="text/javascript"> + //<![CDATA[ + jQuery.noConflict(); + //]]> + </script> + <script type="text/javascript" src="https://images-na.ssl-images-amazon.com/images/G/01/cba/js/widget/widget.js"></script> + <form action="<?php echo $this->getCheckoutUrl();?>" method="POST" id="cbaOneClickForm"> + <input alt="<?php echo Mage::helper('amazonpayments')->__('Checkout by Amazon');?>" src="<?php echo $this->htmlEscape($this->getImageUrl())?>" type="image" /> + </form> + <?php else: ?> + <a href="<?php echo $this->getCheckoutUrl();?>"><img src="<?php echo $this->htmlEscape($this->getImageUrl())?>" alt="<?php echo Mage::helper('amazonpayments')->__('Checkout by Amazon');?>"></a> + <?php endif; ?> + <?php endif; ?> +</li> \ No newline at end of file diff --git a/app/design/frontend/default/default/template/bundle/sales/order/creditmemo/items/renderer.phtml b/app/design/frontend/default/default/template/bundle/sales/order/creditmemo/items/renderer.phtml index 30f244ce33..4baebcb375 100644 --- a/app/design/frontend/default/default/template/bundle/sales/order/creditmemo/items/renderer.phtml +++ b/app/design/frontend/default/default/template/bundle/sales/order/creditmemo/items/renderer.phtml @@ -296,7 +296,7 @@ <?php foreach ($_options as $_option) : ?> <dt><?php echo $this->htmlEscape($_option['label']) ?></dt> <?php if (!$this->getPrintStatus()): ?> - <?php $_formatedOptionValue = $this->getFormatedOptionValue($_option['value']) ?> + <?php $_formatedOptionValue = $this->getFormatedOptionValue($_option) ?> <dd<?php if (isset($_formatedOptionValue['full_view'])): ?> class="truncated"<?php endif; ?>> <?php echo $_formatedOptionValue['value'] ?> <?php if (isset($_formatedOptionValue['full_view'])): ?> @@ -309,7 +309,7 @@ <?php endif; ?> </dd> <?php else: ?> - <dd><?php echo $this->htmlEscape($_option['value']) ?></dd> + <dd><?php echo $this->htmlEscape( (isset($_option['print_value']) ? $_option['print_value'] : $_option['value']) ) ?></dd> <?php endif; ?> <?php endforeach; ?> </dl> diff --git a/app/design/frontend/default/default/template/bundle/sales/order/invoice/items/renderer.phtml b/app/design/frontend/default/default/template/bundle/sales/order/invoice/items/renderer.phtml index 38e7855bb4..7e66d47c38 100644 --- a/app/design/frontend/default/default/template/bundle/sales/order/invoice/items/renderer.phtml +++ b/app/design/frontend/default/default/template/bundle/sales/order/invoice/items/renderer.phtml @@ -321,7 +321,7 @@ <?php foreach ($_options as $_option) : ?> <dt><?php echo $this->htmlEscape($_option['label']) ?></dt> <?php if (!$this->getPrintStatus()): ?> - <?php $_formatedOptionValue = $this->getFormatedOptionValue($_option['value']) ?> + <?php $_formatedOptionValue = $this->getFormatedOptionValue($_option) ?> <dd<?php if (isset($_formatedOptionValue['full_view'])): ?> class="truncated"<?php endif; ?>> <?php echo $_formatedOptionValue['value'] ?> <?php if (isset($_formatedOptionValue['full_view'])): ?> @@ -334,7 +334,7 @@ <?php endif; ?> </dd> <?php else: ?> - <dd><?php echo $this->htmlEscape($_option['value']) ?></dd> + <dd><?php echo $this->htmlEscape( (isset($_option['print_value']) ? $_option['print_value'] : $_option['value']) ) ?></dd> <?php endif; ?> <?php endforeach; ?> </dl> diff --git a/app/design/frontend/default/default/template/bundle/sales/order/items/renderer.phtml b/app/design/frontend/default/default/template/bundle/sales/order/items/renderer.phtml index 5cccf91dfc..f8cc2020cb 100644 --- a/app/design/frontend/default/default/template/bundle/sales/order/items/renderer.phtml +++ b/app/design/frontend/default/default/template/bundle/sales/order/items/renderer.phtml @@ -344,7 +344,7 @@ <?php foreach ($_options as $_option) : ?> <dt><?php echo $this->htmlEscape($_option['label']) ?></dt> <?php if (!$this->getPrintStatus()): ?> - <?php $_formatedOptionValue = $this->getFormatedOptionValue($_option['value']) ?> + <?php $_formatedOptionValue = $this->getFormatedOptionValue($_option) ?> <dd<?php if (isset($_formatedOptionValue['full_view'])): ?> class="truncated"<?php endif; ?>> <?php echo $_formatedOptionValue['value'] ?> <?php if (isset($_formatedOptionValue['full_view'])): ?> @@ -357,7 +357,7 @@ <?php endif; ?> </dd> <?php else: ?> - <dd><?php echo $this->htmlEscape($_option['value']) ?></dd> + <dd><?php echo $this->htmlEscape( (isset($_option['print_value']) ? $_option['print_value'] : $_option['value']) ) ?></dd> <?php endif; ?> <?php endforeach; ?> </dl> diff --git a/app/design/frontend/default/default/template/bundle/sales/order/shipment/items/renderer.phtml b/app/design/frontend/default/default/template/bundle/sales/order/shipment/items/renderer.phtml index c3e3782965..fbd13528ab 100644 --- a/app/design/frontend/default/default/template/bundle/sales/order/shipment/items/renderer.phtml +++ b/app/design/frontend/default/default/template/bundle/sales/order/shipment/items/renderer.phtml @@ -82,7 +82,7 @@ <?php foreach ($_options as $_option) : ?> <dt><?php echo $this->htmlEscape($_option['label']) ?></dt> <?php if (!$this->getPrintStatus()): ?> - <?php $_formatedOptionValue = $this->getFormatedOptionValue($_option['value']) ?> + <?php $_formatedOptionValue = $this->getFormatedOptionValue($_option) ?> <dd<?php if (isset($_formatedOptionValue['full_view'])): ?> class="truncated"<?php endif; ?>> <?php echo $_formatedOptionValue['value'] ?> <?php if (isset($_formatedOptionValue['full_view'])): ?> @@ -95,7 +95,7 @@ <?php endif; ?> </dd> <?php else: ?> - <dd><?php echo $this->htmlEscape($_option['value']) ?></dd> + <dd><?php echo $this->htmlEscape( (isset($_option['print_value']) ? $_option['print_value'] : $_option['value']) ) ?></dd> <?php endif; ?> <?php endforeach; ?> </dl> diff --git a/app/design/frontend/default/default/template/catalog/navigation/left.phtml b/app/design/frontend/default/default/template/catalog/navigation/left.phtml index 8a0611ba0b..8014a3bb2e 100644 --- a/app/design/frontend/default/default/template/catalog/navigation/left.phtml +++ b/app/design/frontend/default/default/template/catalog/navigation/left.phtml @@ -1,4 +1,3 @@ -<?php if (!Mage::registry('current_category')) return ?> <?php /** * Magento @@ -24,14 +23,14 @@ * @copyright Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ -?> -<?php + /** * Category left navigation * * @see Mage_Catalog_Block_Navigation */ ?> +<?php if (!Mage::registry('current_category')) return ?> <?php $_categories=$this->getCurrentChildCategories() ?> <?php $_count = is_array($_categories)?count($_categories):$_categories->count(); ?> <?php if($_count): ?> diff --git a/app/design/frontend/default/default/template/catalog/product/new.phtml b/app/design/frontend/default/default/template/catalog/product/new.phtml index de2151654c..73c98fb43a 100644 --- a/app/design/frontend/default/default/template/catalog/product/new.phtml +++ b/app/design/frontend/default/default/template/catalog/product/new.phtml @@ -33,9 +33,7 @@ <?php if ($i>5): continue; endif; ?> <td> <div> - <a href="<?php echo $_product->getProductUrl() ?>" title="<?php echo $this->htmlEscape($_product->getName()) ?>"> - <img class="product-image" src="<?php echo $this->helper('catalog/image')->init($_product, 'small_image')->resize(80, 77) ?>" width="80" height="77" alt="<?php echo $this->htmlEscape($_product->getName()) ?>" /> - </a> + <a href="<?php echo $_product->getProductUrl() ?>" title="<?php echo $this->htmlEscape($_product->getName()) ?>"><img class="product-image" src="<?php echo $this->helper('catalog/image')->init($_product, 'small_image')->resize(80, 77) ?>" width="80" height="77" alt="<?php echo $this->htmlEscape($_product->getName()) ?>" /></a> </div> <p><a class="product-name" href="<?php echo $_product->getProductUrl() ?>" title="<?php echo $this->htmlEscape($_product->getName()) ?>)"><?php echo $this->htmlEscape($_product->getName()) ?></a></p> <?php echo $this->getReviewsSummaryHtml($_product, 'short') ?> diff --git a/app/design/frontend/default/default/template/catalog/product/view/options/js.phtml b/app/design/frontend/default/default/template/catalog/product/view/options/js.phtml new file mode 100644 index 0000000000..2ff3685e51 --- /dev/null +++ b/app/design/frontend/default/default/template/catalog/product/view/options/js.phtml @@ -0,0 +1,60 @@ +<script type="text/javascript"> +//<![CDATA[ +var DateOption = Class.create({ + + getDaysInMonth: function(month, year) + { + var curDate = new Date(); + if (!month) { + month = curDate.getMonth(); + } + if (!year) { + year = curDate.getFullYear(); + } + return 32 - new Date(year, month, 32).getDate(); + }, + + reloadMonth: function(event) + { + var selectEl = event.findElement(); + var idParts = selectEl.id.split("_"); + if (idParts.length != 3) { + return false; + } + var optionIdPrefix = idParts[0] + "_" + idParts[1]; + var month = parseInt($(optionIdPrefix + "_month").value); + var year = parseInt($(optionIdPrefix + "_year").value); + var dayEl = $(optionIdPrefix + "_day"); + + var days = this.getDaysInMonth(month > 0 ? month-1 : month, year); + + //remove days + for (var i = dayEl.options.length - 1; i >= 0; i--) { + if (dayEl.options[i].value > days) { + dayEl.remove(dayEl.options[i].index); + } + } + + // add days + var lastDay = parseInt(dayEl.options[dayEl.options.length-1].value); + for (i = lastDay + 1; i <= days; i++) { + this.addOption(dayEl, i, i); + } + }, + + addOption: function(select, text, value) + { + var option = document.createElement('OPTION'); + option.value = value; + option.text = text; + + if (select.options.add) { + select.options.add(option); + } else { + select.appendChild(option); + } + } +}); +var dateOption = new DateOption(); +//]]> +</script> diff --git a/app/design/frontend/default/default/template/catalog/product/view/options/type/date.phtml b/app/design/frontend/default/default/template/catalog/product/view/options/type/date.phtml index fc775135fa..6313ef2943 100644 --- a/app/design/frontend/default/default/template/catalog/product/view/options/type/date.phtml +++ b/app/design/frontend/default/default/template/catalog/product/view/options/type/date.phtml @@ -32,7 +32,16 @@ <?php if ($_option->getType() == Mage_Catalog_Model_Product_Option::OPTION_TYPE_DATE_TIME || $_option->getType() == Mage_Catalog_Model_Product_Option::OPTION_TYPE_DATE): ?> + <?php echo $this->getDateHtml() ?> + + <?php if (!$this->useCalendar()): ?> + <script type="text/javascript"> + Event.observe('options_<?php echo $_optionId ?>_month', 'change', dateOption.reloadMonth.bind(dateOption)); + Event.observe('options_<?php echo $_optionId ?>_year', 'change', dateOption.reloadMonth.bind(dateOption)); + </script> + <?php endif; ?> + <?php endif; ?> <?php if ($_option->getType() == Mage_Catalog_Model_Product_Option::OPTION_TYPE_DATE_TIME diff --git a/app/design/frontend/default/default/template/checkout/cart/item/default.phtml b/app/design/frontend/default/default/template/checkout/cart/item/default.phtml index 7bc7d18ac2..da6fc2de67 100644 --- a/app/design/frontend/default/default/template/checkout/cart/item/default.phtml +++ b/app/design/frontend/default/default/template/checkout/cart/item/default.phtml @@ -34,7 +34,7 @@ <?php if ($_options = $this->getOptionList()):?> <dl class="item-options"> <?php foreach ($_options as $_option) : ?> - <?php $_formatedOptionValue = $this->getFormatedOptionValue($_option['value']) ?> + <?php $_formatedOptionValue = $this->getFormatedOptionValue($_option) ?> <dt><?php echo $this->htmlEscape($_option['label']) ?></dt> <dd<?php if (isset($_formatedOptionValue['full_view'])): ?> class="truncated"<?php endif; ?>><?php echo $_formatedOptionValue['value'] ?> <?php if (isset($_formatedOptionValue['full_view'])): ?> diff --git a/app/design/frontend/default/default/template/checkout/multishipping/item/default.phtml b/app/design/frontend/default/default/template/checkout/multishipping/item/default.phtml index 340c36432c..2f65138c75 100644 --- a/app/design/frontend/default/default/template/checkout/multishipping/item/default.phtml +++ b/app/design/frontend/default/default/template/checkout/multishipping/item/default.phtml @@ -29,7 +29,7 @@ <?php if ($_options = $this->getOptionList()):?> <dl class="item-options"> <?php foreach ($_options as $_option) : ?> - <?php $_formatedOptionValue = $this->getFormatedOptionValue($_option['value']) ?> + <?php $_formatedOptionValue = $this->getFormatedOptionValue($_option) ?> <dt><?php echo $this->htmlEscape($_option['label']) ?></dt> <dd<?php if (isset($_formatedOptionValue['full_view'])): ?> class="truncated"<?php endif; ?>><?php echo $_formatedOptionValue['value'] ?> <?php if (isset($_formatedOptionValue['full_view'])): ?> diff --git a/app/design/frontend/default/default/template/checkout/onepage/login.phtml b/app/design/frontend/default/default/template/checkout/onepage/login.phtml index 219b1bbc69..b8a8648850 100644 --- a/app/design/frontend/default/default/template/checkout/onepage/login.phtml +++ b/app/design/frontend/default/default/template/checkout/onepage/login.phtml @@ -42,12 +42,12 @@ <ul class="form-list"> <?php if( $this->getQuote()->isAllowedGuestCheckout() ): ?> <li> - <input type="radio" name="checkout_method" id="login:guest" value="guest"<?php if($this->getQuote()->getCheckoutMethod()=='guest'): ?> checked="checked"<?php endif ?> /> + <input type="radio" name="checkout_method" id="login:guest" value="guest"<?php if($this->getQuote()->getCheckoutMethod()==Mage_Sales_Model_Quote::CHECKOUT_METHOD_GUEST): ?> checked="checked"<?php endif ?> /> <label for="login:guest"><?php echo $this->__('Checkout as Guest') ?></label> </li> <?php endif; ?> <li> - <input type="radio" name="checkout_method" id="login:register" value="register"<?php if($this->getQuote()->getCheckoutMethod()=='register' || !$this->getQuote()->isAllowedGuestCheckout()): ?> checked="checked"<?php endif ?> /> + <input type="radio" name="checkout_method" id="login:register" value="register"<?php if($this->getQuote()->getCheckoutMethod()==Mage_Sales_Model_Quote::CHECKOUT_METHOD_REGISTER || !$this->getQuote()->isAllowedGuestCheckout()): ?> checked="checked"<?php endif ?> /> <label for="login:register"><?php echo $this->__('Register') ?></label> </li> </ul> diff --git a/app/design/frontend/default/default/template/checkout/onepage/review/item.phtml b/app/design/frontend/default/default/template/checkout/onepage/review/item.phtml index 2d45ebe46a..9b3c304bde 100644 --- a/app/design/frontend/default/default/template/checkout/onepage/review/item.phtml +++ b/app/design/frontend/default/default/template/checkout/onepage/review/item.phtml @@ -31,7 +31,7 @@ <?php if ($_options = $this->getOptionList()):?> <dl class="item-options"> <?php foreach ($_options as $_option) : ?> - <?php $_formatedOptionValue = $this->getFormatedOptionValue($_option['value']) ?> + <?php $_formatedOptionValue = $this->getFormatedOptionValue($_option) ?> <dt><?php echo $this->htmlEscape($_option['label']) ?></dt> <dd<?php if (isset($_formatedOptionValue['full_view'])): ?> class="truncated"<?php endif; ?>><?php echo $_formatedOptionValue['value'] ?> <?php if (isset($_formatedOptionValue['full_view'])): ?> diff --git a/app/design/frontend/default/default/template/downloadable/checkout/cart/item/default.phtml b/app/design/frontend/default/default/template/downloadable/checkout/cart/item/default.phtml index 6f4428f700..a9af45b102 100644 --- a/app/design/frontend/default/default/template/downloadable/checkout/cart/item/default.phtml +++ b/app/design/frontend/default/default/template/downloadable/checkout/cart/item/default.phtml @@ -34,7 +34,7 @@ <?php if ($_options = $this->getOptionList()):?> <dl class="item-options"> <?php foreach ($_options as $_option) : ?> - <?php $_formatedOptionValue = $this->getFormatedOptionValue($_option['value']) ?> + <?php $_formatedOptionValue = $this->getFormatedOptionValue($_option) ?> <dt><?php echo $this->htmlEscape($_option['label']) ?></dt> <dd<?php if (isset($_formatedOptionValue['full_view'])): ?> class="truncated"<?php endif; ?>><?php echo $_formatedOptionValue['value'] ?> <?php if (isset($_formatedOptionValue['full_view'])): ?> diff --git a/app/design/frontend/default/default/template/downloadable/checkout/multishipping/item/downloadable.phtml b/app/design/frontend/default/default/template/downloadable/checkout/multishipping/item/downloadable.phtml index 1d72dcac7c..f5c83b2e35 100644 --- a/app/design/frontend/default/default/template/downloadable/checkout/multishipping/item/downloadable.phtml +++ b/app/design/frontend/default/default/template/downloadable/checkout/multishipping/item/downloadable.phtml @@ -29,7 +29,7 @@ <?php if ($_options = $this->getOptionList()):?> <dl class="item-options"> <?php foreach ($_options as $_option) : ?> - <?php $_formatedOptionValue = $this->getFormatedOptionValue($_option['value']) ?> + <?php $_formatedOptionValue = $this->getFormatedOptionValue($_option) ?> <dt><?php echo $this->htmlEscape($_option['label']) ?></dt> <dd<?php if (isset($_formatedOptionValue['full_view'])): ?> class="truncated"<?php endif; ?>><?php echo $_formatedOptionValue['value'] ?> <?php if (isset($_formatedOptionValue['full_view'])): ?> diff --git a/app/design/frontend/default/default/template/downloadable/checkout/onepage/review/item.phtml b/app/design/frontend/default/default/template/downloadable/checkout/onepage/review/item.phtml index d727a61053..21813d6bce 100644 --- a/app/design/frontend/default/default/template/downloadable/checkout/onepage/review/item.phtml +++ b/app/design/frontend/default/default/template/downloadable/checkout/onepage/review/item.phtml @@ -31,7 +31,7 @@ <?php if ($_options = $this->getOptionList()):?> <dl class="item-options"> <?php foreach ($_options as $_option) : ?> - <?php $_formatedOptionValue = $this->getFormatedOptionValue($_option['value']) ?> + <?php $_formatedOptionValue = $this->getFormatedOptionValue($_option) ?> <dt><?php echo $this->htmlEscape($_option['label']) ?></dt> <dd<?php if (isset($_formatedOptionValue['full_view'])): ?> class="truncated"<?php endif; ?>><?php echo $_formatedOptionValue['value'] ?> <?php if (isset($_formatedOptionValue['full_view'])): ?> diff --git a/app/design/frontend/default/default/template/downloadable/sales/order/creditmemo/items/renderer/downloadable.phtml b/app/design/frontend/default/default/template/downloadable/sales/order/creditmemo/items/renderer/downloadable.phtml index 26f93596c2..a401f4276f 100644 --- a/app/design/frontend/default/default/template/downloadable/sales/order/creditmemo/items/renderer/downloadable.phtml +++ b/app/design/frontend/default/default/template/downloadable/sales/order/creditmemo/items/renderer/downloadable.phtml @@ -33,7 +33,7 @@ <?php foreach ($_options as $_option) : ?> <dt><?php echo $this->htmlEscape($_option['label']) ?></dt> <?php if (!$this->getPrintStatus()): ?> - <?php $_formatedOptionValue = $this->getFormatedOptionValue($_option['value']) ?> + <?php $_formatedOptionValue = $this->getFormatedOptionValue($_option) ?> <dd<?php if (isset($_formatedOptionValue['full_view'])): ?> class="truncated"<?php endif; ?>><?php echo $_formatedOptionValue['value'] ?> <?php if (isset($_formatedOptionValue['full_view'])): ?> <div class="truncated_full_value"> @@ -45,7 +45,7 @@ <?php endif; ?> </dd> <?php else: ?> - <dd><?php echo $this->htmlEscape($_option['value']) ?></dd> + <dd><?php echo $this->htmlEscape( (isset($_option['print_value']) ? $_option['print_value'] : $_option['value']) ) ?></dd> <?php endif; ?> <?php endforeach; ?> </dl> diff --git a/app/design/frontend/default/default/template/downloadable/sales/order/invoice/items/renderer/downloadable.phtml b/app/design/frontend/default/default/template/downloadable/sales/order/invoice/items/renderer/downloadable.phtml index 4ec970da0b..1ba5786ce7 100644 --- a/app/design/frontend/default/default/template/downloadable/sales/order/invoice/items/renderer/downloadable.phtml +++ b/app/design/frontend/default/default/template/downloadable/sales/order/invoice/items/renderer/downloadable.phtml @@ -33,7 +33,7 @@ <?php foreach ($_options as $_option) : ?> <dt><?php echo $this->htmlEscape($_option['label']) ?></dt> <?php if (!$this->getPrintStatus()): ?> - <?php $_formatedOptionValue = $this->getFormatedOptionValue($_option['value']) ?> + <?php $_formatedOptionValue = $this->getFormatedOptionValue($_option) ?> <dd<?php if (isset($_formatedOptionValue['full_view'])): ?> class="truncated"<?php endif; ?>> <?php echo $_formatedOptionValue['value'] ?> <?php if (isset($_formatedOptionValue['full_view'])): ?> @@ -46,7 +46,7 @@ <?php endif; ?> </dd> <?php else: ?> - <dd><?php echo $this->htmlEscape($_option['value']) ?></dd> + <dd><?php echo $this->htmlEscape( (isset($_option['print_value']) ? $_option['print_value'] : $_option['value']) ) ?></dd> <?php endif; ?> <?php endforeach; ?> </dl> diff --git a/app/design/frontend/default/default/template/downloadable/sales/order/items/renderer/downloadable.phtml b/app/design/frontend/default/default/template/downloadable/sales/order/items/renderer/downloadable.phtml index 7766284305..ebfdb6fbfb 100644 --- a/app/design/frontend/default/default/template/downloadable/sales/order/items/renderer/downloadable.phtml +++ b/app/design/frontend/default/default/template/downloadable/sales/order/items/renderer/downloadable.phtml @@ -32,7 +32,7 @@ <?php foreach ($_options as $_option) : ?> <dt><?php echo $this->htmlEscape($_option['label']) ?></dt> <?php if (!$this->getPrintStatus()): ?> - <?php $_formatedOptionValue = $this->getFormatedOptionValue($_option['value']) ?> + <?php $_formatedOptionValue = $this->getFormatedOptionValue($_option) ?> <dd<?php if (isset($_formatedOptionValue['full_view'])): ?> class="truncated"<?php endif; ?>> <?php echo $_formatedOptionValue['value'] ?> <?php if (isset($_formatedOptionValue['full_view'])): ?> @@ -45,7 +45,7 @@ <?php endif; ?> </dd> <?php else: ?> - <dd><?php echo $this->htmlEscape($_option['value']) ?></dd> + <dd><?php echo $this->htmlEscape( (isset($_option['print_value']) ? $_option['print_value'] : $_option['value']) ) ?></dd> <?php endif; ?> <?php endforeach; ?> </dl> diff --git a/app/design/frontend/default/default/template/sales/order/creditmemo/items/renderer/default.phtml b/app/design/frontend/default/default/template/sales/order/creditmemo/items/renderer/default.phtml index 56817e599a..f6d7fb7059 100644 --- a/app/design/frontend/default/default/template/sales/order/creditmemo/items/renderer/default.phtml +++ b/app/design/frontend/default/default/template/sales/order/creditmemo/items/renderer/default.phtml @@ -33,7 +33,7 @@ <?php foreach ($_options as $_option) : ?> <dt><?php echo $this->htmlEscape($_option['label']) ?></dt> <?php if (!$this->getPrintStatus()): ?> - <?php $_formatedOptionValue = $this->getFormatedOptionValue($_option['value']) ?> + <?php $_formatedOptionValue = $this->getFormatedOptionValue($_option) ?> <dd<?php if (isset($_formatedOptionValue['full_view'])): ?> class="truncated"<?php endif; ?>><?php echo $_formatedOptionValue['value'] ?> <?php if (isset($_formatedOptionValue['full_view'])): ?> <div class="truncated_full_value"> @@ -45,7 +45,7 @@ <?php endif; ?> </dd> <?php else: ?> - <dd><?php echo $this->htmlEscape($_option['value']) ?></dd> + <dd><?php echo $this->htmlEscape( (isset($_option['print_value']) ? $_option['print_value'] : $_option['value']) ) ?></dd> <?php endif; ?> <?php endforeach; ?> </dl> diff --git a/app/design/frontend/default/default/template/sales/order/invoice/items/renderer/default.phtml b/app/design/frontend/default/default/template/sales/order/invoice/items/renderer/default.phtml index e0bdf1f69f..2afe093a97 100644 --- a/app/design/frontend/default/default/template/sales/order/invoice/items/renderer/default.phtml +++ b/app/design/frontend/default/default/template/sales/order/invoice/items/renderer/default.phtml @@ -33,7 +33,7 @@ <?php foreach ($_options as $_option) : ?> <dt><?php echo $this->htmlEscape($_option['label']) ?></dt> <?php if (!$this->getPrintStatus()): ?> - <?php $_formatedOptionValue = $this->getFormatedOptionValue($_option['value']) ?> + <?php $_formatedOptionValue = $this->getFormatedOptionValue($_option) ?> <dd<?php if (isset($_formatedOptionValue['full_view'])): ?> class="truncated"<?php endif; ?>> <?php echo $_formatedOptionValue['value'] ?> <?php if (isset($_formatedOptionValue['full_view'])): ?> @@ -46,7 +46,7 @@ <?php endif; ?> </dd> <?php else: ?> - <dd><?php echo $this->htmlEscape($_option['value']) ?></dd> + <dd><?php echo $this->htmlEscape( (isset($_option['print_value']) ? $_option['print_value'] : $_option['value']) ) ?></dd> <?php endif; ?> <?php endforeach; ?> </dl> diff --git a/app/design/frontend/default/default/template/sales/order/items/renderer/default.phtml b/app/design/frontend/default/default/template/sales/order/items/renderer/default.phtml index f1da75ab3d..955e2bf4d7 100644 --- a/app/design/frontend/default/default/template/sales/order/items/renderer/default.phtml +++ b/app/design/frontend/default/default/template/sales/order/items/renderer/default.phtml @@ -32,7 +32,7 @@ <?php foreach ($_options as $_option) : ?> <dt><?php echo $this->htmlEscape($_option['label']) ?></dt> <?php if (!$this->getPrintStatus()): ?> - <?php $_formatedOptionValue = $this->getFormatedOptionValue($_option['value']) ?> + <?php $_formatedOptionValue = $this->getFormatedOptionValue($_option) ?> <dd<?php if (isset($_formatedOptionValue['full_view'])): ?> class="truncated"<?php endif; ?>> <?php echo $_formatedOptionValue['value'] ?> <?php if (isset($_formatedOptionValue['full_view'])): ?> @@ -45,7 +45,7 @@ <?php endif; ?> </dd> <?php else: ?> - <dd><?php echo $this->htmlEscape($_option['value']) ?></dd> + <dd><?php echo $this->htmlEscape( (isset($_option['print_value']) ? $_option['print_value'] : $_option['value']) ) ?></dd> <?php endif; ?> <?php endforeach; ?> </dl> diff --git a/app/design/frontend/default/default/template/sales/order/shipment/items/renderer/default.phtml b/app/design/frontend/default/default/template/sales/order/shipment/items/renderer/default.phtml index 2675abb834..16da04a4f2 100644 --- a/app/design/frontend/default/default/template/sales/order/shipment/items/renderer/default.phtml +++ b/app/design/frontend/default/default/template/sales/order/shipment/items/renderer/default.phtml @@ -33,7 +33,7 @@ <?php foreach ($_options as $_option) : ?> <dt><?php echo $this->htmlEscape($_option['label']) ?></dt> <?php if (!$this->getPrintStatus()): ?> - <?php $_formatedOptionValue = $this->getFormatedOptionValue($_option['value']) ?> + <?php $_formatedOptionValue = $this->getFormatedOptionValue($_option) ?> <dd<?php if (isset($_formatedOptionValue['full_view'])): ?> class="truncated"<?php endif; ?>> <?php echo $_formatedOptionValue['value'] ?> <?php if (isset($_formatedOptionValue['full_view'])): ?> @@ -46,7 +46,7 @@ <?php endif; ?> </dd> <?php else: ?> - <dd><?php echo $this->htmlEscape($_option['value']) ?></dd> + <dd><?php echo $this->htmlEscape( (isset($_option['print_value']) ? $_option['print_value'] : $_option['value']) ) ?></dd> <?php endif; ?> <?php endforeach; ?> </dl> diff --git a/app/design/install/default/default/template/install/begin.phtml b/app/design/install/default/default/template/install/begin.phtml index f4b65b85e1..630dfeeb08 100644 --- a/app/design/install/default/default/template/install/begin.phtml +++ b/app/design/install/default/default/template/install/begin.phtml @@ -37,48 +37,7 @@ <?php if(count($this->getMessagesBlock()->getMessages())==0): ?> <form action="<?php echo $this->getPostUrl() ?>" method="post"> <div style="height:20em; border:1px solid #ccc; margin-bottom:8px; padding:5px; background:#fff; overflow: auto; overflow-x:hidden; overflow-y:scroll;"> -<h4>Open Software License ("OSL") v. 3.0</h4> - -<p>This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work:</p> -<h5>Licensed under the Open Software License version 3.0</h5> -<p>Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following:</p> -<ul class="disc"> - <li>to reproduce the Original Work in copies, either alone or as part of a collective work</li> - <li>to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work</li> - <li>to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License</li> - <li>to perform the Original Work publicly</li> - <li>to display the Original Work publicly</li> -</ul> - -<p><strong>Grant of Patent License.</strong> Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works.</p> - -<p><strong>Grant of Source Code License.</strong> The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work.</p> - -<p><strong>Exclusions From License Grant.</strong> Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license.</p> - -<p><strong>External Deployment.</strong> The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c).</p> - -<p><strong>Attribution Rights.</strong> You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work.</p> - -<p><strong>Warranty of Provenance and Disclaimer of Warranty.</strong> Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer.</p> - -<p><strong>Limitation of Liability.</strong> Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation.</p> - -<p><strong>Acceptance and Termination.</strong> If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c).</p> - -<p><strong>Termination for Patent Action.</strong> This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware.</p> - -<p><strong>Jurisdiction, Venue and Governing Law.</strong> Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License.</p> - -<p><strong>Attorneys Fees.</strong> In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License.</p> - -<p><strong>Miscellaneous.</strong> If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable.</p> - -<p><strong>Definition of "You" in This License.</strong> "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.</p> - -<p><strong>Right to Use.</strong> You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You.</p> - -<p><strong>Modification of This License.</strong> This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under Open Software License ("OSL") v. 3.0" or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process.</p> +<?php echo $this->getLicenseHtml() ?> </div> <script type="text/javascript"> diff --git a/app/design/install/default/default/template/install/create_admin.phtml b/app/design/install/default/default/template/install/create_admin.phtml index 1002332db7..2cb8094a3f 100644 --- a/app/design/install/default/default/template/install/create_admin.phtml +++ b/app/design/install/default/default/template/install/create_admin.phtml @@ -66,7 +66,7 @@ <li> <div class="input-box"> <label for="password"><?php echo $this->__('Password') ?> <span class="required">*</span></label><br/> - <input type="password" name="admin[password]" id="password" title="<?php echo $this->__('Password') ?>" class="required-entry validate-password input-text"/> + <input type="password" name="admin[password]" id="password" title="<?php echo $this->__('Password') ?>" class="required-entry validate-admin-password input-text"/> </div> <div class="input-box"> <label for="confirmation"><?php echo $this->__('Confirm Password') ?> <span class="required">*</span></label><br/> diff --git a/app/etc/modules/Mage_AmazonPayments.xml b/app/etc/modules/Mage_AmazonPayments.xml new file mode 100644 index 0000000000..eb0a2b3ed7 --- /dev/null +++ b/app/etc/modules/Mage_AmazonPayments.xml @@ -0,0 +1,39 @@ +<?xml version="1.0"?> +<!-- +/** + * 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 Mage + * @package Mage_AmazonPayments + * @copyright Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com) + * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) + */ +--> +<config> + <modules> + <Mage_AmazonPayments> + <active>true</active> + <codePool>core</codePool> + <depends> + <Mage_Sales /> + <Mage_Payment /> + </depends> + </Mage_AmazonPayments> + </modules> +</config> diff --git a/app/locale/en_US/Mage_Adminhtml.csv b/app/locale/en_US/Mage_Adminhtml.csv index 59393fd3b6..68d9465745 100644 --- a/app/locale/en_US/Mage_Adminhtml.csv +++ b/app/locale/en_US/Mage_Adminhtml.csv @@ -23,8 +23,10 @@ "--Please Select--","--Please Select--" "1 Hour","1 Hour" "12 Hours","12 Hours" +"12h AM/PM","12h AM/PM" "2 Hours","2 Hours" "24 Hours","24 Hours" +"24h","24h" "2YTD","2YTD" "6 Hours","6 Hours" "<h1 class=""page-heading"">404 Error</h1><p>Page not found.</p>","<h1 class=""page-heading"">404 Error</h1><p>Page not found.</p>" @@ -67,6 +69,8 @@ "Add Urlrewrite for a Product","Add Urlrewrite for a Product" "Add after","Add after" "Add new Role","Add new Role" +"Add new store view map","Add new store view map" +"Add new website map","Add new website map" "Address Type:","Address Type:" "Admin","Admin" "Advanced Admin Section","Advanced Admin Section" @@ -161,7 +165,8 @@ "Catalog Rewrites","Catalog Rewrites" "Catalog Rewrites was refreshed successfully","Catalog Rewrites was refreshed successfully" "Catalog Rewrites were refreshed successfully","Catalog Rewrites were refreshed successfully" -"CatalogInventory Stock Status was rebuilded successfully","CatalogInventory Stock Status was rebuilded successfully" +"CatalogInventory Stock Status rebuild error. Please try again later","CatalogInventory Stock Status rebuild error. Please try again later" +"CatalogInventory Stock Status was rebuilt successfully","CatalogInventory Stock Status was rebuilt successfully" "Category","Category" "Category:","Category:" "Changelog","Changelog" @@ -191,7 +196,10 @@ "Create DB Backup","Create DB Backup" "Create Extension Package","Create Extension Package" "Create New Attribute","Create New Attribute" +"Create New Staging Store View","Create New Staging Store View" "Create Urlrewrite:","Create Urlrewrite:" +"Create a backup","Create a backup" +"Created <strong>%s</strong> items","Created <strong>%s</strong> items" "Created At","Created At" "Credit Card Number","Credit Card Number" "Credit Card Number: xxxx-%s","Credit Card Number: xxxx-%s" @@ -225,11 +233,14 @@ "Dashboard","Dashboard" "Data Format","Data Format" "Data transfer:","Data transfer:" +"Date","Date" +"Date & Time","Date & Time" "Date Added","Date Added" "Date Updated","Date Updated" "Date selection:","Date selection:" "Date selector","Date selector" "Date:","Date:" +"Day","Day" "Decimal separator:","Decimal separator:" "Default (Admin) Values","Default (Admin) Values" "Default Config","Default Config" @@ -298,10 +309,9 @@ "Error while deleting this role. Please try again later.","Error while deleting this role. Please try again later." "Error while deleting this set.","Error while deleting this set." "Error while finished process. Please refresh cache","Error while finished process. Please refresh cache" -"Error while rebuilded CatalogInventory Stock Status. Please try again later","Error while rebuilded CatalogInventory Stock Status. Please try again later" -"Error while rebuilded Search Index. Please try again later","Error while rebuilded Search Index. Please try again later" "Error while refreshed Catalog Rewrites. Please try again later","Error while refreshed Catalog Rewrites. Please try again later" "Error while refreshed Layered Navigation Indices. Please try again later","Error while refreshed Layered Navigation Indices. Please try again later" +"Error while saving account. Please check all required fields","Error while saving account. Please check all required fields" "Error while saving account. Please try again later","Error while saving account. Please try again later" "Error while saving this configuration: ","Error while saving this configuration: " "Error while saving this role. Please try again later.","Error while saving this role. Please try again later." @@ -329,11 +339,16 @@ "File name:","File name:" "File size should be more than 0 bytes","File size should be more than 0 bytes" "Final Price","Final Price" +"Finished items creation.","Finished items creation." "Finished profile execution.","Finished profile execution." "First Name","First Name" "First Name:","First Name:" "Firstname","Firstname" "Fixed","Fixed" +"Flat Catalog Category rebuild error","Flat Catalog Category rebuild error" +"Flat Catalog Category was rebuilt successfully","Flat Catalog Category was rebuilt successfully" +"Flat Catalog Product rebuild error. Please try again later","Flat Catalog Product rebuild error. Please try again later" +"Flat Catalog Product was rebuilt successfully","Flat Catalog Product was rebuilt successfully" "For category","For category" "For latest version visit: %s","For latest version visit: %s" "For product","For product" @@ -392,9 +407,10 @@ "Instant Payment Notification (IPN)","Instant Payment Notification (IPN)" "Interactive","Interactive" "Interface Locale: %s","Interface Locale: %s" -"Invalid Form Key","Invalid Form Key" +"Invalid Form Key. Please refresh the page.","Invalid Form Key. Please refresh the page." "Invalid Import Service Specified","Invalid Import Service Specified" "Invalid POST data (please check post_max_size and upload_max_filesize settings in you php.ini file)","Invalid POST data (please check post_max_size and upload_max_filesize settings in you php.ini file)" +"Invalid Secret Key. Please refresh the page.","Invalid Secret Key. Please refresh the page." "Invalid URL","Invalid URL" "Invalid Username or Password.","Invalid Username or Password." "Invalid directory: %s","Invalid directory: %s" @@ -421,6 +437,7 @@ "Issue Number","Issue Number" "Issuer: %s","Issuer: %s" "Items","Items" +"Items to merge","Items to merge" "Kb","Kb" "Last 24 hours","Last 24 hours" "Last 5 Orders","Last 5 Orders" @@ -479,14 +496,18 @@ "Manage Stores","Manage Stores" "Manage osCommerce Orders","Manage osCommerce Orders" "Manage osCommerce Profiles","Manage osCommerce Profiles" +"Mapping Configuration","Mapping Configuration" "Matched expression","Matched expression" "Max","Max" "Maximum","Maximum" "Mb","Mb" "Media (.avi, .flv, .swf)","Media (.avi, .flv, .swf)" +"Merge Now","Merge Now" "Messages Inbox","Messages Inbox" "Min","Min" "Minimum","Minimum" +"Module version conflict!","Module version conflict!" +"Month","Month" "Most Viewed Products","Most Viewed Products" "Multiple Select","Multiple Select" "My Account","My Account" @@ -624,6 +645,7 @@ "Please wait, loading...","Please wait, loading..." "Please wait...","Please wait..." "Please, add a few answers to this poll first","Please, add a few answers to this poll first" +"Please, select a store view","Please, select a store view" "Please, select visible in stores to this poll first","Please, select visible in stores to this poll first" "Poll Manager","Poll Manager" "Poll was successfully deleted","Poll was successfully deleted" @@ -640,6 +662,7 @@ "Price alert subscription was saved successfully","Price alert subscription was saved successfully" "Price:","Price:" "Primary Billing Address","Primary Billing Address" +"Processed <strong>%s%% %s/%d</strong> items","Processed <strong>%s%% %s/%d</strong> items" "Processed <strong>%s%% %s/%d</strong> records","Processed <strong>%s%% %s/%d</strong> records" "Product","Product" "Product Thumbnail Itself","Product Thumbnail Itself" @@ -668,6 +691,8 @@ "Rating was successfully saved","Rating was successfully saved" "Read details","Read details" "Rebuild","Rebuild" +"Rebuild Flat Catalog Category","Rebuild Flat Catalog Category" +"Rebuild Flat Catalog Product","Rebuild Flat Catalog Product" "Recent Orders","Recent Orders" "Recommended","Recommended" "Recursive Dir","Recursive Dir" @@ -736,9 +761,11 @@ "Save cache settings","Save cache settings" "Save data and Create Package","Save data and Create Package" "Save package with custom package file name","Save package with custom package file name" +"Schedule Merge","Schedule Merge" "Search","Search" "Search Index","Search Index" -"Search Index was rebuilded successfully","Search Index was rebuilded successfully" +"Search Index rebuild error. Please try again later","Search Index rebuild error. Please try again later" +"Search Index was rebuilt successfully","Search Index was rebuilt successfully" "Search Term","Search Term" "Search Terms","Search Terms" "Select","Select" @@ -748,6 +775,8 @@ "Select Range","Select Range" "Select Visible","Select Visible" "Select date","Select date" +"Select website from map","Select website from map" +"Select website to map","Select website to map" "Selected allow currency ""%s"" is not available in installed currencies","Selected allow currency ""%s"" is not available in installed currencies" "Selected base currency is not available in installed currencies","Selected base currency is not available in installed currencies" "Selected default display currency is not available in allowed currencies","Selected default display currency is not available in allowed currencies" @@ -782,6 +811,8 @@ "Specific Countries","Specific Countries" "Spreadsheet Name:","Spreadsheet Name:" "Stability","Stability" +"Staging Store: ","Staging Store: " +"Staging Website: ","Staging Website: " "Start Date","Start Date" "Starting profile execution, please wait...","Starting profile execution, please wait..." "State/Province:","State/Province:" @@ -793,6 +824,7 @@ "Store Email Addresses Section","Store Email Addresses Section" "Store View","Store View" "Store:","Store:" +"Store: ","Store: " "Subject","Subject" "Submit","Submit" "Subpackage","Subpackage" @@ -827,6 +859,7 @@ "The archive can be uncompressed with <a href=""%s"">%s</a> on Windows systems","The archive can be uncompressed with <a href=""%s"">%s</a> on Windows systems" "The information in this tab has been changed.","The information in this tab has been changed." "This account is","This account is" +"This account is inactive.","This account is inactive." "This attribute set don\'t have attributes which we can use for configurable product","This attribute set don\'t have attributes which we can use for configurable product" "This attribute shares the same value in all the stores","This attribute shares the same value in all the stores" "This is a required field.","This is a required field." @@ -835,6 +868,7 @@ "This tab contains invalid data. Please solve the problem before saving.","This tab contains invalid data. Please solve the problem before saving." "This user no longer exists","This user no longer exists" "Thumbnail","Thumbnail" +"Time","Time" "Time selection:","Time selection:" "Time:","Time:" "To","To" @@ -889,6 +923,7 @@ "Upload local file:","Upload local file:" "Url Rewrite Management","Url Rewrite Management" "Urlrewrite Information","Urlrewrite Information" +"Use Config Settings","Use Config Settings" "Use Default Value","Use Default Value" "Use default","Use default" "Use website","Use website" @@ -925,12 +960,16 @@ "Web Services","Web Services" "Web services","Web services" "Website","Website" +"Website: ","Website: " +"Websites","Websites" +"Websites / Stores","Websites / Stores" "What is this?","What is this?" "Wishlist Report","Wishlist Report" "Wrong column format","Wrong column format" "Wrong tab configuration","Wrong tab configuration" "XML","XML" "YTD","YTD" +"Year","Year" "Yes","Yes" "Yes (only price with tax)","Yes (only price with tax)" "You can not delete self assigned roles.","You can not delete self assigned roles." @@ -941,7 +980,6 @@ "You need specify carrier.","You need specify carrier." "You need to specify order items","You need to specify order items" "You successfully logged out.","You successfully logged out." -"Your Account has been deactivated.","Your Account has been deactivated." "Your answers contain duplicates.","Your answers contain duplicates." "Your server PHP settings allow you to upload files not more than %s at a time. Please modify post_max_size (currently is %s) and upload_max_filesize (currently is %s) values in php.ini if you want to upload larger files.","Your server PHP settings allow you to upload files not more than %s at a time. Please modify post_max_size (currently is %s) and upload_max_filesize (currently is %s) values in php.ini if you want to upload larger files." "Zip/Postal Code","Zip/Postal Code" @@ -953,6 +991,7 @@ "critical","critical" "example: ""sitemap/"" or ""/"" for base path (path must be writeable)","example: ""sitemap/"" or ""/"" for base path (path must be writeable)" "example: sitemap.xml","example: sitemap.xml" +"from","from" "images/logo.gif","images/logo.gif" "items selected","items selected" "major","major" @@ -960,6 +999,7 @@ "minor","minor" "notice","notice" "of %s pages","of %s pages" +"or","or" "per page","per page" "store(%s) scope","store(%s) scope" "to","to" diff --git a/app/locale/en_US/Mage_Api.csv b/app/locale/en_US/Mage_Api.csv index 6ea0c209ce..0bc33c07e6 100644 --- a/app/locale/en_US/Mage_Api.csv +++ b/app/locale/en_US/Mage_Api.csv @@ -23,6 +23,7 @@ "Account Created on:","Account Created on:" "Action","Action" "Actions","Actions" +"Add","Add" "Add Contents Path","Add Contents Path" "Add Field with URL:","Add Field with URL:" "Add Maintainer","Add Maintainer" @@ -32,6 +33,8 @@ "Add PHP Extension dependency","Add PHP Extension dependency" "Add Package dependency","Add Package dependency" "Add Subpackage dependency","Add Subpackage dependency" +"Add new store view map","Add new store view map" +"Add new website map","Add new website map" "Address Type:","Address Type:" "All","All" "All Websites","All Websites" @@ -59,6 +62,9 @@ "Connect with the Magento Community","Connect with the Magento Community" "Contents","Contents" "Country:","Country:" +"Create New Staging Store View","Create New Staging Store View" +"Create a backup","Create a backup" +"Created <strong>%s</strong> items","Created <strong>%s</strong> items" "Credit Card Number","Credit Card Number" "Credit Card Number: xxxx-%s","Credit Card Number: xxxx-%s" "Credit Card Type","Credit Card Type" @@ -113,6 +119,7 @@ "File Information","File Information" "File name:","File name:" "Final Price","Final Price" +"Finished items creation.","Finished items creation." "First Name:","First Name:" "For latest version visit: %s","For latest version visit: %s" "From","From" @@ -141,6 +148,7 @@ "Invoice Totals","Invoice Totals" "Issue Number","Issue Number" "Issuer: %s","Issuer: %s" +"Items to merge","Items to merge" "Last 5 Orders","Last 5 Orders" "Last 5 Search Terms","Last 5 Search Terms" "Last Logged In (%s):","Last Logged In (%s):" @@ -163,8 +171,11 @@ "Magento™ is a trademark of Irubin Consulting Inc. DBA Varien.<br/>Copyright © %s Irubin Consulting Inc.","Magento™ is a trademark of Irubin Consulting Inc. DBA Varien.<br/>Copyright © %s Irubin Consulting Inc." "Maintainers","Maintainers" "Manage Stores","Manage Stores" +"Mapping Configuration","Mapping Configuration" "Max","Max" +"Merge Now","Merge Now" "Min","Min" +"Module version conflict!","Module version conflict!" "N/A","N/A" "Name","Name" "Name on Card","Name on Card" @@ -226,12 +237,14 @@ "Please use only letters (a-z) or numbers (0-9) or spaces and # only in this field.","Please use only letters (a-z) or numbers (0-9) or spaces and # only in this field." "Please use this date format: dd/mm/yyyy. For example 17/03/2006 for the 17th of March, 2006.","Please use this date format: dd/mm/yyyy. For example 17/03/2006 for the 17th of March, 2006." "Please wait, loading...","Please wait, loading..." +"Please, select a store view","Please, select a store view" "Prev. month (hold for menu)","Prev. month (hold for menu)" "Prev. year (hold for menu)","Prev. year (hold for menu)" "Previous page","Previous page" "Price","Price" "Price:","Price:" "Primary Billing Address","Primary Billing Address" +"Processed <strong>%s%% %s/%d</strong> items","Processed <strong>%s%% %s/%d</strong> items" "Product","Product" "Products","Products" "Profile Information","Profile Information" @@ -241,6 +254,7 @@ "Recommended","Recommended" "Remote FTP","Remote FTP" "Remote URL:","Remote URL:" +"Remove","Remove" "Resource Access","Resource Access" "Resources","Resources" "Role","Role" @@ -249,11 +263,14 @@ "Roles Resources","Roles Resources" "SKU","SKU" "SKU:","SKU:" +"Schedule Merge","Schedule Merge" "Select All","Select All" "Select Date","Select Date" "Select Range","Select Range" "Select Visible","Select Visible" "Select date","Select date" +"Select website from map","Select website from map" +"Select website to map","Select website to map" "Shipment Comments","Shipment Comments" "Shipment History","Shipment History" "Shipping Address","Shipping Address" @@ -264,12 +281,15 @@ "Some items in this order have different invoice and shipment types. You can create shipment only after the invoice is created.","Some items in this order have different invoice and shipment types. You can create shipment only after the invoice is created." "Sort Order","Sort Order" "Spreadsheet Name:","Spreadsheet Name:" +"Staging Store: ","Staging Store: " +"Staging Website: ","Staging Website: " "Start Date","Start Date" "State/Province:","State/Province:" "Status","Status" "Status:","Status:" "Stock Quantity:","Stock Quantity:" "Store:","Store:" +"Store: ","Store: " "Subpackage","Subpackage" "Subpackages","Subpackages" "Summary:","Summary:" @@ -314,6 +334,9 @@ "Warning!\r\nThis action will remove those users from already assigned roles\r\nAre you sure?","Warning!\r\nThis action will remove those users from already assigned roles\r\nAre you sure?" "Web Services","Web Services" "Web Services Configuration","Web Services Configuration" +"Website: ","Website: " +"Websites","Websites" +"Websites / Stores","Websites / Stores" "What is this?","What is this?" "Yes","Yes" "You have","You have" @@ -328,5 +351,6 @@ "minor","minor" "notice","notice" "of %s pages","of %s pages" +"or","or" "per page","per page" "to","to" diff --git a/app/locale/en_US/Mage_Catalog.csv b/app/locale/en_US/Mage_Catalog.csv index a65fdd9a26..c11eb366c4 100644 --- a/app/locale/en_US/Mage_Catalog.csv +++ b/app/locale/en_US/Mage_Catalog.csv @@ -8,6 +8,7 @@ "-- Please Select --","-- Please Select --" "A name is required","A name is required" "ALL GROUPS","ALL GROUPS" +"AM","AM" "Action","Action" "Add Attribute","Add Attribute" "Add Design Change","Add Design Change" @@ -38,6 +39,9 @@ "All Product Types","All Product Types" "All products of this set will be deleted! Are you sure you want to delete this attribute set?","All products of this set will be deleted! Are you sure you want to delete this attribute set?" "Allow HTML-tags on Front-end","Allow HTML-tags on Front-end" +"Allowed File Extensions","Allowed File Extensions" +"Allowed file extensions to upload","Allowed file extensions to upload" +"Amount","Amount" "Apply To","Apply To" "Apply To Configurable/Grouped Product","Apply To Configurable/Grouped Product" "Approved","Approved" @@ -86,6 +90,7 @@ "Can be used only with catalog input type Dropdown","Can be used only with catalog input type Dropdown" "Can be used only with catalog input type Dropdown, Multiple Select and Price","Can be used only with catalog input type Dropdown, Multiple Select and Price" "Can\'t create image.","Can\'t create image." +"Cannot create writeable directory '%s'","Cannot create writeable directory '%s'" "Cart Item Attribute","Cart Item Attribute" "Catalog","Catalog" "Catalog Input Type for Store Owner","Catalog Input Type for Store Owner" @@ -148,7 +153,9 @@ "Customers for alert %s was successfuly added to queue","Customers for alert %s was successfuly added to queue" "Data Type for Saving in Database","Data Type for Saving in Database" "Date","Date" +"Date & Time Custom Options","Date & Time Custom Options" "Date Subscribed","Date Subscribed" +"Date fields order","Date fields order" "Datetime","Datetime" "Decimal","Decimal" "Decimal Number","Decimal Number" @@ -167,12 +174,12 @@ "Delete Selected Group","Delete Selected Group" "Delete category","Delete category" "Delete product","Delete product" +"Depends on design theme","Depends on design theme" "Design","Design" "Disabled","Disabled" "Display in Suggested Terms","Display in Suggested Terms" "Double click on a group to rename it","Double click on a group to rename it" "Double click on above image to view full picture","Double click on above image to view full picture" -"Downloadable Product","Downloadable Product" "Dropdown","Dropdown" "Duplicate","Duplicate" "Duplicate website tier price customer group and quantity.","Duplicate website tier price customer group and quantity." @@ -196,7 +203,8 @@ "Failed","Failed" "Failed to move file: %s","Failed to move file: %s" "Feature Products","Feature Products" -"File Extension","File Extension" +"File options format is not valid","File options format is not valid" +"File upload failed","File upload failed" "Filter model name must be declared","Filter model name must be declared" "Filter must be as object. Set correct filter please","Filter must be as object. Set correct filter please" "Filterable (no results)","Filterable (no results)" @@ -204,6 +212,7 @@ "Filters must be as array","Filters must be as array" "First Name","First Name" "Fixed","Fixed" +"Flat Catalog module has a limit of %2\$d filterable and/or sort able attributes. Currently there are %1\$d. Please reduce the number of filterable/sort able attributes in order to use this module.","Flat Catalog module has a limit of %2\$d filterable and/or sort able attributes. Currently there are %1\$d. Please reduce the number of filterable/sort able attributes in order to use this module." "For internal use. Must be unique with no spaces","For internal use. Must be unique with no spaces" "For reindex enabled product(s) you need specify store or product","For reindex enabled product(s) you need specify store or product" "Frontend","Frontend" @@ -221,9 +230,8 @@ "Home","Home" "ID","ID" "ID: %s","ID: %s" -"If you do not specify an option value for a store then the default value will be used.","If you do not specify an option value for a store then the default value will be used." +"If you do not specify an option value for a specific store view then the default (Admin) value will be used.","If you do not specify an option value for a specific store view then the default (Admin) value will be used." "Image","Image" -"Image Size","Image Size" "Image content is not valid base64 data.","Image content is not valid base64 data." "Image file not found","Image file not found" "Image not exists","Image not exists" @@ -238,6 +246,7 @@ "Integer","Integer" "Integer Number","Integer Number" "Invalid Tier Prices","Invalid Tier Prices" +"Invalid attribute %s","Invalid attribute %s" "Invalid attribute option specified for attribute %s (%s), skipping the record","Invalid attribute option specified for attribute %s (%s), skipping the record" "Invalid attribute set specified, skipping the record","Invalid attribute set specified, skipping the record" "Invalid block: %s","Invalid block: %s" @@ -280,9 +289,13 @@ "Manage Titles (Size, Color, etc.)","Manage Titles (Size, Color, etc.)" "Max Characters","Max Characters" "Maximal Depth","Maximal Depth" +"Maximum Image Size","Maximum Image Size" "Maximum Qty Allowed in Shopping Cart","Maximum Qty Allowed in Shopping Cart" +"Maximum image height","Maximum image height" +"Maximum image width","Maximum image width" "Maximum number of characters","Maximum number of characters" "Media Image","Media Image" +"Minimum Lines Per Page","Minimum Lines Per Page" "Minimum Qty Allowed in Shopping Cart","Minimum Qty Allowed in Shopping Cart" "Minimum Qty for Item\'s Status to be Out of Stock","Minimum Qty for Item\'s Status to be Out of Stock" "Missing SKU, skipping the record","Missing SKU, skipping the record" @@ -314,12 +327,15 @@ "Number of results<br/>(For last time placed)","Number of results<br/>(For last time placed)" "OR","OR" "Old Price:","Old Price:" +"Option validation failed to add product to cart","Option validation failed to add product to cart" "Option:","Option:" "Options Control","Options Control" "Options is required","Options is required" +"Our customer service is available 24/7. Call us at (555) 555-0123.","Our customer service is available 24/7. Call us at (555) 555-0123." "Out of Stock","Out of Stock" "Out of stock","Out of stock" "Out of stock.","Out of stock." +"PM","PM" "Page Title Separator","Page Title Separator" "Page:","Page:" "Parent Category","Parent Category" @@ -336,9 +352,13 @@ "Please select product(s)","Please select product(s)" "Please select products for attributes update","Please select products for attributes update" "Please select static block ...","Please select static block ..." +"Please set up merge date/time .","Please set up merge date/time ." +"Please specify date required option(s)","Please specify date required option(s)" "Please specify the product option(s)","Please specify the product option(s)" "Please specify the product required option(s)","Please specify the product required option(s)" "Please specify the product(s) quantity","Please specify the product(s) quantity" +"Please specify time required option(s)","Please specify time required option(s)" +"Please, use four-digit year format","Please, use four-digit year format" "Position","Position" "Position In Layered Navigation","Position In Layered Navigation" "Position of attribute in layered navigation block","Position of attribute in layered navigation block" @@ -381,6 +401,7 @@ "Product duplicated","Product duplicated" "Product has required options","Product has required options" "Product links API (related, cross sells, up sells)","Product links API (related, cross sells, up sells)" +"Product listing sort by","Product listing sort by" "Product saving error.","Product saving error." "Product saving error. ","Product saving error. " "Product types API","Product types API" @@ -483,6 +504,7 @@ "Show Tags","Show Tags" "Simple Product","Simple Product" "Site Map","Site Map" +"Sitemap","Sitemap" "Skip import row, is not valid value ""%s"" for field ""%s""","Skip import row, is not valid value ""%s"" for field ""%s""" "Skip import row, required field ""%s"" for new customer not defined","Skip import row, required field ""%s"" for new customer not defined" "Skip import row, required field ""%s"" for new products not defined","Skip import row, required field ""%s"" for new products not defined" @@ -524,10 +546,11 @@ "This attribute no longer exists","This attribute no longer exists" "This group contains attributes, used in configurable products. Please move these attributes to another group and try again.","This group contains attributes, used in configurable products. Please move these attributes to another group and try again." "This group contains system attributes. Please move system attributes to another group and try again.","This group contains system attributes. Please move system attributes to another group and try again." +"This is a required option","This is a required option" "This search no longer exists","This search no longer exists" "Tier Pricing","Tier Pricing" "Tile","Tile" -"Time","Time" +"Time format","Time format" "Title","Title" "Top/Left","Top/Left" "Top/Right","Top/Right" @@ -561,13 +584,19 @@ "Url rewrie save problem.","Url rewrie save problem." "Use Config Settings","Use Config Settings" "Use Default Value","Use Default Value" +"Use Flat Catalog Category","Use Flat Catalog Category" +"Use Flat Catalog Product","Use Flat Catalog Product" "Use In Layered Navigation","Use In Layered Navigation" "Use In Layered Navigation<br/>(Can be used only with catalog input type 'Dropdown')","Use In Layered Navigation<br/>(Can be used only with catalog input type 'Dropdown')" "Use In Search Results Layered Navigation","Use In Search Results Layered Navigation" +"Use JavaScript Calendar","Use JavaScript Calendar" "Use To Create Configurable Product","Use To Create Configurable Product" +"Use Tree Like Category Sitemap","Use Tree Like Category Sitemap" "Use categories path for product URLs","Use categories path for product URLs" "Use in advanced search","Use in advanced search" "Use in quick search","Use in quick search" +"Used for sorting in product listing","Used for sorting in product listing" +"Used in product listing","Used in product listing" "Values Required","Values Required" "Varchar","Varchar" "View as","View as" @@ -575,12 +604,22 @@ "Visibility","Visibility" "Visible","Visible" "Visible on Catalog Pages on Front-end","Visible on Catalog Pages on Front-end" +"Visible on Product View Page on Front-end","Visible on Product View Page on Front-end" "Watermark","Watermark" "Watermark Default Size","Watermark Default Size" "Watermark Position","Watermark Position" "We Also Recommend","We Also Recommend" +"Web Site Item Must be checked","Web Site Item Must be checked" +"Web Site Store Must be checked","Web Site Store Must be checked" "Website","Website" "Websites","Websites" +"Wrong BuyRequest instance in options group","Wrong BuyRequest instance in options group" +"Wrong option instance type in options group","Wrong option instance type in options group" +"Wrong option type to get group instance.","Wrong option type to get group instance." +"Wrong product instance type in options group","Wrong product instance type in options group" +"Wrong quote item instance in options group","Wrong quote item instance in options group" +"Wrong quote item option instance in options group","Wrong quote item option instance in options group" +"Year Range","Year Range" "Yes","Yes" "Yes/No","Yes/No" "You cannot delete this attribute","You cannot delete this attribute" @@ -599,9 +638,12 @@ "comma separated","comma separated" "each","each" "ex. http://domain.com","ex. http://domain.com" +"leave blank if its not an image","leave blank if its not an image" "must be in the allowed values list","must be in the allowed values list" "per page","per page" "products","products" +"px.","px." "save","save" "select all","select all" "unselect all","unselect all" +"x","x" diff --git a/app/locale/en_US/Mage_CatalogSearch.csv b/app/locale/en_US/Mage_CatalogSearch.csv index 58be9f254e..7d3b26c219 100644 --- a/app/locale/en_US/Mage_CatalogSearch.csv +++ b/app/locale/en_US/Mage_CatalogSearch.csv @@ -18,13 +18,11 @@ "Maximum Search query length is %s. Your query was cut.","Maximum Search query length is %s. Your query was cut." "Minimal Query Length","Minimal Query Length" "Modify your search","Modify your search" -"Name","Name" "No","No" "No items were found using the following search criteria.","No items were found using the following search criteria." "Please enter ""0"" to enable layered navigation for any number of results","Please enter ""0"" to enable layered navigation for any number of results" "Popular Search Terms","Popular Search Terms" "Popular search terms","Popular search terms" -"Price","Price" "Relevance","Relevance" "Required Fields","Required Fields" "Results","Results" diff --git a/app/locale/en_US/Mage_Checkout.csv b/app/locale/en_US/Mage_Checkout.csv index a8062ac316..32aaca17cf 100644 --- a/app/locale/en_US/Mage_Checkout.csv +++ b/app/locale/en_US/Mage_Checkout.csv @@ -65,6 +65,7 @@ "Coupon code was canceled successfully.","Coupon code was canceled successfully." "Create Billing Address","Create Billing Address" "Create Shipping Address","Create Shipping Address" +"Customer Address is not valid.","Customer Address is not valid." "Data saving problem","Data saving problem" "Default Billing","Default Billing" "Default Shipping","Default Shipping" diff --git a/app/locale/en_US/Mage_Core.csv b/app/locale/en_US/Mage_Core.csv index 4b35018df1..fab0198237 100644 --- a/app/locale/en_US/Mage_Core.csv +++ b/app/locale/en_US/Mage_Core.csv @@ -2,6 +2,7 @@ "%s already exists","%s already exists" "-- Please Select --","-- Please Select --" "Add Block Names to Hints","Add Block Names to Hints" +"Add Secret Key to URLs","Add Secret Key to URLs" "Add Store Code to Urls","Add Store Code to Urls" "Admin","Admin" "Admin Base URL","Admin Base URL" @@ -124,11 +125,13 @@ "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"" can not be depended from ""%2$s""","Module ""%1$s"" can not be depended from ""%2$s""" +"Module ""%1$s"" requires module ""%2$s""","Module ""%1$s"" requires module ""%2$s""" "Name","Name" "New Design Change","New Design Change" "New Store","New Store" "New Store View","New Store View" "New Website","New Website" +"No","No" "Package","Package" "Please choose to register or to checkout as a guest","Please choose to register or to checkout as a guest" "Please define flag code.","Please define flag code." @@ -179,11 +182,13 @@ "Save Website","Save Website" "Search Engines Optimization","Search Engines Optimization" "Secure","Secure" +"Security","Security" "Select Date","Select Date" "Sender email","Sender email" "Sender name","Sender name" "Services","Services" "Session Cookie management","Session Cookie management" +"Session Lifetime, Seconds","Session Lifetime, Seconds" "Session Validation Settings","Session Validation Settings" "Set as default","Set as default" "Skin (Images / CSS)","Skin (Images / CSS)" @@ -249,6 +254,7 @@ "Validate HTTP_VIA","Validate HTTP_VIA" "Validate HTTP_X_FORWARDED_FOR","Validate HTTP_X_FORWARDED_FOR" "Validate REMOTE_ADDR","Validate REMOTE_ADDR" +"Values less than 60 are ignored.","Values less than 60 are ignored." "Web","Web" "Website","Website" "Website Information","Website Information" @@ -261,6 +267,7 @@ "Weekend Days","Weekend Days" "Welcome Text","Welcome Text" "Wrong number of arguments for %s","Wrong number of arguments for %s" +"Yes","Yes" "Your order can not be completed at this time as there is no payment methods available for it.","Your order can not be completed at this time as there is no payment methods available for it." -"Your order can not be completed at this time as there is no shipping methods available for it. Please make neccessary changes in your shipping address.","Your order can not be completed at this time as there is no shipping methods available for it. Please make neccessary changes in your shipping address." +"Your order can not be completed at this time as there is no shipping methods available for it. Please make necessary changes in your shipping address.","Your order can not be completed at this time as there is no shipping methods available for it. Please make necessary changes in your shipping address." "Your session has been expired, you will be relogged in now.","Your session has been expired, you will be relogged in now." diff --git a/app/locale/en_US/Mage_Customer.csv b/app/locale/en_US/Mage_Customer.csv index 6199b8e7ce..2dc08c1b4b 100644 --- a/app/locale/en_US/Mage_Customer.csv +++ b/app/locale/en_US/Mage_Customer.csv @@ -283,6 +283,7 @@ "Sales Statistics","Sales Statistics" "Save","Save" "Save Address","Save Address" +"Save And Continue Edit","Save And Continue Edit" "Save Customer","Save Customer" "Save Customer Group","Save Customer Group" "Save Password","Save Password" diff --git a/app/locale/en_US/Mage_Directory.csv b/app/locale/en_US/Mage_Directory.csv index 5324c374ea..41240b0787 100644 --- a/app/locale/en_US/Mage_Directory.csv +++ b/app/locale/en_US/Mage_Directory.csv @@ -2,6 +2,7 @@ "Allowed currencies","Allowed currencies" "Base currency","Base currency" "Cannot retrieve rate from %s","Cannot retrieve rate from %s" +"Connection timeout in seconds","Connection timeout in seconds" "Continue »","Continue »" "Country","Country" "Country Api","Country Api" @@ -38,4 +39,5 @@ "Unable to initialize import model","Unable to initialize import model" "Undefined rate from ""%s-%s""","Undefined rate from ""%s-%s""" "WARNING:","WARNING:" +"Webservicex","Webservicex" "Your current currency is: %s","Your current currency is: %s" diff --git a/app/locale/en_US/Mage_Downloadable.csv b/app/locale/en_US/Mage_Downloadable.csv index 0a4daabd92..de37d7442c 100644 --- a/app/locale/en_US/Mage_Downloadable.csv +++ b/app/locale/en_US/Mage_Downloadable.csv @@ -59,7 +59,7 @@ "U","U" "Unlimited","Unlimited" "View Order","View Order" -"You have no Downloadable Products.","You have no Downloadable Products." +"You have not purchased any downloadable products yet.","You have not purchased any downloadable products yet." "attachment","attachment" "download","download" "inline","inline" diff --git a/app/locale/en_US/Mage_Eav.csv b/app/locale/en_US/Mage_Eav.csv index ae58d4e7ab..74aba5405d 100644 --- a/app/locale/en_US/Mage_Eav.csv +++ b/app/locale/en_US/Mage_Eav.csv @@ -4,6 +4,7 @@ "Attribute used in configurable products.","Attribute used in configurable products." "Attribute with the same code","Attribute with the same code" "Data integrity: No header row found for attribute","Data integrity: No header row found for attribute" +"Default Product Listing Sort by not exists on Available Product Listing Sort by","Default Product Listing Sort by not exists on Available Product Listing Sort by" "Default option value is not defined","Default option value is not defined" "EAV types and attributes","EAV types and attributes" "Entity collection expected","Entity collection expected" diff --git a/app/locale/en_US/Mage_GoogleBase.csv b/app/locale/en_US/Mage_GoogleBase.csv index 63a4becaf8..617b71bfd4 100644 --- a/app/locale/en_US/Mage_GoogleBase.csv +++ b/app/locale/en_US/Mage_GoogleBase.csv @@ -13,6 +13,8 @@ "Attributes mapping","Attributes mapping" "AuthSub","AuthSub" "Available Products","Available Products" +"British Pound Sterling","British Pound Sterling" +"Cannot update Google Base Item for Store '%s'","Cannot update Google Base Item for Store '%s'" "Captcha confirmation error: %s","Captcha confirmation error: %s" "Captcha confirmed successfully","Captcha confirmed successfully" "Clicks","Clicks" @@ -26,6 +28,7 @@ "Edit Item Type","Edit Item Type" "Edit Item Type ""%s""","Edit Item Type ""%s""" "Error: %s","Error: %s" +"Euro","Euro" "Expires","Expires" "Germany","Germany" "Google Base","Google Base" @@ -67,6 +70,7 @@ "Total of %d items(s) were successfully removed from Google Base","Total of %d items(s) were successfully removed from Google Base" "Total of %d items(s) were successfully saved as Inactive items","Total of %d items(s) were successfully saved as Inactive items" "Total of %d product(s) were successfully added to Google Base","Total of %d product(s) were successfully added to Google Base" +"US Dollar","US Dollar" "Unable to connect to Google Base. Please, check Account settings in configuration.","Unable to connect to Google Base. Please, check Account settings in configuration." "United Kingdom","United Kingdom" "United States","United States" diff --git a/app/locale/en_US/Mage_Page.csv b/app/locale/en_US/Mage_Page.csv index 6b37bd6101..2991e3d60d 100644 --- a/app/locale/en_US/Mage_Page.csv +++ b/app/locale/en_US/Mage_Page.csv @@ -1,25 +1,48 @@ "%s Item(s)","%s Item(s)" "© 2008 Magento Demo Store. All Rights Reserved.","© 2008 Magento Demo Store. All Rights Reserved." +"(Shift-)Click or drag to change value","(Shift-)Click or drag to change value" "(ver. %s)","(ver. %s)" +"- 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" +"- Use the %s, %s buttons to select year","- Use the %s, %s buttons to select year" +"- or Shift-click to decrease it","- or Shift-click to decrease it" +"- or click and drag for faster selection.","- or click and drag for faster selection." +"About the calendar","About the calendar" "Click <a href=""%s"">here</a> if nothing has happened","Click <a href=""%s"">here</a> if nothing has happened" +"Close","Close" "Close Window","Close Window" +"DHTML Date/Time Selector","DHTML Date/Time Selector" +"Date selection:","Date selection:" "Default Description","Default Description" "Default welcome msg!","Default welcome msg!" +"Display %s first","Display %s first" +"Distributed under GNU LGPL. See %s for details.","Distributed under GNU LGPL. See %s for details." +"Drag to move","Drag to move" +"For latest version visit: %s","For latest version visit: %s" +"Go Today","Go Today" "Help Us to Keep Magento Healthy","Help Us to Keep Magento Healthy" "Interface Language","Interface Language" "Items %s to %s of %s total","Items %s to %s of %s total" "Next Page","Next Page" +"Next month (hold for menu)","Next month (hold for menu)" +"Next year (hold for menu)","Next year (hold for menu)" "Page:","Page:" +"Prev. month (hold for menu)","Prev. month (hold for menu)" +"Prev. year (hold for menu)","Prev. year (hold for menu)" "Previous Page","Previous Page" "Redirecting...","Redirecting..." "Report All Bugs","Report All Bugs" "Select Store","Select Store" +"Select date","Select date" "Show","Show" "Skip to Footer","Skip to Footer" "Skip to Left Column","Skip to Left Column" "Skip to Main Content","Skip to Main Content" "Skip to Right Column","Skip to Right Column" "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." +"Time selection:","Time selection:" +"Time:","Time:" "Welcome, %s!","Welcome, %s!" "You're currently on:","You're currently on:" "Your Language","Your Language" diff --git a/app/locale/en_US/Mage_Payment.csv b/app/locale/en_US/Mage_Payment.csv index 8778054e6c..a71069ac61 100644 --- a/app/locale/en_US/Mage_Payment.csv +++ b/app/locale/en_US/Mage_Payment.csv @@ -1,6 +1,7 @@ "--Please Select--","--Please Select--" "<label>Make Check payable to:</label> %s","<label>Make Check payable to:</label> %s" "<label>Make Check payable to</label>: %s","<label>Make Check payable to</label>: %s" +"Automatically invoice all items","Automatically invoice all items" "Can not configuration for payment method with code: %s","Can not configuration for payment method with code: %s" "Can not retrieve payment info model object.","Can not retrieve payment info model object." "Can not retrieve payment method instance","Can not retrieve payment method instance" diff --git a/app/locale/en_US/Mage_Reports.csv b/app/locale/en_US/Mage_Reports.csv index 81abd70432..1f0b7e9742 100644 --- a/app/locale/en_US/Mage_Reports.csv +++ b/app/locale/en_US/Mage_Reports.csv @@ -85,6 +85,7 @@ "Products","Products" "Products Report","Products Report" "Products Reviews","Products Reviews" +"Products Sold","Products Sold" "Products Tags","Products Tags" "Products in carts","Products in carts" "Purchases","Purchases" diff --git a/app/locale/en_US/template/email/amazonpayments_asp_notification_error.html b/app/locale/en_US/template/email/amazonpayments_asp_notification_error.html new file mode 100644 index 0000000000..508a5f4b5b --- /dev/null +++ b/app/locale/en_US/template/email/amazonpayments_asp_notification_error.html @@ -0,0 +1,8 @@ +<!--@subject Amazonpayments ASP notification error @--> +<div> + <div><b>Amazon Simple Pay notification message</b></div> + <div>Request:</div> + <div style="border:1px solid #000;">{{var request}}</div> + <div>Error:</div> + <div style="border:1px solid #000;">{{var error}}</div> +</div> \ No newline at end of file diff --git a/downloader/Maged/Controller.php b/downloader/Maged/Controller.php index e6a955c20e..4f5c36914c 100755 --- a/downloader/Maged/Controller.php +++ b/downloader/Maged/Controller.php @@ -1,403 +1,408 @@ -<?php -/** - * Magento - * - * NOTICE OF LICENSE - * - * This source file is subject to the Open Software License (OSL 3.0) - * that is bundled with this package in the file LICENSE.txt. - * It is also available through the world-wide-web at this URL: - * http://opensource.org/licenses/osl-3.0.php - * If you did not receive a copy of the license and are unable to - * obtain it through the world-wide-web, please send an email - * to license@magentocommerce.com so we can send you a copy immediately. - * - * DISCLAIMER - * - * Do not edit or add to this file if you wish to upgrade Magento to newer - * versions in the future. If you wish to customize Magento for your - * needs please refer to http://www.magentocommerce.com for more information. - * - * @category Varien - * @package Varien_Object - * @copyright Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com) - * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) - */ - -include_once "Maged/Model.php"; -include_once "Maged/View.php"; -include_once "Maged/Exception.php"; - -final class Maged_Controller -{ - const ACTION_KEY = 'A'; - - private static $_instance; - - private $_action; - private $_isDispatched = false; - private $_redirectUrl; - private $_rootDir; - - private $_view; - private $_config; - private $_session; - - private $_writable; - - private $_useCache; - - //////////////////////////// ACTIONS - - public function norouteAction() - { - header("HTTP/1.0 404 Invalid Action"); - echo $this->view()->template('noroute.phtml'); - } - - public function loginAction() - { - $this->view()->set('username', !empty($_GET['username']) ? $_GET['username'] : ''); - echo $this->view()->template('login.phtml'); - } - - public function logoutAction() - { - $this->session()->logout(); - $this->redirect($this->url()); - } - - public function indexAction() - { - if (!$this->isInstalled()) { - if (!$this->isWritable()) { - echo $this->view()->template('install/writable.phtml'); - } else { - $this->view()->set('mage_url', dirname(dirname($_SERVER['SCRIPT_NAME']))); - echo $this->view()->template('install/download.phtml'); - } - } else { - if (!$this->isWritable()) { - echo $this->view()->template('writable.phtml'); - } else { - $this->forward('pearPackages'); - } - } - } - - public function emptyAction() - { - $this->model('pear', true)->pear()->runHtmlConsole('Please wait, preparing for updates...'); - } - - public function pearGlobalAction() - { - echo $this->view()->template('pear/global.phtml'); - } - - public function pearInstallAllAction() - { - $this->model('pear', true)->installAll(!empty($_GET['force'])); - } - - public function pearUpgradeAllAction() - { - $this->model('pear', true)->upgradeAll(); - } - - public function pearPackagesAction() - { - $pear = $this->model('pear', true); - $this->view()->set('pear', $pear); - $this->view()->set('channels', $pear->pear()->getMagentoChannels()); - echo $this->view()->template('pear/packages.phtml'); - } - - public function pearPackagesPostAction() - { - $actions = isset($_POST['actions']) ? $_POST['actions'] : array(); - $this->model('pear', true)->applyPackagesActions($actions); - } - - public function pearInstallPackagePostAction() - { - if (!$_POST) { - echo "INVALID POST DATA"; - return; - } - $this->model('pear', true)->installPackage($_POST['install_package_id']); - } - - public function distUpgradeAction() - { - $pear = $this->model('pear', true); - $this->view()->set('pear', $pear); - $this->view()->set('state', $pear->getPreferredState()); - echo $this->view()->template('pear/dist.phtml'); - } - - public function distUpgradePostAction() - { - if (!$_POST) { - echo "INVALID POST DATA"; - return; - } - $result = $this->model('pear', true)->distUpgrade($_POST['version']); - } - - public function settingsAction() - { - $pearConfig = $this->model('pear', true)->pear()->getConfig(); - $this->view()->set('state', $pearConfig->get('preferred_state')); - $this->view()->set('mage_dir', $pearConfig->get('mage_dir')); - echo $this->view()->template('settings.phtml'); - } - - public function settingsPostAction() - { - if ($_POST) { - $this->config()->saveConfigPost($_POST); - $this->model('pear', true)->saveConfigPost($_POST); - } - $this->redirect($this->url('settings')); - } - - //////////////////////////// ABSTRACT - - public static function run() - { - try { - self::singleton()->dispatch(); - } catch (Exception $e) { - echo self::singleton()->view()->set('exception', $e)->template("exception.phtml"); - } - } - - public static function singleton() - { - if (!self::$_instance) { - self::$_instance = new self; - } - return self::$_instance; - } - - public function __construct() - { - $this->_rootDir = dirname(dirname(__FILE__)); - $this->_mageDir = dirname($this->_rootDir); - } - - public function getRootDir() - { - return $this->_rootDir; - } - - public function getMageDir() - { - return $this->_mageDir; - } - - public function getMageFilename() - { - $ds = DIRECTORY_SEPARATOR; - return $this->getMageDir().$ds.'app'.$ds.'Mage.php'; - } - - public function getVarFilename() - { - $ds = DIRECTORY_SEPARATOR; - return $this->getMageDir().$ds.'lib'.$ds.'Varien'.$ds.'Profiler.php'; - } - - public function filepath($name='') - { - $ds = DIRECTORY_SEPARATOR; - return rtrim($this->getRootDir().$ds.str_replace('/', $ds, $name), $ds); - } - - public function view() - { - if (!$this->_view) { - $this->_view = new Maged_View; - } - return $this->_view; - } - - public function model($model=null, $singleton=false) - { - if ($singleton && isset($this->_singletons[$model])) { - return $this->_singletons[$model]; - } - - if (is_null($model)) { - $class = 'Maged_Model'; - } else { - $class = 'Maged_Model_'.str_replace(' ', '_', ucwords(str_replace('_', ' ', $model))); - if (!class_exists($class, false)) { - include_once str_replace('_', DIRECTORY_SEPARATOR, $class).'.php'; - } - } - - $object = new $class(); - - if ($singleton) { - $this->_singletons[$model] = $object; - } - - return $object; - } - - public function config() - { - if (!$this->_config) { - $this->_config = $this->model('config')->load(); - } - return $this->_config; - } - - public function session() - { - if (!$this->_session) { - $this->_session = $this->model('session')->start(); - } - return $this->_session; - } - - public function setAction($action=null) - { - if (is_null($action)) { - if (!empty($this->_action)) { - return $this; - } - $action = !empty($_GET[self::ACTION_KEY]) ? $_GET[self::ACTION_KEY] : 'index'; - } - if (empty($action) || !is_string($action) - || !method_exists($this, $this->getActionMethod($action))) { - $action = 'noroute'; - } - $this->_action = $action; - return $this; - } - - public function getAction() - { - return $this->_action; - } - - public function redirect($url, $force=false) - { - $this->_redirectUrl = $url; - if ($force) { - $this->processRedirect(); - } - return $this; - } - - public function processRedirect() - { - if ($this->_redirectUrl) { - if (headers_sent()) { - echo '<script type="text/javascript">location.href="'.$this->_redirectUrl.'"</script>'; - exit; - } else { - header("Location: ".$this->_redirectUrl); - exit; - } - } - return $this; - } - - public function forward($action) - { - $this->setAction($action); - $this->_isDispatched = false; - return $this; - } - - public function getActionMethod($action = null) - { - $method = (!is_null($action) ? $action : $this->_action).'Action'; - return $method; - } - - public function url($action='', $params=array()) - { - $paramsStr = ''; - foreach ($params as $k=>$v) { - $paramStr .= '&'.$k.'='.urlencode($v); - } - return $_SERVER['SCRIPT_NAME'].'?'.self::ACTION_KEY.'='.$action.$paramsStr; - } - - public function dispatch() - { - header('Content-type: text/html; charset=UTF-8'); - - $this->setAction(); - - if (!$this->isWritable() || !$this->isInstalled()) { - if (!in_array($this->getAction(), array('index', 'pearInstallAll', 'empty'))) { - $this->setAction('index'); - } - } else { - $this->session()->authenticate(); - } - - while (!$this->_isDispatched) { - $this->_isDispatched = true; - - $method = $this->getActionMethod(); - $this->$method(); - } - - $this->processRedirect(); - } - - public function isWritable() - { - if (is_null($this->_writable)) { - $this->_writable = is_writable($this->getMageDir()) - && is_writable($this->filepath()) - && (!file_exists($this->filepath('config.ini') || is_writable($this->filepath('config.ini')))) - && (!file_exists($this->filepath('pearlib/config.ini') || is_writable($this->filepath('pearlib/pear.ini')))) - && is_writable($this->filepath('pearlib/php')); - - } - return $this->_writable; - } - - public function isDownloaded() - { - return file_exists($this->getMageFilename()) - && file_exists($this->getVarFilename()); - } - - public function isInstalled() - { - if (!$this->isDownloaded()) { - return false; - } - if (!class_exists('Mage', false)) { - include_once $this->getMageFilename(); - Mage::setIsDownloader(); - } - return Mage::isInstalled(); - } - - public function startInstall() - { - - } - - public function endInstall() - { - try { - if (!empty($_GET['clean_sessions'])) { - Mage::app()->cleanAllSessions(); - } - Mage::app()->cleanCache(); - } catch (Exception $e) { - $this->session()->addMessage('error', "Exception during cache and session cleaning: ".$e->getMessage()); - } - } -} +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Varien + * @package Varien_Object + * @copyright Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +include_once "Maged/Model.php"; +include_once "Maged/View.php"; +include_once "Maged/Exception.php"; + +final class Maged_Controller +{ + const ACTION_KEY = 'A'; + + private static $_instance; + + private $_action; + private $_isDispatched = false; + private $_redirectUrl; + private $_rootDir; + + private $_view; + private $_config; + private $_session; + + private $_writable; + + private $_useCache; + + //////////////////////////// ACTIONS + + public function norouteAction() + { + header("HTTP/1.0 404 Invalid Action"); + echo $this->view()->template('noroute.phtml'); + } + + public function loginAction() + { + $this->view()->set('username', !empty($_GET['username']) ? $_GET['username'] : ''); + echo $this->view()->template('login.phtml'); + } + + public function logoutAction() + { + $this->session()->logout(); + $this->redirect($this->url()); + } + + public function indexAction() + { + if (!$this->isInstalled()) { + if (!$this->isWritable()) { + echo $this->view()->template('install/writable.phtml'); + } else { + $this->view()->set('mage_url', dirname(dirname($_SERVER['SCRIPT_NAME']))); + echo $this->view()->template('install/download.phtml'); + } + } else { + if (!$this->isWritable()) { + echo $this->view()->template('writable.phtml'); + } else { + $this->forward('pearPackages'); + } + } + } + + public function emptyAction() + { + $this->model('pear', true)->pear()->runHtmlConsole('Please wait, preparing for updates...'); + } + + public function pearGlobalAction() + { + echo $this->view()->template('pear/global.phtml'); + } + + public function pearInstallAllAction() + { + $this->model('pear', true)->installAll(!empty($_GET['force'])); + } + + public function pearUpgradeAllAction() + { + $this->model('pear', true)->upgradeAll(); + } + + public function pearPackagesAction() + { + $pear = $this->model('pear', true); + $this->view()->set('pear', $pear); + $this->view()->set('channels', $pear->pear()->getMagentoChannels()); + echo $this->view()->template('pear/packages.phtml'); + } + + public function pearPackagesPostAction() + { + $actions = isset($_POST['actions']) ? $_POST['actions'] : array(); + $this->model('pear', true)->applyPackagesActions($actions); + } + + public function pearInstallPackagePostAction() + { + if (!$_POST) { + echo "INVALID POST DATA"; + return; + } + $this->model('pear', true)->installPackage($_POST['install_package_id']); + } + + public function distUpgradeAction() + { + $pear = $this->model('pear', true); + $this->view()->set('pear', $pear); + $this->view()->set('state', $pear->getPreferredState()); + echo $this->view()->template('pear/dist.phtml'); + } + + public function distUpgradePostAction() + { + if (!$_POST) { + echo "INVALID POST DATA"; + return; + } + $result = $this->model('pear', true)->distUpgrade($_POST['version']); + } + + public function settingsAction() + { + $pearConfig = $this->model('pear', true)->pear()->getConfig(); + $this->view()->set('state', $pearConfig->get('preferred_state')); + $this->view()->set('mage_dir', $pearConfig->get('mage_dir')); + echo $this->view()->template('settings.phtml'); + } + + public function settingsPostAction() + { + if ($_POST) { + $this->config()->saveConfigPost($_POST); + $this->model('pear', true)->saveConfigPost($_POST); + } + $this->redirect($this->url('settings')); + } + + //////////////////////////// ABSTRACT + + public static function run() + { + try { + self::singleton()->dispatch(); + } catch (Exception $e) { + echo self::singleton()->view()->set('exception', $e)->template("exception.phtml"); + } + } + + public static function singleton() + { + if (!self::$_instance) { + self::$_instance = new self; + + if (self::$_instance->isDownloaded() && self::$_instance->isInstalled()) { + Mage::app(); + Mage::getSingleton('adminhtml/url')->turnOffSecretKey(); + } + } + return self::$_instance; + } + + public function __construct() + { + $this->_rootDir = dirname(dirname(__FILE__)); + $this->_mageDir = dirname($this->_rootDir); + } + + public function getRootDir() + { + return $this->_rootDir; + } + + public function getMageDir() + { + return $this->_mageDir; + } + + public function getMageFilename() + { + $ds = DIRECTORY_SEPARATOR; + return $this->getMageDir().$ds.'app'.$ds.'Mage.php'; + } + + public function getVarFilename() + { + $ds = DIRECTORY_SEPARATOR; + return $this->getMageDir().$ds.'lib'.$ds.'Varien'.$ds.'Profiler.php'; + } + + public function filepath($name='') + { + $ds = DIRECTORY_SEPARATOR; + return rtrim($this->getRootDir().$ds.str_replace('/', $ds, $name), $ds); + } + + public function view() + { + if (!$this->_view) { + $this->_view = new Maged_View; + } + return $this->_view; + } + + public function model($model=null, $singleton=false) + { + if ($singleton && isset($this->_singletons[$model])) { + return $this->_singletons[$model]; + } + + if (is_null($model)) { + $class = 'Maged_Model'; + } else { + $class = 'Maged_Model_'.str_replace(' ', '_', ucwords(str_replace('_', ' ', $model))); + if (!class_exists($class, false)) { + include_once str_replace('_', DIRECTORY_SEPARATOR, $class).'.php'; + } + } + + $object = new $class(); + + if ($singleton) { + $this->_singletons[$model] = $object; + } + + return $object; + } + + public function config() + { + if (!$this->_config) { + $this->_config = $this->model('config')->load(); + } + return $this->_config; + } + + public function session() + { + if (!$this->_session) { + $this->_session = $this->model('session')->start(); + } + return $this->_session; + } + + public function setAction($action=null) + { + if (is_null($action)) { + if (!empty($this->_action)) { + return $this; + } + $action = !empty($_GET[self::ACTION_KEY]) ? $_GET[self::ACTION_KEY] : 'index'; + } + if (empty($action) || !is_string($action) + || !method_exists($this, $this->getActionMethod($action))) { + $action = 'noroute'; + } + $this->_action = $action; + return $this; + } + + public function getAction() + { + return $this->_action; + } + + public function redirect($url, $force=false) + { + $this->_redirectUrl = $url; + if ($force) { + $this->processRedirect(); + } + return $this; + } + + public function processRedirect() + { + if ($this->_redirectUrl) { + if (headers_sent()) { + echo '<script type="text/javascript">location.href="'.$this->_redirectUrl.'"</script>'; + exit; + } else { + header("Location: ".$this->_redirectUrl); + exit; + } + } + return $this; + } + + public function forward($action) + { + $this->setAction($action); + $this->_isDispatched = false; + return $this; + } + + public function getActionMethod($action = null) + { + $method = (!is_null($action) ? $action : $this->_action).'Action'; + return $method; + } + + public function url($action='', $params=array()) + { + $paramsStr = ''; + foreach ($params as $k=>$v) { + $paramStr .= '&'.$k.'='.urlencode($v); + } + return $_SERVER['SCRIPT_NAME'].'?'.self::ACTION_KEY.'='.$action.$paramsStr; + } + + public function dispatch() + { + header('Content-type: text/html; charset=UTF-8'); + + $this->setAction(); + + if (!$this->isWritable() || !$this->isInstalled()) { + if (!in_array($this->getAction(), array('index', 'pearInstallAll', 'empty'))) { + $this->setAction('index'); + } + } else { + $this->session()->authenticate(); + } + + while (!$this->_isDispatched) { + $this->_isDispatched = true; + + $method = $this->getActionMethod(); + $this->$method(); + } + + $this->processRedirect(); + } + + public function isWritable() + { + if (is_null($this->_writable)) { + $this->_writable = is_writable($this->getMageDir()) + && is_writable($this->filepath()) + && (!file_exists($this->filepath('config.ini') || is_writable($this->filepath('config.ini')))) + && (!file_exists($this->filepath('pearlib/config.ini') || is_writable($this->filepath('pearlib/pear.ini')))) + && is_writable($this->filepath('pearlib/php')); + + } + return $this->_writable; + } + + public function isDownloaded() + { + return file_exists($this->getMageFilename()) + && file_exists($this->getVarFilename()); + } + + public function isInstalled() + { + if (!$this->isDownloaded()) { + return false; + } + if (!class_exists('Mage', false)) { + include_once $this->getMageFilename(); + Mage::setIsDownloader(); + } + return Mage::isInstalled(); + } + + public function startInstall() + { + + } + + public function endInstall() + { + try { + if (!empty($_GET['clean_sessions'])) { + Mage::app()->cleanAllSessions(); + } + Mage::app()->cleanCache(); + } catch (Exception $e) { + $this->session()->addMessage('error', "Exception during cache and session cleaning: ".$e->getMessage()); + } + } +} diff --git a/downloader/template/install/footer.phtml b/downloader/template/install/footer.phtml index c2133f35f0..f3753504d7 100644 --- a/downloader/template/install/footer.phtml +++ b/downloader/template/install/footer.phtml @@ -36,7 +36,7 @@ <div class="footer-container"> <div class="footer"> <p class="legality"> - Help Us to Keep Magento Healthy - <a href="http://www.magentocommerce.com/bug-tracking" id="bug_tracking_link"><strong>Report All Bugs</strong></a> (Downloader ver. 1.2.1)<br/> + Help Us to Keep Magento Healthy - <a href="http://www.magentocommerce.com/bug-tracking" id="bug_tracking_link"><strong>Report All Bugs</strong></a> (Downloader ver. 1.3.1)<br/> <script type="text/javascript"> $('bug_tracking_link').target = "varien_external"; diff --git a/js/calendar/calendar.js b/js/calendar/calendar.js index c49fc2c79d..a784b0a1aa 100644 --- a/js/calendar/calendar.js +++ b/js/calendar/calendar.js @@ -95,23 +95,90 @@ Calendar.is_opera = /opera/i.test(navigator.userAgent); /// detect KHTML-based browsers Calendar.is_khtml = /Konqueror|Safari|KHTML/i.test(navigator.userAgent); +/// detect Gecko browsers +Calendar.is_gecko = navigator.userAgent.match(/gecko/i); + // BEGIN: UTILITY FUNCTIONS; beware that these might be moved into a separate // library, at some point. -Calendar.getAbsolutePos = function(el) { - var SL = 0, ST = 0; - var is_div = /^div$/i.test(el.tagName); - if (is_div && el.scrollLeft) - SL = el.scrollLeft; - if (is_div && el.scrollTop) - ST = el.scrollTop; - var r = { x: el.offsetLeft - SL, y: el.offsetTop - ST }; - if (el.offsetParent) { - var tmp = this.getAbsolutePos(el.offsetParent); - r.x += tmp.x; - r.y += tmp.y; - } - return r; +// Returns CSS property for element +Calendar.getStyle = function(element, style) { + if (element.currentStyle) { + var y = element.currentStyle[style]; + } else if (window.getComputedStyle) { + var y = document.defaultView.getComputedStyle(element,null).getPropertyValue(style); + } + + return y; +}; + +/* + * Different ways to find element's absolute position + */ +Calendar.getAbsolutePos = function(element) { + + var res = new Object(); + res.x = 0; res.y = 0; + + // variant 1 (working best, copy-paste from prototype library) + do { + res.x += element.offsetLeft || 0; + res.y += element.offsetTop || 0; + element = element.offsetParent; + if (element) { + if (element.tagName.toUpperCase() == 'BODY') break; + var p = Calendar.getStyle(element, 'position'); + if (p !== 'static') break; + } + } while (element); + + return res; + + // variant 2 (good solution, but lost in IE8) + if (element !== null) { + res.x = element.offsetLeft; + res.y = element.offsetTop; + + var offsetParent = element.offsetParent; + var parentNode = element.parentNode; + + while (offsetParent !== null) { + res.x += offsetParent.offsetLeft; + res.y += offsetParent.offsetTop; + + if (offsetParent != document.body && offsetParent != document.documentElement) { + res.x -= offsetParent.scrollLeft; + res.y -= offsetParent.scrollTop; + } + //next lines are necessary to support FireFox problem with offsetParent + if (Calendar.is_gecko) { + while (offsetParent != parentNode && parentNode !== null) { + res.x -= parentNode.scrollLeft; + res.y -= parentNode.scrollTop; + parentNode = parentNode.parentNode; + } + } + parentNode = offsetParent.parentNode; + offsetParent = offsetParent.offsetParent; + } + } + return res; + + // variant 2 (not working) + +// var SL = 0, ST = 0; +// var is_div = /^div$/i.test(el.tagName); +// if (is_div && el.scrollLeft) +// SL = el.scrollLeft; +// if (is_div && el.scrollTop) +// ST = el.scrollTop; +// var r = { x: el.offsetLeft - SL, y: el.offsetTop - ST }; +// if (el.offsetParent) { +// var tmp = this.getAbsolutePos(el.offsetParent); +// r.x += tmp.x; +// r.y += tmp.y; +// } +// return r; }; Calendar.isRelated = function (el, evt) { diff --git a/js/mage/adminhtml/product.js b/js/mage/adminhtml/product.js index e5ba3b2503..e4694e2dbc 100644 --- a/js/mage/adminhtml/product.js +++ b/js/mage/adminhtml/product.js @@ -311,6 +311,7 @@ Product.Configurable.prototype = { this.onLabelUpdate = this.updateLabel.bindAsEventListener(this); // Update attribute label this.onValuePriceUpdate = this.updateValuePrice.bindAsEventListener(this); // Update pricing value this.onValueTypeUpdate = this.updateValueType.bindAsEventListener(this); // Update pricing type + this.onValueDefaultUpdate = this.updateValueUseDefault.bindAsEventListener(this); /* Grid initialization and attributes initialization */ this.createAttributes(); // Creation of default attributes @@ -579,6 +580,14 @@ Product.Configurable.prototype = { Event.observe(priceField, 'keyup', this.onValuePriceUpdate); Event.observe(priceField, 'change', this.onValuePriceUpdate); Event.observe(priceTypeField, 'change', this.onValueTypeUpdate); + var useDefaultEl = li.down('.attribute-use-default-value'); + if (useDefaultEl) { + if (li.valueObject.use_default_value) { + useDefaultEl.checked = true; + this.updateUseDefaultRow(useDefaultEl, li); + } + Event.observe(useDefaultEl, 'change', this.onValueDefaultUpdate); + } }, updateValuePrice: function(event) { var li = Event.findElement(event, 'LI'); @@ -592,6 +601,27 @@ Product.Configurable.prototype = { this.updateSimpleForm(); this.updateSaveInput(); }, + updateValueUseDefault: function(event) { + var li = Event.findElement(event, 'LI'); + var useDefaultEl = Event.element(event); + li.valueObject.use_default_value = useDefaultEl.checked; + this.updateUseDefaultRow(useDefaultEl, li); + }, + updateUseDefaultRow: function(useDefaultEl, li) + { + var priceField = li.down('.attribute-price'); + var priceTypeField = li.down('.attribute-price-type'); + if (useDefaultEl.checked) { + priceField.disabled = true; + priceTypeField.disabled = true; + } + else { + priceField.disabled = false; + priceTypeField.disabled = false; + } + this.updateSimpleForm(); + this.updateSaveInput(); + }, updateSaveInput: function() { $(this.idPrefix + 'save_attributes').value = this.attributes.toJSON(); $(this.idPrefix + 'save_links').value = this.links.toJSON(); diff --git a/js/mage/adminhtml/sales.js b/js/mage/adminhtml/sales.js index b17e7d73b3..9bfe030781 100644 --- a/js/mage/adminhtml/sales.js +++ b/js/mage/adminhtml/sales.js @@ -696,7 +696,13 @@ AdminOrder.prototype = { if (Prototype.Browser.IE) { parentEl.select('select').each(function (elem) { - show ? elem .show() : elem.hide(); + if (show) { + elem.needShowOnSuccess = false; + elem.style.visibility = ''; + } else { + elem.style.visibility = 'hidden'; + elem.needShowOnSuccess = true; + } }); } diff --git a/js/prototype/validation.js b/js/prototype/validation.js index f8da82d617..6a83155277 100644 --- a/js/prototype/validation.js +++ b/js/prototype/validation.js @@ -68,6 +68,17 @@ Validator.methods = { } var Validation = Class.create(); +Validation.defaultOptions = { + onSubmit : true, + stopOnFirst : false, + immediate : false, + focusOnError : true, + useTitles : false, + addClassNameToContainer: false, + containerClassName: '.input-box', + onFormValidate : function(result, form) {}, + onElementValidate : function(result, elm) {} +}; Validation.prototype = { initialize : function(form, options){ @@ -76,23 +87,32 @@ Validation.prototype = { return; } this.options = Object.extend({ - onSubmit : true, - stopOnFirst : false, - immediate : false, - focusOnError : true, - useTitles : false, - onFormValidate : function(result, form) {}, - onElementValidate : function(result, elm) {} + onSubmit : Validation.defaultOptions.onSubmit, + stopOnFirst : Validation.defaultOptions.stopOnFirst, + immediate : Validation.defaultOptions.immediate, + focusOnError : Validation.defaultOptions.focusOnError, + useTitles : Validation.defaultOptions.useTitles, + onFormValidate : Validation.defaultOptions.onFormValidate, + onElementValidate : Validation.defaultOptions.onElementValidate }, options || {}); if(this.options.onSubmit) Event.observe(this.form,'submit',this.onSubmit.bind(this),false); if(this.options.immediate) { - var useTitles = this.options.useTitles; - var callback = this.options.onElementValidate; - Form.getElements(this.form).each(function(input) { // Thanks Mike! - Event.observe(input, 'blur', function(ev) { Validation.validate(Event.element(ev),{useTitle : useTitles, onElementValidate : callback}); }); - }); + Form.getElements(this.form).each(function(input) { // Thanks Mike! + if (input.tagName.toLowerCase() == 'select') { + Event.observe(input, 'blur', this.onChange.bindAsEventListener(this)); + } + Event.observe(input, 'change', this.onChange.bindAsEventListener(this)); + }, this); } }, + onChange : function (ev) { + Validation.isOnChange = true; + Validation.validate(Event.element(ev),{ + useTitle : this.options.useTitles, + onElementValidate : this.options.onElementValidate + }); + Validation.isOnChange = false; + }, onSubmit : function(ev){ if(!this.validate()) Event.stop(ev); }, @@ -144,8 +164,9 @@ Object.extend(Validation, { var container = $(elm).up('.field-row'); if(container){ Element.insert(container, {after: advice}); - } - else if (elm.advaiceContainer && $(elm.advaiceContainer)) { + } else if (elm.up('td.value')) { + elm.up('td.value').insert({bottom: advice}); + } else if (elm.advaiceContainer && $(elm.advaiceContainer)) { $(elm.advaiceContainer).update(advice); } else { @@ -211,6 +232,13 @@ Object.extend(Validation, { elm.addClassName('validation-failed'); elm.addClassName('validate-ajax'); + if (Validation.defaultOptions.addClassNameToContainer && Validation.defaultOptions.containerClassName != '') { + var container = elm.up(Validation.defaultOptions.containerClassName); + if (container && elm.type !== 'radio' && elm.type !== 'checkbox') { + container.removeClassName('validation-passed'); + container.addClassName('validation-error'); + } + } }, test : function(name, elm, useTitle) { var v = Validation.get(name); @@ -229,6 +257,13 @@ Object.extend(Validation, { if (!elm.advaiceContainer) { elm.removeClassName('validation-passed'); elm.addClassName('validation-failed'); + if (Validation.defaultOptions.addClassNameToContainer && Validation.defaultOptions.containerClassName != '') { + var container = elm.up(Validation.defaultOptions.containerClassName); + if (container && elm.type !== 'radio' && elm.type !== 'checkbox') { + container.removeClassName('validation-passed'); + container.addClassName('validation-error'); + } + } } return false; } else { @@ -238,6 +273,17 @@ Object.extend(Validation, { elm[prop] = ''; elm.removeClassName('validation-failed'); elm.addClassName('validation-passed'); + if (Validation.defaultOptions.addClassNameToContainer && Validation.defaultOptions.containerClassName != '') { + var container = elm.up(Validation.defaultOptions.containerClassName); + if (container && elm.type !== 'radio' && elm.type !== 'checkbox') { + if (!Validation.get('IsEmpty').test(elm.value)) { + container.addClassName('validation-passed'); + } else { + container.removeClassName('validation-passed'); + } + container.removeClassName('validation-error'); + } + } return true; } } catch(e) { @@ -298,6 +344,13 @@ Object.extend(Validation, { } elm.removeClassName('validation-failed'); elm.removeClassName('validation-passed'); + if (Validation.defaultOptions.addClassNameToContainer && Validation.defaultOptions.containerClassName != '') { + var container = elm.up(Validation.defaultOptions.containerClassName); + if (container) { + container.removeClassName('validation-passed'); + container.removeClassName('validation-error'); + } + } }); }, add : function(className, error, test, options) { @@ -371,8 +424,23 @@ Validation.addAllThese([ var pass=v.strip(); /*strip leading and trailing spaces*/ return !(pass.length>0 && pass.length < 6); }], + ['validate-admin-password', 'Please enter 7 or more characters. Password should contain both numeric and alphabetic characters.', function(v) { + var pass=v.strip(); + if (0 == pass.length) { + return true; + } + if (!(/[a-z]/i.test(v)) || !(/[0-9]/.test(v))) { + return false; + } + return !(pass.length < 7); + }], ['validate-cpassword', 'Please make sure your passwords match.', function(v) { - var pass = $('password') ? $('password') : $$('.validate-password')[0]; + if ($('password')) { + var pass = $('password'); + } + else { + var pass = $$('.validate-password').length ? $$('.validate-password')[0] : $$('.validate-admin-password')[0]; + } var conf = $('confirmation') ? $('confirmation') : $$('.validate-cpassword')[0]; return (pass.value == conf.value); }], @@ -507,11 +575,21 @@ Validation.addAllThese([ if(ccMatchedType != ccType) { return false; } + + if (ccTypeContainer.hasClassName('validation-failed') && Validation.isOnChange) { + Validation.validate(ccTypeContainer); + } return true; }], ['validate-cc-type-select', 'Card type doesn\'t match credit card number', function(v, elm) { var ccNumberContainer = $(elm.id.substr(0,elm.id.indexOf('_cc_type')) + '_cc_number'); + if (Validation.isOnChange && Validation.get('IsEmpty').test(ccNumberContainer.value)) { + return true; + } + if (Validation.get('validate-cc-type').test(ccNumberContainer.value, ccNumberContainer)) { + Validation.validate(ccNumberContainer); + } return Validation.get('validate-cc-type').test(ccNumberContainer.value, ccNumberContainer); }], ['validate-cc-exp', 'Incorrect credit card expiration date', function(v, elm) { diff --git a/lib/PEAR/HTTP/HTTP.php b/lib/PEAR/HTTP/HTTP.php new file mode 100644 index 0000000000..6ebd6dbc9a --- /dev/null +++ b/lib/PEAR/HTTP/HTTP.php @@ -0,0 +1,548 @@ +<?php + +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * HTTP + * + * PHP versions 4 and 5 + * + * @category HTTP + * @package HTTP + * @author Stig Bakken <ssb@fast.no> + * @author Sterling Hughes <sterling@php.net> + * @author Tomas V.V.Cox <cox@idecnet.com> + * @author Richard Heyes <richard@php.net> + * @author Philippe Jausions <jausions@php.net> + * @author Michael Wallner <mike@php.net> + * @copyright 2002-2008 The Authors + * @license http://www.opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: HTTP.php,v 1.56 2008/08/31 20:15:43 jausions Exp $ + * @link http://pear.php.net/package/HTTP + */ + +/** + * Miscellaneous HTTP Utilities + * + * PEAR::HTTP provides static shorthand methods for generating HTTP dates, + * issueing HTTP HEAD requests, building absolute URIs, firing redirects and + * negotiating user preferred language. + * + * @category HTTP + * @package HTTP + * @author Stig Bakken <ssb@fast.no> + * @author Sterling Hughes <sterling@php.net> + * @author Tomas V.V.Cox <cox@idecnet.com> + * @author Richard Heyes <richard@php.net> + * @author Philippe Jausions <jausions@php.net> + * @author Michael Wallner <mike@php.net> + * @license http://www.opensource.org/licenses/bsd-license.php New BSD License + * @abstract + * @version Release: $Revision: 1.56 $ + * @link http://pear.php.net/package/HTTP + */ +class HTTP +{ + /** + * Formats a RFC compliant GMT date HTTP header. This function honors the + * "y2k_compliance" php.ini directive and formats the GMT date corresponding + * to either RFC850 or RFC822. + * + * @param mixed $time unix timestamp or date (default = current time) + * + * @return mixed GMT date string, or false for an invalid $time parameter + * @access public + * @static + */ + function Date($time = null) + { + if (!isset($time)) { + $time = time(); + } elseif (!is_numeric($time) && (-1 === $time = strtotime($time))) { + return false; + } + + // RFC822 or RFC850 + $format = ini_get('y2k_compliance') ? 'D, d M Y' : 'l, d-M-y'; + + return gmdate($format .' H:i:s \G\M\T', $time); + } + + /** + * Negotiates language with the user's browser through the Accept-Language + * HTTP header or the user's host address. Language codes are generally in + * the form "ll" for a language spoken in only one country, or "ll-CC" for a + * language spoken in a particular country. For example, U.S. English is + * "en-US", while British English is "en-UK". Portugese as spoken in + * Portugal is "pt-PT", while Brazilian Portugese is "pt-BR". + * + * Quality factors in the Accept-Language: header are supported, e.g.: + * Accept-Language: en-UK;q=0.7, en-US;q=0.6, no, dk;q=0.8 + * + * <code> + * require_once 'HTTP.php'; + * $langs = array( + * 'en' => 'locales/en', + * 'en-US' => 'locales/en', + * 'en-UK' => 'locales/en', + * 'de' => 'locales/de', + * 'de-DE' => 'locales/de', + * 'de-AT' => 'locales/de', + * ); + * $neg = HTTP::negotiateLanguage($langs); + * $dir = $langs[$neg]; + * </code> + * + * @param array $supported An associative array of supported languages, + * whose values must evaluate to true. + * @param string $default The default language to use if none is found. + * + * @return string The negotiated language result or the supplied default. + * @static + * @access public + */ + function negotiateLanguage($supported, $default = 'en-US') + { + $supp = array(); + foreach ($supported as $lang => $isSupported) { + if ($isSupported) { + $supp[strtolower($lang)] = $lang; + } + } + + if (!count($supp)) { + return $default; + } + + if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { + $match = HTTP::_matchAccept($_SERVER['HTTP_ACCEPT_LANGUAGE'], + $supp); + if (!is_null($match)) { + return $match; + } + } + + if (isset($_SERVER['REMOTE_HOST'])) { + $lang = strtolower(end($h = explode('.', $_SERVER['REMOTE_HOST']))); + if (isset($supp[$lang])) { + return $supp[$lang]; + } + } + + return $default; + } + + /** + * Negotiates charset with the user's browser through the Accept-Charset + * HTTP header. + * + * Quality factors in the Accept-Charset: header are supported, e.g.: + * Accept-Language: en-UK;q=0.7, en-US;q=0.6, no, dk;q=0.8 + * + * <code> + * require_once 'HTTP.php'; + * $charsets = array( + * 'UTF-8', + * 'ISO-8859-1', + * ); + * $charset = HTTP::negotiateCharset($charsets); + * </code> + * + * @param array $supported An array of supported charsets + * @param string $default The default charset to use if none is found. + * + * @return string The negotiated language result or the supplied default. + * @static + * @author Philippe Jausions <jausions@php.net> + * @access public + * @since 1.4.1 + */ + function negotiateCharset($supported, $default = 'ISO-8859-1') + { + $supp = array(); + foreach ($supported as $charset) { + $supp[strtolower($charset)] = $charset; + } + + if (!count($supp)) { + return $default; + } + + if (isset($_SERVER['HTTP_ACCEPT_CHARSET'])) { + $match = HTTP::_matchAccept($_SERVER['HTTP_ACCEPT_CHARSET'], + $supp); + if (!is_null($match)) { + return $match; + } + } + + return $default; + } + + /** + * Negotiates content type with the user's browser through the Accept + * HTTP header. + * + * Quality factors in the Accept: header are supported, e.g.: + * Accept: application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8 + * + * <code> + * require_once 'HTTP.php'; + * $contentType = array( + * 'application/xhtml+xml', + * 'application/xml', + * 'text/html', + * 'text/plain', + * ); + * $mime = HTTP::negotiateContentType($contentType); + * </code> + * + * @param array $supported An associative array of supported MIME types. + * @param string $default The default type to use if none match. + * + * @return string The negotiated MIME type result or the supplied default. + * @static + * @author Philippe Jausions <jausions@php.net> + * @access public + * @since 1.4.1 + */ + function negotiateMimeType($supported, $default) + { + $supp = array(); + foreach ($supported as $type) { + $supp[strtolower($type)] = $type; + } + + if (!count($supp)) { + return $default; + } + + if (isset($_SERVER['HTTP_ACCEPT'])) { + $accepts = HTTP::_sortAccept($_SERVER['HTTP_ACCEPT']); + + foreach ($accepts as $type => $q) { + if (substr($type, -2) != '/*') { + if (isset($supp[$type])) { + return $supp[$type]; + } + continue; + } + if ($type == '*/*') { + return array_shift($supp); + } + list($general, $specific) = explode('/', $type); + $general .= '/'; + $len = strlen($general); + foreach ($supp as $mime => $t) { + if (strncasecmp($general, $mime, $len) == 0) { + return $t; + } + } + } + } + + return $default; + } + + /** + * Parses a weighed "Accept" HTTP header and matches it against a list + * of supported options + * + * @param string $header The HTTP "Accept" header to parse + * @param array $supported A list of supported values + * + * @return string|NULL a matched option, or NULL if no match + * @access private + * @static + */ + function _matchAccept($header, $supported) + { + $matches = HTTP::_sortAccept($header); + foreach ($matches as $key => $q) { + if (isset($supported[$key])) { + return $supported[$key]; + } + } + // If any (i.e. "*") is acceptable, return the first supported format + if (isset($matches['*'])) { + return array_shift($supported); + } + return null; + } + + /** + * Parses and sorts a weighed "Accept" HTTP header + * + * @param string $header The HTTP "Accept" header to parse + * + * @return array a sorted list of "accept" options + * @access private + * @static + */ + function _sortAccept($header) + { + $matches = array(); + foreach (explode(',', $header) as $option) { + $option = array_map('trim', explode(';', $option)); + + $l = strtolower($option[0]); + if (isset($option[1])) { + $q = (float) str_replace('q=', '', $option[1]); + } else { + $q = null; + // Assign default low weight for generic values + if ($l == '*/*') { + $q = 0.01; + } elseif (substr($l, -1) == '*') { + $q = 0.02; + } + } + // Unweighted values, get high weight by their position in the + // list + $matches[$l] = isset($q) ? $q : 1000 - count($matches); + } + arsort($matches, SORT_NUMERIC); + return $matches; + } + + /** + * Sends a "HEAD" HTTP command to a server and returns the headers + * as an associative array. + * + * Example output could be: + * <code> + * Array + * ( + * [response_code] => 200 // The HTTP response code + * [response] => HTTP/1.1 200 OK // The full HTTP response string + * [Date] => Fri, 11 Jan 2002 01:41:44 GMT + * [Server] => Apache/1.3.20 (Unix) PHP/4.1.1 + * [X-Powered-By] => PHP/4.1.1 + * [Connection] => close + * [Content-Type] => text/html + * ) + * </code> + * + * @param string $url A valid URL, e.g.: http://pear.php.net/credits.php + * @param integer $timeout Timeout in seconds (default = 10) + * + * @return array Returns associative array of response headers on success + * or PEAR error on failure. + * @static + * @access public + * @see HTTP_Client::head() + * @see HTTP_Request + */ + function head($url, $timeout = 10) + { + $p = parse_url($url); + if (!isset($p['scheme'])) { + $p = parse_url(HTTP::absoluteURI($url)); + } elseif ($p['scheme'] != 'http') { + return HTTP::raiseError('Unsupported protocol: '. $p['scheme']); + } + + $port = isset($p['port']) ? $p['port'] : 80; + + if (!$fp = @fsockopen($p['host'], $port, $eno, $estr, $timeout)) { + return HTTP::raiseError("Connection error: $estr ($eno)"); + } + + $path = !empty($p['path']) ? $p['path'] : '/'; + $path .= !empty($p['query']) ? '?' . $p['query'] : ''; + + fputs($fp, "HEAD $path HTTP/1.0\r\n"); + fputs($fp, 'Host: ' . $p['host'] . ':' . $port . "\r\n"); + fputs($fp, "Connection: close\r\n\r\n"); + + $response = rtrim(fgets($fp, 4096)); + if (preg_match("|^HTTP/[^\s]*\s(.*?)\s|", $response, $status)) { + $headers['response_code'] = $status[1]; + } + $headers['response'] = $response; + + while ($line = fgets($fp, 4096)) { + if (!trim($line)) { + break; + } + if (($pos = strpos($line, ':')) !== false) { + $header = substr($line, 0, $pos); + $value = trim(substr($line, $pos + 1)); + + $headers[$header] = $value; + } + } + fclose($fp); + return $headers; + } + + /** + * This function redirects the client. This is done by issuing + * a "Location" header and exiting if wanted. If you set $rfc2616 to true + * HTTP will output a hypertext note with the location of the redirect. + * + * @param string $url URL where the redirect should go to. + * @param bool $exit Whether to exit immediately after redirection. + * @param bool $rfc2616 Wheter to output a hypertext note where we're + * redirecting to (Redirecting to + * <a href="...">...</a>.) + * + * @return boolean Returns TRUE on succes (or exits) or FALSE if headers + * have already been sent. + * @static + * @access public + */ + function redirect($url, $exit = true, $rfc2616 = false) + { + if (headers_sent()) { + return false; + } + + $url = HTTP::absoluteURI($url); + header('Location: '. $url); + + if ($rfc2616 && isset($_SERVER['REQUEST_METHOD']) + && $_SERVER['REQUEST_METHOD'] != 'HEAD') { + echo ' +<p>Redirecting to: <a href="'.str_replace('"', '%22', $url).'">' + .htmlspecialchars($url).'</a>.</p> +<script type="text/javascript"> +//<![CDATA[ +if (location.replace == null) { + location.replace = location.assign; +} +location.replace("'.str_replace('"', '\\"', $url).'"); +// ]]> +</script>'; + } + if ($exit) { + exit; + } + return true; + } + + /** + * This function returns the absolute URI for the partial URL passed. + * The current scheme (HTTP/HTTPS), host server, port, current script + * location are used if necessary to resolve any relative URLs. + * + * Offsets potentially created by PATH_INFO are taken care of to resolve + * relative URLs to the current script. + * + * You can choose a new protocol while resolving the URI. This is + * particularly useful when redirecting a web browser using relative URIs + * and to switch from HTTP to HTTPS, or vice-versa, at the same time. + * + * @param string $url Absolute or relative URI the redirect should + * go to. + * @param string $protocol Protocol to use when redirecting URIs. + * @param integer $port A new port number. + * + * @return string The absolute URI. + * @author Philippe Jausions <Philippe.Jausions@11abacus.com> + * @static + * @access public + */ + function absoluteURI($url = null, $protocol = null, $port = null) + { + // filter CR/LF + $url = str_replace(array("\r", "\n"), ' ', $url); + + // Mess around protocol and port with already absolute URIs + if (preg_match('!^([a-z0-9]+)://!i', $url)) { + if (empty($protocol) && empty($port)) { + return $url; + } + if (!empty($protocol)) { + $url = $protocol .':'. end($array = explode(':', $url, 2)); + } + if (!empty($port)) { + $url = preg_replace('!^(([a-z0-9]+)://[^/:]+)(:[\d]+)?!i', + '\1:'. $port, $url); + } + return $url; + } + + $host = 'localhost'; + if (!empty($_SERVER['HTTP_HOST'])) { + list($host) = explode(':', $_SERVER['HTTP_HOST']); + } elseif (!empty($_SERVER['SERVER_NAME'])) { + list($host) = explode(':', $_SERVER['SERVER_NAME']); + } + + if (empty($protocol)) { + if (isset($_SERVER['HTTPS']) && !strcasecmp($_SERVER['HTTPS'], 'on')) { + $protocol = 'https'; + } else { + $protocol = 'http'; + } + if (!isset($port) || $port != intval($port)) { + $port = isset($_SERVER['SERVER_PORT']) ? $_SERVER['SERVER_PORT'] : 80; + } + } + + if ($protocol == 'http' && $port == 80) { + unset($port); + } + if ($protocol == 'https' && $port == 443) { + unset($port); + } + + $server = $protocol.'://'.$host.(isset($port) ? ':'.$port : ''); + + $uriAll = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] + : $_SERVER['PHP_SELF']; + if (false !== ($q = strpos($uriAll, '?'))) { + $uriBase = substr($uriAll, 0, $q); + } else { + $uriBase = $uriAll; + } + if (!strlen($url) || $url{0} == '#') { + $url = $uriAll.$url; + } elseif ($url{0} == '?') { + $url = $uriBase.$url; + } + if ($url{0} == '/') { + return $server . $url; + } + + // Adjust for PATH_INFO if needed + if (isset($_SERVER['PATH_INFO']) && strlen($_SERVER['PATH_INFO'])) { + $path = dirname(substr($uriBase, 0, + -strlen($_SERVER['PATH_INFO']))); + } else { + /** + * Fixes bug #12672 PHP_SELF ending on / causes incorrect redirects + * + * @link http://pear.php.net/bugs/12672 + */ + $path = dirname($uriBase.'-'); + } + + if (substr($path = strtr($path, '\\', '/'), -1) != '/') { + $path .= '/'; + } + + return $server . $path . $url; + } + + /** + * Raise Error + * + * Lazy raising of PEAR_Errors. + * + * @param mixed $error Error + * @param integer $code Error code + * + * @return object PEAR_Error + * @static + * @access protected + */ + function raiseError($error = null, $code = null) + { + include_once 'PEAR.php'; + return PEAR::raiseError($error, $code); + } +} + +?> \ No newline at end of file diff --git a/lib/PEAR/HTTP/Request.php b/lib/PEAR/HTTP/Request.php new file mode 100644 index 0000000000..b4b9b668ad --- /dev/null +++ b/lib/PEAR/HTTP/Request.php @@ -0,0 +1,1521 @@ +<?php +/** + * Class for performing HTTP requests + * + * PHP versions 4 and 5 + * + * LICENSE: + * + * Copyright (c) 2002-2007, Richard Heyes + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * o Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * o Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * o The names of the authors may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @category HTTP + * @package HTTP_Request + * @author Richard Heyes <richard@phpguru.org> + * @author Alexey Borzov <avb@php.net> + * @copyright 2002-2007 Richard Heyes + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Request.php,v 1.63 2008/10/11 11:07:10 avb Exp $ + * @link http://pear.php.net/package/HTTP_Request/ + */ + +/** + * PEAR and PEAR_Error classes (for error handling) + */ +require_once 'PEAR.php'; +/** + * Socket class + */ +require_once 'Net/Socket.php'; +/** + * URL handling class + */ +require_once 'Net/URL.php'; + +/**#@+ + * Constants for HTTP request methods + */ +define('HTTP_REQUEST_METHOD_GET', 'GET', true); +define('HTTP_REQUEST_METHOD_HEAD', 'HEAD', true); +define('HTTP_REQUEST_METHOD_POST', 'POST', true); +define('HTTP_REQUEST_METHOD_PUT', 'PUT', true); +define('HTTP_REQUEST_METHOD_DELETE', 'DELETE', true); +define('HTTP_REQUEST_METHOD_OPTIONS', 'OPTIONS', true); +define('HTTP_REQUEST_METHOD_TRACE', 'TRACE', true); +/**#@-*/ + +/**#@+ + * Constants for HTTP request error codes + */ +define('HTTP_REQUEST_ERROR_FILE', 1); +define('HTTP_REQUEST_ERROR_URL', 2); +define('HTTP_REQUEST_ERROR_PROXY', 4); +define('HTTP_REQUEST_ERROR_REDIRECTS', 8); +define('HTTP_REQUEST_ERROR_RESPONSE', 16); +define('HTTP_REQUEST_ERROR_GZIP_METHOD', 32); +define('HTTP_REQUEST_ERROR_GZIP_READ', 64); +define('HTTP_REQUEST_ERROR_GZIP_DATA', 128); +define('HTTP_REQUEST_ERROR_GZIP_CRC', 256); +/**#@-*/ + +/**#@+ + * Constants for HTTP protocol versions + */ +define('HTTP_REQUEST_HTTP_VER_1_0', '1.0', true); +define('HTTP_REQUEST_HTTP_VER_1_1', '1.1', true); +/**#@-*/ + +if (extension_loaded('mbstring') && (2 & ini_get('mbstring.func_overload'))) { + /** + * Whether string functions are overloaded by their mbstring equivalents + */ + define('HTTP_REQUEST_MBSTRING', true); +} else { + /** + * @ignore + */ + define('HTTP_REQUEST_MBSTRING', false); +} + +/** + * Class for performing HTTP requests + * + * Simple example (fetches yahoo.com and displays it): + * <code> + * $a = &new HTTP_Request('http://www.yahoo.com/'); + * $a->sendRequest(); + * echo $a->getResponseBody(); + * </code> + * + * @category HTTP + * @package HTTP_Request + * @author Richard Heyes <richard@phpguru.org> + * @author Alexey Borzov <avb@php.net> + * @version Release: 1.4.4 + */ +class HTTP_Request +{ + /**#@+ + * @access private + */ + /** + * Instance of Net_URL + * @var Net_URL + */ + var $_url; + + /** + * Type of request + * @var string + */ + var $_method; + + /** + * HTTP Version + * @var string + */ + var $_http; + + /** + * Request headers + * @var array + */ + var $_requestHeaders; + + /** + * Basic Auth Username + * @var string + */ + var $_user; + + /** + * Basic Auth Password + * @var string + */ + var $_pass; + + /** + * Socket object + * @var Net_Socket + */ + var $_sock; + + /** + * Proxy server + * @var string + */ + var $_proxy_host; + + /** + * Proxy port + * @var integer + */ + var $_proxy_port; + + /** + * Proxy username + * @var string + */ + var $_proxy_user; + + /** + * Proxy password + * @var string + */ + var $_proxy_pass; + + /** + * Post data + * @var array + */ + var $_postData; + + /** + * Request body + * @var string + */ + var $_body; + + /** + * A list of methods that MUST NOT have a request body, per RFC 2616 + * @var array + */ + var $_bodyDisallowed = array('TRACE'); + + /** + * Methods having defined semantics for request body + * + * Content-Length header (indicating that the body follows, section 4.3 of + * RFC 2616) will be sent for these methods even if no body was added + * + * @var array + */ + var $_bodyRequired = array('POST', 'PUT'); + + /** + * Files to post + * @var array + */ + var $_postFiles = array(); + + /** + * Connection timeout. + * @var float + */ + var $_timeout; + + /** + * HTTP_Response object + * @var HTTP_Response + */ + var $_response; + + /** + * Whether to allow redirects + * @var boolean + */ + var $_allowRedirects; + + /** + * Maximum redirects allowed + * @var integer + */ + var $_maxRedirects; + + /** + * Current number of redirects + * @var integer + */ + var $_redirects; + + /** + * Whether to append brackets [] to array variables + * @var bool + */ + var $_useBrackets = true; + + /** + * Attached listeners + * @var array + */ + var $_listeners = array(); + + /** + * Whether to save response body in response object property + * @var bool + */ + var $_saveBody = true; + + /** + * Timeout for reading from socket (array(seconds, microseconds)) + * @var array + */ + var $_readTimeout = null; + + /** + * Options to pass to Net_Socket::connect. See stream_context_create + * @var array + */ + var $_socketOptions = null; + /**#@-*/ + + /** + * Constructor + * + * Sets up the object + * @param string The url to fetch/access + * @param array Associative array of parameters which can have the following keys: + * <ul> + * <li>method - Method to use, GET, POST etc (string)</li> + * <li>http - HTTP Version to use, 1.0 or 1.1 (string)</li> + * <li>user - Basic Auth username (string)</li> + * <li>pass - Basic Auth password (string)</li> + * <li>proxy_host - Proxy server host (string)</li> + * <li>proxy_port - Proxy server port (integer)</li> + * <li>proxy_user - Proxy auth username (string)</li> + * <li>proxy_pass - Proxy auth password (string)</li> + * <li>timeout - Connection timeout in seconds (float)</li> + * <li>allowRedirects - Whether to follow redirects or not (bool)</li> + * <li>maxRedirects - Max number of redirects to follow (integer)</li> + * <li>useBrackets - Whether to append [] to array variable names (bool)</li> + * <li>saveBody - Whether to save response body in response object property (bool)</li> + * <li>readTimeout - Timeout for reading / writing data over the socket (array (seconds, microseconds))</li> + * <li>socketOptions - Options to pass to Net_Socket object (array)</li> + * </ul> + * @access public + */ + function HTTP_Request($url = '', $params = array()) + { + $this->_method = HTTP_REQUEST_METHOD_GET; + $this->_http = HTTP_REQUEST_HTTP_VER_1_1; + $this->_requestHeaders = array(); + $this->_postData = array(); + $this->_body = null; + + $this->_user = null; + $this->_pass = null; + + $this->_proxy_host = null; + $this->_proxy_port = null; + $this->_proxy_user = null; + $this->_proxy_pass = null; + + $this->_allowRedirects = false; + $this->_maxRedirects = 3; + $this->_redirects = 0; + + $this->_timeout = null; + $this->_response = null; + + foreach ($params as $key => $value) { + $this->{'_' . $key} = $value; + } + + if (!empty($url)) { + $this->setURL($url); + } + + // Default useragent + $this->addHeader('User-Agent', 'PEAR HTTP_Request class ( http://pear.php.net/ )'); + + // We don't do keep-alives by default + $this->addHeader('Connection', 'close'); + + // Basic authentication + if (!empty($this->_user)) { + $this->addHeader('Authorization', 'Basic ' . base64_encode($this->_user . ':' . $this->_pass)); + } + + // Proxy authentication (see bug #5913) + if (!empty($this->_proxy_user)) { + $this->addHeader('Proxy-Authorization', 'Basic ' . base64_encode($this->_proxy_user . ':' . $this->_proxy_pass)); + } + + // Use gzip encoding if possible + if (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http && extension_loaded('zlib')) { + $this->addHeader('Accept-Encoding', 'gzip'); + } + } + + /** + * Generates a Host header for HTTP/1.1 requests + * + * @access private + * @return string + */ + function _generateHostHeader() + { + if ($this->_url->port != 80 AND strcasecmp($this->_url->protocol, 'http') == 0) { + $host = $this->_url->host . ':' . $this->_url->port; + + } elseif ($this->_url->port != 443 AND strcasecmp($this->_url->protocol, 'https') == 0) { + $host = $this->_url->host . ':' . $this->_url->port; + + } elseif ($this->_url->port == 443 AND strcasecmp($this->_url->protocol, 'https') == 0 AND strpos($this->_url->url, ':443') !== false) { + $host = $this->_url->host . ':' . $this->_url->port; + + } else { + $host = $this->_url->host; + } + + return $host; + } + + /** + * Resets the object to its initial state (DEPRECATED). + * Takes the same parameters as the constructor. + * + * @param string $url The url to be requested + * @param array $params Associative array of parameters + * (see constructor for details) + * @access public + * @deprecated deprecated since 1.2, call the constructor if this is necessary + */ + function reset($url, $params = array()) + { + $this->HTTP_Request($url, $params); + } + + /** + * Sets the URL to be requested + * + * @param string The url to be requested + * @access public + */ + function setURL($url) + { + $this->_url = new Net_URL($url, $this->_useBrackets); + + if (!empty($this->_url->user) || !empty($this->_url->pass)) { + $this->setBasicAuth($this->_url->user, $this->_url->pass); + } + + if (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http) { + $this->addHeader('Host', $this->_generateHostHeader()); + } + + // set '/' instead of empty path rather than check later (see bug #8662) + if (empty($this->_url->path)) { + $this->_url->path = '/'; + } + } + + /** + * Returns the current request URL + * + * @return string Current request URL + * @access public + */ + function getUrl() + { + return empty($this->_url)? '': $this->_url->getUrl(); + } + + /** + * Sets a proxy to be used + * + * @param string Proxy host + * @param int Proxy port + * @param string Proxy username + * @param string Proxy password + * @access public + */ + function setProxy($host, $port = 8080, $user = null, $pass = null) + { + $this->_proxy_host = $host; + $this->_proxy_port = $port; + $this->_proxy_user = $user; + $this->_proxy_pass = $pass; + + if (!empty($user)) { + $this->addHeader('Proxy-Authorization', 'Basic ' . base64_encode($user . ':' . $pass)); + } + } + + /** + * Sets basic authentication parameters + * + * @param string Username + * @param string Password + */ + function setBasicAuth($user, $pass) + { + $this->_user = $user; + $this->_pass = $pass; + + $this->addHeader('Authorization', 'Basic ' . base64_encode($user . ':' . $pass)); + } + + /** + * Sets the method to be used, GET, POST etc. + * + * @param string Method to use. Use the defined constants for this + * @access public + */ + function setMethod($method) + { + $this->_method = $method; + } + + /** + * Sets the HTTP version to use, 1.0 or 1.1 + * + * @param string Version to use. Use the defined constants for this + * @access public + */ + function setHttpVer($http) + { + $this->_http = $http; + } + + /** + * Adds a request header + * + * @param string Header name + * @param string Header value + * @access public + */ + function addHeader($name, $value) + { + $this->_requestHeaders[strtolower($name)] = $value; + } + + /** + * Removes a request header + * + * @param string Header name to remove + * @access public + */ + function removeHeader($name) + { + if (isset($this->_requestHeaders[strtolower($name)])) { + unset($this->_requestHeaders[strtolower($name)]); + } + } + + /** + * Adds a querystring parameter + * + * @param string Querystring parameter name + * @param string Querystring parameter value + * @param bool Whether the value is already urlencoded or not, default = not + * @access public + */ + function addQueryString($name, $value, $preencoded = false) + { + $this->_url->addQueryString($name, $value, $preencoded); + } + + /** + * Sets the querystring to literally what you supply + * + * @param string The querystring data. Should be of the format foo=bar&x=y etc + * @param bool Whether data is already urlencoded or not, default = already encoded + * @access public + */ + function addRawQueryString($querystring, $preencoded = true) + { + $this->_url->addRawQueryString($querystring, $preencoded); + } + + /** + * Adds postdata items + * + * @param string Post data name + * @param string Post data value + * @param bool Whether data is already urlencoded or not, default = not + * @access public + */ + function addPostData($name, $value, $preencoded = false) + { + if ($preencoded) { + $this->_postData[$name] = $value; + } else { + $this->_postData[$name] = $this->_arrayMapRecursive('urlencode', $value); + } + } + + /** + * Recursively applies the callback function to the value + * + * @param mixed Callback function + * @param mixed Value to process + * @access private + * @return mixed Processed value + */ + function _arrayMapRecursive($callback, $value) + { + if (!is_array($value)) { + return call_user_func($callback, $value); + } else { + $map = array(); + foreach ($value as $k => $v) { + $map[$k] = $this->_arrayMapRecursive($callback, $v); + } + return $map; + } + } + + /** + * Adds a file to form-based file upload + * + * Used to emulate file upload via a HTML form. The method also sets + * Content-Type of HTTP request to 'multipart/form-data'. + * + * If you just want to send the contents of a file as the body of HTTP + * request you should use setBody() method. + * + * @access public + * @param string name of file-upload field + * @param mixed file name(s) + * @param mixed content-type(s) of file(s) being uploaded + * @return bool true on success + * @throws PEAR_Error + */ + function addFile($inputName, $fileName, $contentType = 'application/octet-stream') + { + if (!is_array($fileName) && !is_readable($fileName)) { + return PEAR::raiseError("File '{$fileName}' is not readable", HTTP_REQUEST_ERROR_FILE); + } elseif (is_array($fileName)) { + foreach ($fileName as $name) { + if (!is_readable($name)) { + return PEAR::raiseError("File '{$name}' is not readable", HTTP_REQUEST_ERROR_FILE); + } + } + } + $this->addHeader('Content-Type', 'multipart/form-data'); + $this->_postFiles[$inputName] = array( + 'name' => $fileName, + 'type' => $contentType + ); + return true; + } + + /** + * Adds raw postdata (DEPRECATED) + * + * @param string The data + * @param bool Whether data is preencoded or not, default = already encoded + * @access public + * @deprecated deprecated since 1.3.0, method setBody() should be used instead + */ + function addRawPostData($postdata, $preencoded = true) + { + $this->_body = $preencoded ? $postdata : urlencode($postdata); + } + + /** + * Sets the request body (for POST, PUT and similar requests) + * + * @param string Request body + * @access public + */ + function setBody($body) + { + $this->_body = $body; + } + + /** + * Clears any postdata that has been added (DEPRECATED). + * + * Useful for multiple request scenarios. + * + * @access public + * @deprecated deprecated since 1.2 + */ + function clearPostData() + { + $this->_postData = null; + } + + /** + * Appends a cookie to "Cookie:" header + * + * @param string $name cookie name + * @param string $value cookie value + * @access public + */ + function addCookie($name, $value) + { + $cookies = isset($this->_requestHeaders['cookie']) ? $this->_requestHeaders['cookie']. '; ' : ''; + $this->addHeader('Cookie', $cookies . $name . '=' . $value); + } + + /** + * Clears any cookies that have been added (DEPRECATED). + * + * Useful for multiple request scenarios + * + * @access public + * @deprecated deprecated since 1.2 + */ + function clearCookies() + { + $this->removeHeader('Cookie'); + } + + /** + * Sends the request + * + * @access public + * @param bool Whether to store response body in Response object property, + * set this to false if downloading a LARGE file and using a Listener + * @return mixed PEAR error on error, true otherwise + */ + function sendRequest($saveBody = true) + { + if (!$this->_url instanceof Net_Url) { + return PEAR::raiseError('No URL given', HTTP_REQUEST_ERROR_URL); + } + + $host = isset($this->_proxy_host) ? $this->_proxy_host : $this->_url->host; + $port = isset($this->_proxy_port) ? $this->_proxy_port : $this->_url->port; + + if (strcasecmp($this->_url->protocol, 'https') == 0) { + // Bug #14127, don't try connecting to HTTPS sites without OpenSSL + if (version_compare(PHP_VERSION, '4.3.0', '<') || !extension_loaded('openssl')) { + return PEAR::raiseError('Need PHP 4.3.0 or later with OpenSSL support for https:// requests', + HTTP_REQUEST_ERROR_URL); + } elseif (isset($this->_proxy_host)) { + return PEAR::raiseError('HTTPS proxies are not supported', HTTP_REQUEST_ERROR_PROXY); + } + $host = 'ssl://' . $host; + } + + // magic quotes may fuck up file uploads and chunked response processing + $magicQuotes = ini_get('magic_quotes_runtime'); + ini_set('magic_quotes_runtime', false); + + // RFC 2068, section 19.7.1: A client MUST NOT send the Keep-Alive + // connection token to a proxy server... + if (isset($this->_proxy_host) && !empty($this->_requestHeaders['connection']) && + 'Keep-Alive' == $this->_requestHeaders['connection']) + { + $this->removeHeader('connection'); + } + + $keepAlive = (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http && empty($this->_requestHeaders['connection'])) || + (!empty($this->_requestHeaders['connection']) && 'Keep-Alive' == $this->_requestHeaders['connection']); + $sockets = &PEAR::getStaticProperty('HTTP_Request', 'sockets'); + $sockKey = $host . ':' . $port; + unset($this->_sock); + + // There is a connected socket in the "static" property? + if ($keepAlive && !empty($sockets[$sockKey]) && + !empty($sockets[$sockKey]->fp)) + { + $this->_sock =& $sockets[$sockKey]; + $err = null; + } else { + $this->_notify('connect'); + $this->_sock = new Net_Socket(); + $err = $this->_sock->connect($host, $port, null, $this->_timeout, $this->_socketOptions); + } + PEAR::isError($err) or $err = $this->_sock->write($this->_buildRequest()); + + if (!PEAR::isError($err)) { + if (!empty($this->_readTimeout)) { + $this->_sock->setTimeout($this->_readTimeout[0], $this->_readTimeout[1]); + } + + $this->_notify('sentRequest'); + + // Read the response + $this->_response = new HTTP_Response($this->_sock, $this->_listeners); + $err = $this->_response->process( + $this->_saveBody && $saveBody, + HTTP_REQUEST_METHOD_HEAD != $this->_method + ); + + if ($keepAlive) { + $keepAlive = (isset($this->_response->_headers['content-length']) + || (isset($this->_response->_headers['transfer-encoding']) + && strtolower($this->_response->_headers['transfer-encoding']) == 'chunked')); + if ($keepAlive) { + if (isset($this->_response->_headers['connection'])) { + $keepAlive = strtolower($this->_response->_headers['connection']) == 'keep-alive'; + } else { + $keepAlive = 'HTTP/'.HTTP_REQUEST_HTTP_VER_1_1 == $this->_response->_protocol; + } + } + } + } + + ini_set('magic_quotes_runtime', $magicQuotes); + + if (PEAR::isError($err)) { + return $err; + } + + if (!$keepAlive) { + $this->disconnect(); + // Store the connected socket in "static" property + } elseif (empty($sockets[$sockKey]) || empty($sockets[$sockKey]->fp)) { + $sockets[$sockKey] =& $this->_sock; + } + + // Check for redirection + if ( $this->_allowRedirects + AND $this->_redirects <= $this->_maxRedirects + AND $this->getResponseCode() > 300 + AND $this->getResponseCode() < 399 + AND !empty($this->_response->_headers['location'])) { + + + $redirect = $this->_response->_headers['location']; + + // Absolute URL + if (preg_match('/^https?:\/\//i', $redirect)) { + $this->_url = new Net_URL($redirect); + $this->addHeader('Host', $this->_generateHostHeader()); + // Absolute path + } elseif ($redirect{0} == '/') { + $this->_url->path = $redirect; + + // Relative path + } elseif (substr($redirect, 0, 3) == '../' OR substr($redirect, 0, 2) == './') { + if (substr($this->_url->path, -1) == '/') { + $redirect = $this->_url->path . $redirect; + } else { + $redirect = dirname($this->_url->path) . '/' . $redirect; + } + $redirect = Net_URL::resolvePath($redirect); + $this->_url->path = $redirect; + + // Filename, no path + } else { + if (substr($this->_url->path, -1) == '/') { + $redirect = $this->_url->path . $redirect; + } else { + $redirect = dirname($this->_url->path) . '/' . $redirect; + } + $this->_url->path = $redirect; + } + + $this->_redirects++; + return $this->sendRequest($saveBody); + + // Too many redirects + } elseif ($this->_allowRedirects AND $this->_redirects > $this->_maxRedirects) { + return PEAR::raiseError('Too many redirects', HTTP_REQUEST_ERROR_REDIRECTS); + } + + return true; + } + + /** + * Disconnect the socket, if connected. Only useful if using Keep-Alive. + * + * @access public + */ + function disconnect() + { + if (!empty($this->_sock) && !empty($this->_sock->fp)) { + $this->_notify('disconnect'); + $this->_sock->disconnect(); + } + } + + /** + * Returns the response code + * + * @access public + * @return mixed Response code, false if not set + */ + function getResponseCode() + { + return isset($this->_response->_code) ? $this->_response->_code : false; + } + + /** + * Returns the response reason phrase + * + * @access public + * @return mixed Response reason phrase, false if not set + */ + function getResponseReason() + { + return isset($this->_response->_reason) ? $this->_response->_reason : false; + } + + /** + * Returns either the named header or all if no name given + * + * @access public + * @param string The header name to return, do not set to get all headers + * @return mixed either the value of $headername (false if header is not present) + * or an array of all headers + */ + function getResponseHeader($headername = null) + { + if (!isset($headername)) { + return isset($this->_response->_headers)? $this->_response->_headers: array(); + } else { + $headername = strtolower($headername); + return isset($this->_response->_headers[$headername]) ? $this->_response->_headers[$headername] : false; + } + } + + /** + * Returns the body of the response + * + * @access public + * @return mixed response body, false if not set + */ + function getResponseBody() + { + return isset($this->_response->_body) ? $this->_response->_body : false; + } + + /** + * Returns cookies set in response + * + * @access public + * @return mixed array of response cookies, false if none are present + */ + function getResponseCookies() + { + return isset($this->_response->_cookies) ? $this->_response->_cookies : false; + } + + /** + * Builds the request string + * + * @access private + * @return string The request string + */ + function _buildRequest() + { + $separator = ini_get('arg_separator.output'); + ini_set('arg_separator.output', '&'); + $querystring = ($querystring = $this->_url->getQueryString()) ? '?' . $querystring : ''; + ini_set('arg_separator.output', $separator); + + $host = isset($this->_proxy_host) ? $this->_url->protocol . '://' . $this->_url->host : ''; + $port = (isset($this->_proxy_host) AND $this->_url->port != 80) ? ':' . $this->_url->port : ''; + $path = $this->_url->path . $querystring; + $url = $host . $port . $path; + + if (!strlen($url)) { + $url = '/'; + } + + $request = $this->_method . ' ' . $url . ' HTTP/' . $this->_http . "\r\n"; + + if (in_array($this->_method, $this->_bodyDisallowed) || + (0 == strlen($this->_body) && (HTTP_REQUEST_METHOD_POST != $this->_method || + (empty($this->_postData) && empty($this->_postFiles))))) + { + $this->removeHeader('Content-Type'); + } else { + if (empty($this->_requestHeaders['content-type'])) { + // Add default content-type + $this->addHeader('Content-Type', 'application/x-www-form-urlencoded'); + } elseif ('multipart/form-data' == $this->_requestHeaders['content-type']) { + $boundary = 'HTTP_Request_' . md5(uniqid('request') . microtime()); + $this->addHeader('Content-Type', 'multipart/form-data; boundary=' . $boundary); + } + } + + // Request Headers + if (!empty($this->_requestHeaders)) { + foreach ($this->_requestHeaders as $name => $value) { + $canonicalName = implode('-', array_map('ucfirst', explode('-', $name))); + $request .= $canonicalName . ': ' . $value . "\r\n"; + } + } + + // Method does not allow a body, simply add a final CRLF + if (in_array($this->_method, $this->_bodyDisallowed)) { + + $request .= "\r\n"; + + // Post data if it's an array + } elseif (HTTP_REQUEST_METHOD_POST == $this->_method && + (!empty($this->_postData) || !empty($this->_postFiles))) { + + // "normal" POST request + if (!isset($boundary)) { + $postdata = implode('&', array_map( + create_function('$a', 'return $a[0] . \'=\' . $a[1];'), + $this->_flattenArray('', $this->_postData) + )); + + // multipart request, probably with file uploads + } else { + $postdata = ''; + if (!empty($this->_postData)) { + $flatData = $this->_flattenArray('', $this->_postData); + foreach ($flatData as $item) { + $postdata .= '--' . $boundary . "\r\n"; + $postdata .= 'Content-Disposition: form-data; name="' . $item[0] . '"'; + $postdata .= "\r\n\r\n" . urldecode($item[1]) . "\r\n"; + } + } + foreach ($this->_postFiles as $name => $value) { + if (is_array($value['name'])) { + $varname = $name . ($this->_useBrackets? '[]': ''); + } else { + $varname = $name; + $value['name'] = array($value['name']); + } + foreach ($value['name'] as $key => $filename) { + $fp = fopen($filename, 'r'); + $basename = basename($filename); + $type = is_array($value['type'])? @$value['type'][$key]: $value['type']; + + $postdata .= '--' . $boundary . "\r\n"; + $postdata .= 'Content-Disposition: form-data; name="' . $varname . '"; filename="' . $basename . '"'; + $postdata .= "\r\nContent-Type: " . $type; + $postdata .= "\r\n\r\n" . fread($fp, filesize($filename)) . "\r\n"; + fclose($fp); + } + } + $postdata .= '--' . $boundary . "--\r\n"; + } + $request .= 'Content-Length: ' . + (HTTP_REQUEST_MBSTRING? mb_strlen($postdata, 'iso-8859-1'): strlen($postdata)) . + "\r\n\r\n"; + $request .= $postdata; + + // Explicitly set request body + } elseif (0 < strlen($this->_body)) { + + $request .= 'Content-Length: ' . + (HTTP_REQUEST_MBSTRING? mb_strlen($this->_body, 'iso-8859-1'): strlen($this->_body)) . + "\r\n\r\n"; + $request .= $this->_body; + + // No body: send a Content-Length header nonetheless (request #12900), + // but do that only for methods that require a body (bug #14740) + } else { + + if (in_array($this->_method, $this->_bodyRequired)) { + $request .= "Content-Length: 0\r\n"; + } + $request .= "\r\n"; + } + + return $request; + } + + /** + * Helper function to change the (probably multidimensional) associative array + * into the simple one. + * + * @param string name for item + * @param mixed item's values + * @return array array with the following items: array('item name', 'item value'); + * @access private + */ + function _flattenArray($name, $values) + { + if (!is_array($values)) { + return array(array($name, $values)); + } else { + $ret = array(); + foreach ($values as $k => $v) { + if (empty($name)) { + $newName = $k; + } elseif ($this->_useBrackets) { + $newName = $name . '[' . $k . ']'; + } else { + $newName = $name; + } + $ret = array_merge($ret, $this->_flattenArray($newName, $v)); + } + return $ret; + } + } + + + /** + * Adds a Listener to the list of listeners that are notified of + * the object's events + * + * Events sent by HTTP_Request object + * - 'connect': on connection to server + * - 'sentRequest': after the request was sent + * - 'disconnect': on disconnection from server + * + * Events sent by HTTP_Response object + * - 'gotHeaders': after receiving response headers (headers are passed in $data) + * - 'tick': on receiving a part of response body (the part is passed in $data) + * - 'gzTick': on receiving a gzip-encoded part of response body (ditto) + * - 'gotBody': after receiving the response body (passes the decoded body in $data if it was gzipped) + * + * @param HTTP_Request_Listener listener to attach + * @return boolean whether the listener was successfully attached + * @access public + */ + function attach(&$listener) + { + if (!is_a($listener, 'HTTP_Request_Listener')) { + return false; + } + $this->_listeners[$listener->getId()] =& $listener; + return true; + } + + + /** + * Removes a Listener from the list of listeners + * + * @param HTTP_Request_Listener listener to detach + * @return boolean whether the listener was successfully detached + * @access public + */ + function detach(&$listener) + { + if (!is_a($listener, 'HTTP_Request_Listener') || + !isset($this->_listeners[$listener->getId()])) { + return false; + } + unset($this->_listeners[$listener->getId()]); + return true; + } + + + /** + * Notifies all registered listeners of an event. + * + * @param string Event name + * @param mixed Additional data + * @access private + * @see HTTP_Request::attach() + */ + function _notify($event, $data = null) + { + foreach (array_keys($this->_listeners) as $id) { + $this->_listeners[$id]->update($this, $event, $data); + } + } +} + + +/** + * Response class to complement the Request class + * + * @category HTTP + * @package HTTP_Request + * @author Richard Heyes <richard@phpguru.org> + * @author Alexey Borzov <avb@php.net> + * @version Release: 1.4.4 + */ +class HTTP_Response +{ + /** + * Socket object + * @var Net_Socket + */ + var $_sock; + + /** + * Protocol + * @var string + */ + var $_protocol; + + /** + * Return code + * @var string + */ + var $_code; + + /** + * Response reason phrase + * @var string + */ + var $_reason; + + /** + * Response headers + * @var array + */ + var $_headers; + + /** + * Cookies set in response + * @var array + */ + var $_cookies; + + /** + * Response body + * @var string + */ + var $_body = ''; + + /** + * Used by _readChunked(): remaining length of the current chunk + * @var string + */ + var $_chunkLength = 0; + + /** + * Attached listeners + * @var array + */ + var $_listeners = array(); + + /** + * Bytes left to read from message-body + * @var null|int + */ + var $_toRead; + + /** + * Constructor + * + * @param Net_Socket socket to read the response from + * @param array listeners attached to request + */ + function HTTP_Response(&$sock, &$listeners) + { + $this->_sock =& $sock; + $this->_listeners =& $listeners; + } + + + /** + * Processes a HTTP response + * + * This extracts response code, headers, cookies and decodes body if it + * was encoded in some way + * + * @access public + * @param bool Whether to store response body in object property, set + * this to false if downloading a LARGE file and using a Listener. + * This is assumed to be true if body is gzip-encoded. + * @param bool Whether the response can actually have a message-body. + * Will be set to false for HEAD requests. + * @throws PEAR_Error + * @return mixed true on success, PEAR_Error in case of malformed response + */ + function process($saveBody = true, $canHaveBody = true) + { + do { + $line = $this->_sock->readLine(); + if (!preg_match('!^(HTTP/\d\.\d) (\d{3})(?: (.+))?!', $line, $s)) { + return PEAR::raiseError('Malformed response', HTTP_REQUEST_ERROR_RESPONSE); + } else { + $this->_protocol = $s[1]; + $this->_code = intval($s[2]); + $this->_reason = empty($s[3])? null: $s[3]; + } + while ('' !== ($header = $this->_sock->readLine())) { + $this->_processHeader($header); + } + } while (100 == $this->_code); + + $this->_notify('gotHeaders', $this->_headers); + + // RFC 2616, section 4.4: + // 1. Any response message which "MUST NOT" include a message-body ... + // is always terminated by the first empty line after the header fields + // 3. ... If a message is received with both a + // Transfer-Encoding header field and a Content-Length header field, + // the latter MUST be ignored. + $canHaveBody = $canHaveBody && $this->_code >= 200 && + $this->_code != 204 && $this->_code != 304; + + // If response body is present, read it and decode + $chunked = isset($this->_headers['transfer-encoding']) && ('chunked' == $this->_headers['transfer-encoding']); + $gzipped = isset($this->_headers['content-encoding']) && ('gzip' == $this->_headers['content-encoding']); + $hasBody = false; + if ($canHaveBody && ($chunked || !isset($this->_headers['content-length']) || + 0 != $this->_headers['content-length'])) + { + if ($chunked || !isset($this->_headers['content-length'])) { + $this->_toRead = null; + } else { + $this->_toRead = $this->_headers['content-length']; + } + while (!$this->_sock->eof() && (is_null($this->_toRead) || 0 < $this->_toRead)) { + if ($chunked) { + $data = $this->_readChunked(); + } elseif (is_null($this->_toRead)) { + $data = $this->_sock->read(4096); + } else { + $data = $this->_sock->read(min(4096, $this->_toRead)); + $this->_toRead -= HTTP_REQUEST_MBSTRING? mb_strlen($data, 'iso-8859-1'): strlen($data); + } + if ('' == $data && (!$this->_chunkLength || $this->_sock->eof())) { + break; + } else { + $hasBody = true; + if ($saveBody || $gzipped) { + $this->_body .= $data; + } + $this->_notify($gzipped? 'gzTick': 'tick', $data); + } + } + } + + if ($hasBody) { + // Uncompress the body if needed + if ($gzipped) { + $body = $this->_decodeGzip($this->_body); + if (PEAR::isError($body)) { + return $body; + } + $this->_body = $body; + $this->_notify('gotBody', $this->_body); + } else { + $this->_notify('gotBody'); + } + } + return true; + } + + + /** + * Processes the response header + * + * @access private + * @param string HTTP header + */ + function _processHeader($header) + { + if (false === strpos($header, ':')) { + return; + } + list($headername, $headervalue) = explode(':', $header, 2); + $headername = strtolower($headername); + $headervalue = ltrim($headervalue); + + if ('set-cookie' != $headername) { + if (isset($this->_headers[$headername])) { + $this->_headers[$headername] .= ',' . $headervalue; + } else { + $this->_headers[$headername] = $headervalue; + } + } else { + $this->_parseCookie($headervalue); + } + } + + + /** + * Parse a Set-Cookie header to fill $_cookies array + * + * @access private + * @param string value of Set-Cookie header + */ + function _parseCookie($headervalue) + { + $cookie = array( + 'expires' => null, + 'domain' => null, + 'path' => null, + 'secure' => false + ); + + // Only a name=value pair + if (!strpos($headervalue, ';')) { + $pos = strpos($headervalue, '='); + $cookie['name'] = trim(substr($headervalue, 0, $pos)); + $cookie['value'] = trim(substr($headervalue, $pos + 1)); + + // Some optional parameters are supplied + } else { + $elements = explode(';', $headervalue); + $pos = strpos($elements[0], '='); + $cookie['name'] = trim(substr($elements[0], 0, $pos)); + $cookie['value'] = trim(substr($elements[0], $pos + 1)); + + for ($i = 1; $i < count($elements); $i++) { + if (false === strpos($elements[$i], '=')) { + $elName = trim($elements[$i]); + $elValue = null; + } else { + list ($elName, $elValue) = array_map('trim', explode('=', $elements[$i])); + } + $elName = strtolower($elName); + if ('secure' == $elName) { + $cookie['secure'] = true; + } elseif ('expires' == $elName) { + $cookie['expires'] = str_replace('"', '', $elValue); + } elseif ('path' == $elName || 'domain' == $elName) { + $cookie[$elName] = urldecode($elValue); + } else { + $cookie[$elName] = $elValue; + } + } + } + $this->_cookies[] = $cookie; + } + + + /** + * Read a part of response body encoded with chunked Transfer-Encoding + * + * @access private + * @return string + */ + function _readChunked() + { + // at start of the next chunk? + if (0 == $this->_chunkLength) { + $line = $this->_sock->readLine(); + if (preg_match('/^([0-9a-f]+)/i', $line, $matches)) { + $this->_chunkLength = hexdec($matches[1]); + // Chunk with zero length indicates the end + if (0 == $this->_chunkLength) { + $this->_sock->readLine(); // make this an eof() + return ''; + } + } else { + return ''; + } + } + $data = $this->_sock->read($this->_chunkLength); + $this->_chunkLength -= HTTP_REQUEST_MBSTRING? mb_strlen($data, 'iso-8859-1'): strlen($data); + if (0 == $this->_chunkLength) { + $this->_sock->readLine(); // Trailing CRLF + } + return $data; + } + + + /** + * Notifies all registered listeners of an event. + * + * @param string Event name + * @param mixed Additional data + * @access private + * @see HTTP_Request::_notify() + */ + function _notify($event, $data = null) + { + foreach (array_keys($this->_listeners) as $id) { + $this->_listeners[$id]->update($this, $event, $data); + } + } + + + /** + * Decodes the message-body encoded by gzip + * + * The real decoding work is done by gzinflate() built-in function, this + * method only parses the header and checks data for compliance with + * RFC 1952 + * + * @access private + * @param string gzip-encoded data + * @return string decoded data + */ + function _decodeGzip($data) + { + if (HTTP_REQUEST_MBSTRING) { + $oldEncoding = mb_internal_encoding(); + mb_internal_encoding('iso-8859-1'); + } + $length = strlen($data); + // If it doesn't look like gzip-encoded data, don't bother + if (18 > $length || strcmp(substr($data, 0, 2), "\x1f\x8b")) { + return $data; + } + $method = ord(substr($data, 2, 1)); + if (8 != $method) { + return PEAR::raiseError('_decodeGzip(): unknown compression method', HTTP_REQUEST_ERROR_GZIP_METHOD); + } + $flags = ord(substr($data, 3, 1)); + if ($flags & 224) { + return PEAR::raiseError('_decodeGzip(): reserved bits are set', HTTP_REQUEST_ERROR_GZIP_DATA); + } + + // header is 10 bytes minimum. may be longer, though. + $headerLength = 10; + // extra fields, need to skip 'em + if ($flags & 4) { + if ($length - $headerLength - 2 < 8) { + return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA); + } + $extraLength = unpack('v', substr($data, 10, 2)); + if ($length - $headerLength - 2 - $extraLength[1] < 8) { + return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA); + } + $headerLength += $extraLength[1] + 2; + } + // file name, need to skip that + if ($flags & 8) { + if ($length - $headerLength - 1 < 8) { + return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA); + } + $filenameLength = strpos(substr($data, $headerLength), chr(0)); + if (false === $filenameLength || $length - $headerLength - $filenameLength - 1 < 8) { + return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA); + } + $headerLength += $filenameLength + 1; + } + // comment, need to skip that also + if ($flags & 16) { + if ($length - $headerLength - 1 < 8) { + return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA); + } + $commentLength = strpos(substr($data, $headerLength), chr(0)); + if (false === $commentLength || $length - $headerLength - $commentLength - 1 < 8) { + return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA); + } + $headerLength += $commentLength + 1; + } + // have a CRC for header. let's check + if ($flags & 1) { + if ($length - $headerLength - 2 < 8) { + return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA); + } + $crcReal = 0xffff & crc32(substr($data, 0, $headerLength)); + $crcStored = unpack('v', substr($data, $headerLength, 2)); + if ($crcReal != $crcStored[1]) { + return PEAR::raiseError('_decodeGzip(): header CRC check failed', HTTP_REQUEST_ERROR_GZIP_CRC); + } + $headerLength += 2; + } + // unpacked data CRC and size at the end of encoded data + $tmp = unpack('V2', substr($data, -8)); + $dataCrc = $tmp[1]; + $dataSize = $tmp[2]; + + // finally, call the gzinflate() function + // don't pass $dataSize to gzinflate, see bugs #13135, #14370 + $unpacked = gzinflate(substr($data, $headerLength, -8)); + if (false === $unpacked) { + return PEAR::raiseError('_decodeGzip(): gzinflate() call failed', HTTP_REQUEST_ERROR_GZIP_READ); + } elseif ($dataSize != strlen($unpacked)) { + return PEAR::raiseError('_decodeGzip(): data size check failed', HTTP_REQUEST_ERROR_GZIP_READ); + } elseif ((0xffffffff & $dataCrc) != (0xffffffff & crc32($unpacked))) { + return PEAR::raiseError('_decodeGzip(): data CRC check failed', HTTP_REQUEST_ERROR_GZIP_CRC); + } + if (HTTP_REQUEST_MBSTRING) { + mb_internal_encoding($oldEncoding); + } + return $unpacked; + } +} // End class HTTP_Response +?> diff --git a/lib/PEAR/HTTP/Request/Listener.php b/lib/PEAR/HTTP/Request/Listener.php new file mode 100644 index 0000000000..b4fe444b35 --- /dev/null +++ b/lib/PEAR/HTTP/Request/Listener.php @@ -0,0 +1,106 @@ +<?php +/** + * Listener for HTTP_Request and HTTP_Response objects + * + * PHP versions 4 and 5 + * + * LICENSE: + * + * Copyright (c) 2002-2007, Richard Heyes + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * o Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * o Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * o The names of the authors may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @category HTTP + * @package HTTP_Request + * @author Alexey Borzov <avb@php.net> + * @copyright 2002-2007 Richard Heyes + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Listener.php,v 1.3 2007/05/18 10:33:31 avb Exp $ + * @link http://pear.php.net/package/HTTP_Request/ + */ + +/** + * Listener for HTTP_Request and HTTP_Response objects + * + * This class implements the Observer part of a Subject-Observer + * design pattern. + * + * @category HTTP + * @package HTTP_Request + * @author Alexey Borzov <avb@php.net> + * @version Release: 1.4.4 + */ +class HTTP_Request_Listener +{ + /** + * A listener's identifier + * @var string + */ + var $_id; + + /** + * Constructor, sets the object's identifier + * + * @access public + */ + function HTTP_Request_Listener() + { + $this->_id = md5(uniqid('http_request_', 1)); + } + + + /** + * Returns the listener's identifier + * + * @access public + * @return string + */ + function getId() + { + return $this->_id; + } + + + /** + * This method is called when Listener is notified of an event + * + * @access public + * @param object an object the listener is attached to + * @param string Event name + * @param mixed Additional data + * @abstract + */ + function update(&$subject, $event, $data = null) + { + echo "Notified of event: '$event'\n"; + if (null !== $data) { + echo "Additional data: "; + var_dump($data); + } + } +} +?> diff --git a/lib/PEAR/Mail/mime.php b/lib/PEAR/Mail/mime.php new file mode 100644 index 0000000000..2286920d76 --- /dev/null +++ b/lib/PEAR/Mail/mime.php @@ -0,0 +1,1095 @@ +<?php +/** + * The Mail_Mime class is used to create MIME E-mail messages + * + * The Mail_Mime class provides an OO interface to create MIME + * enabled email messages. This way you can create emails that + * contain plain-text bodies, HTML bodies, attachments, inline + * images and specific headers. + * + * Compatible with PHP versions 4 and 5 + * + * LICENSE: This LICENSE is in the BSD license style. + * Copyright (c) 2002-2003, Richard Heyes <richard@phpguru.org> + * Copyright (c) 2003-2006, PEAR <pear-group@php.net> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - Neither the name of the authors, nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + * + * @category Mail + * @package Mail_Mime + * @author Richard Heyes <richard@phpguru.org> + * @author Tomas V.V. Cox <cox@idecnet.com> + * @author Cipriano Groenendal <cipri@php.net> + * @author Sean Coates <sean@php.net> + * @copyright 2003-2006 PEAR <pear-group@php.net> + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version CVS: $Id: mime.php,v 1.81 2007/06/21 19:08:28 cipri Exp $ + * @link http://pear.php.net/package/Mail_mime + * + * This class is based on HTML Mime Mail class from + * Richard Heyes <richard@phpguru.org> which was based also + * in the mime_mail.class by Tobias Ratschiller <tobias@dnet.it> + * and Sascha Schumann <sascha@schumann.cx> + */ + + +/** + * require PEAR + * + * This package depends on PEAR to raise errors. + */ +require_once 'PEAR.php'; + +/** + * require Mail_mimePart + * + * Mail_mimePart contains the code required to + * create all the different parts a mail can + * consist of. + */ +require_once 'Mail/mimePart.php'; + + +/** + * The Mail_Mime class provides an OO interface to create MIME + * enabled email messages. This way you can create emails that + * contain plain-text bodies, HTML bodies, attachments, inline + * images and specific headers. + * + * @category Mail + * @package Mail_Mime + * @author Richard Heyes <richard@phpguru.org> + * @author Tomas V.V. Cox <cox@idecnet.com> + * @author Cipriano Groenendal <cipri@php.net> + * @author Sean Coates <sean@php.net> + * @copyright 2003-2006 PEAR <pear-group@php.net> + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/Mail_mime + */ +class Mail_mime +{ + /** + * Contains the plain text part of the email + * + * @var string + * @access private + */ + var $_txtbody; + + /** + * Contains the html part of the email + * + * @var string + * @access private + */ + var $_htmlbody; + + /** + * contains the mime encoded text + * + * @var string + * @access private + */ + var $_mime; + + /** + * contains the multipart content + * + * @var string + * @access private + */ + var $_multipart; + + /** + * list of the attached images + * + * @var array + * @access private + */ + var $_html_images = array(); + + /** + * list of the attachements + * + * @var array + * @access private + */ + var $_parts = array(); + + /** + * Build parameters + * + * @var array + * @access private + */ + var $_build_params = array(); + + /** + * Headers for the mail + * + * @var array + * @access private + */ + var $_headers = array(); + + /** + * End Of Line sequence (for serialize) + * + * @var string + * @access private + */ + var $_eol; + + + /** + * Constructor function. + * + * @param string $crlf what type of linebreak to use. + * Defaults to "\r\n" + * + * @return void + * + * @access public + */ + function Mail_mime($crlf = "\r\n") + { + $this->_setEOL($crlf); + $this->_build_params = array( + 'head_encoding' => 'quoted-printable', + 'text_encoding' => '7bit', + 'html_encoding' => 'quoted-printable', + '7bit_wrap' => 998, + 'html_charset' => 'ISO-8859-1', + 'text_charset' => 'ISO-8859-1', + 'head_charset' => 'ISO-8859-1' + ); + } + + /** + * wakeup function called by unserialize. It re-sets the EOL constant + * + * @access private + * @return void + */ + function __wakeup() + { + $this->_setEOL($this->_eol); + } + + + /** + * Accessor function to set the body text. Body text is used if + * it's not an html mail being sent or else is used to fill the + * text/plain part that emails clients who don't support + * html should show. + * + * @param string $data Either a string or + * the file name with the contents + * @param bool $isfile If true the first param should be treated + * as a file name, else as a string (default) + * @param bool $append If true the text or file is appended to + * the existing body, else the old body is + * overwritten + * + * @return mixed true on success or PEAR_Error object + * @access public + */ + function setTXTBody($data, $isfile = false, $append = false) + { + if (!$isfile) { + if (!$append) { + $this->_txtbody = $data; + } else { + $this->_txtbody .= $data; + } + } else { + $cont = $this->_file2str($data); + if (PEAR::isError($cont)) { + return $cont; + } + if (!$append) { + $this->_txtbody = $cont; + } else { + $this->_txtbody .= $cont; + } + } + return true; + } + + /** + * Adds a html part to the mail. + * + * @param string $data either a string or the file name with the + * contents + * @param bool $isfile a flag that determines whether $data is a + * filename, or a string(false, default) + * + * @return bool true on success + * @access public + */ + function setHTMLBody($data, $isfile = false) + { + if (!$isfile) { + $this->_htmlbody = $data; + } else { + $cont = $this->_file2str($data); + if (PEAR::isError($cont)) { + return $cont; + } + $this->_htmlbody = $cont; + } + + return true; + } + + /** + * Adds an image to the list of embedded images. + * + * @param string $file the image file name OR image data itself + * @param string $c_type the content type + * @param string $name the filename of the image. + * Only used if $file is the image data. + * @param bool $isfile whether $file is a filename or not. + * Defaults to true + * + * @return bool true on success + * @access public + */ + function addHTMLImage($file, $c_type='application/octet-stream', + $name = '', $isfile = true) + { + $filedata = ($isfile === true) ? $this->_file2str($file) + : $file; + if ($isfile === true) { + $filename = ($name == '' ? $file : $name); + } else { + $filename = $name; + } + if (PEAR::isError($filedata)) { + return $filedata; + } + $this->_html_images[] = array( + 'body' => $filedata, + 'name' => $filename, + 'c_type' => $c_type, + 'cid' => md5(uniqid(time())) + ); + return true; + } + + /** + * Adds a file to the list of attachments. + * + * @param string $file The file name of the file to attach + * OR the file contents itself + * @param string $c_type The content type + * @param string $name The filename of the attachment + * Only use if $file is the contents + * @param bool $isfile Whether $file is a filename or not + * Defaults to true + * @param string $encoding The type of encoding to use. + * Defaults to base64. + * Possible values: 7bit, 8bit, base64, + * or quoted-printable. + * @param string $disposition The content-disposition of this file + * Defaults to attachment. + * Possible values: attachment, inline. + * @param string $charset The character set used in the filename + * of this attachment. + * @param string $language The language of the attachment + * @param string $location The RFC 2557.4 location of the attachment + * + * @return mixed true on success or PEAR_Error object + * @access public + */ + function addAttachment($file, + $c_type = 'application/octet-stream', + $name = '', + $isfile = true, + $encoding = 'base64', + $disposition = 'attachment', + $charset = '', + $language = '', + $location = '') + { + $filedata = ($isfile === true) ? $this->_file2str($file) + : $file; + if ($isfile === true) { + // Force the name the user supplied, otherwise use $file + $filename = (strlen($name)) ? $name : $file; + } else { + $filename = $name; + } + if (!strlen($filename)) { + $msg = "The supplied filename for the attachment can't be empty"; + $err = PEAR::raiseError($msg); + return $err; + } + $filename = basename($filename); + if (PEAR::isError($filedata)) { + return $filedata; + } + + $this->_parts[] = array( + 'body' => $filedata, + 'name' => $filename, + 'c_type' => $c_type, + 'encoding' => $encoding, + 'charset' => $charset, + 'language' => $language, + 'location' => $location, + 'disposition' => $disposition + ); + return true; + } + + /** + * Get the contents of the given file name as string + * + * @param string $file_name path of file to process + * + * @return string contents of $file_name + * @access private + */ + function &_file2str($file_name) + { + if (!is_readable($file_name)) { + $err = PEAR::raiseError('File is not readable ' . $file_name); + return $err; + } + if (!$fd = fopen($file_name, 'rb')) { + $err = PEAR::raiseError('Could not open ' . $file_name); + return $err; + } + $filesize = filesize($file_name); + if ($filesize == 0) { + $cont = ""; + } else { + if ($magic_quote_setting = get_magic_quotes_runtime()) { + set_magic_quotes_runtime(0); + } + $cont = fread($fd, $filesize); + if ($magic_quote_setting) { + set_magic_quotes_runtime($magic_quote_setting); + } + } + fclose($fd); + return $cont; + } + + /** + * Adds a text subpart to the mimePart object and + * returns it during the build process. + * + * @param mixed &$obj The object to add the part to, or + * null if a new object is to be created. + * @param string $text The text to add. + * + * @return object The text mimePart object + * @access private + */ + function &_addTextPart(&$obj, $text) + { + $params['content_type'] = 'text/plain'; + $params['encoding'] = $this->_build_params['text_encoding']; + $params['charset'] = $this->_build_params['text_charset']; + if (is_object($obj)) { + $ret = $obj->addSubpart($text, $params); + return $ret; + } else { + $ret = new Mail_mimePart($text, $params); + return $ret; + } + } + + /** + * Adds a html subpart to the mimePart object and + * returns it during the build process. + * + * @param mixed &$obj The object to add the part to, or + * null if a new object is to be created. + * + * @return object The html mimePart object + * @access private + */ + function &_addHtmlPart(&$obj) + { + $params['content_type'] = 'text/html'; + $params['encoding'] = $this->_build_params['html_encoding']; + $params['charset'] = $this->_build_params['html_charset']; + if (is_object($obj)) { + $ret = $obj->addSubpart($this->_htmlbody, $params); + return $ret; + } else { + $ret = new Mail_mimePart($this->_htmlbody, $params); + return $ret; + } + } + + /** + * Creates a new mimePart object, using multipart/mixed as + * the initial content-type and returns it during the + * build process. + * + * @return object The multipart/mixed mimePart object + * @access private + */ + function &_addMixedPart() + { + $params = array(); + $params['content_type'] = 'multipart/mixed'; + + //Create empty multipart/mixed Mail_mimePart object to return + $ret = new Mail_mimePart('', $params); + return $ret; + } + + /** + * Adds a multipart/alternative part to a mimePart + * object (or creates one), and returns it during + * the build process. + * + * @param mixed &$obj The object to add the part to, or + * null if a new object is to be created. + * + * @return object The multipart/mixed mimePart object + * @access private + */ + function &_addAlternativePart(&$obj) + { + $params['content_type'] = 'multipart/alternative'; + if (is_object($obj)) { + return $obj->addSubpart('', $params); + } else { + $ret = new Mail_mimePart('', $params); + return $ret; + } + } + + /** + * Adds a multipart/related part to a mimePart + * object (or creates one), and returns it during + * the build process. + * + * @param mixed &$obj The object to add the part to, or + * null if a new object is to be created + * + * @return object The multipart/mixed mimePart object + * @access private + */ + function &_addRelatedPart(&$obj) + { + $params['content_type'] = 'multipart/related'; + if (is_object($obj)) { + return $obj->addSubpart('', $params); + } else { + $ret = new Mail_mimePart('', $params); + return $ret; + } + } + + /** + * Adds an html image subpart to a mimePart object + * and returns it during the build process. + * + * @param object &$obj The mimePart to add the image to + * @param array $value The image information + * + * @return object The image mimePart object + * @access private + */ + function &_addHtmlImagePart(&$obj, $value) + { + $params['content_type'] = $value['c_type']; + $params['encoding'] = 'base64'; + $params['disposition'] = 'inline'; + $params['dfilename'] = $value['name']; + $params['cid'] = $value['cid']; + + $ret = $obj->addSubpart($value['body'], $params); + return $ret; + + } + + /** + * Adds an attachment subpart to a mimePart object + * and returns it during the build process. + * + * @param object &$obj The mimePart to add the image to + * @param array $value The attachment information + * + * @return object The image mimePart object + * @access private + */ + function &_addAttachmentPart(&$obj, $value) + { + $params['dfilename'] = $value['name']; + $params['encoding'] = $value['encoding']; + if ($value['charset']) { + $params['charset'] = $value['charset']; + } + if ($value['language']) { + $params['language'] = $value['language']; + } + if ($value['location']) { + $params['location'] = $value['location']; + } + $params['content_type'] = $value['c_type']; + $params['disposition'] = isset($value['disposition']) ? + $value['disposition'] : 'attachment'; + $ret = $obj->addSubpart($value['body'], $params); + return $ret; + } + + /** + * Returns the complete e-mail, ready to send using an alternative + * mail delivery method. Note that only the mailpart that is made + * with Mail_Mime is created. This means that, + * YOU WILL HAVE NO TO: HEADERS UNLESS YOU SET IT YOURSELF + * using the $xtra_headers parameter! + * + * @param string $separation The separation etween these two parts. + * @param array $build_params The Build parameters passed to the + * &get() function. See &get for more info. + * @param array $xtra_headers The extra headers that should be passed + * to the &headers() function. + * See that function for more info. + * @param bool $overwrite Overwrite the existing headers with new. + * + * @return string The complete e-mail. + * @access public + */ + function getMessage( + $separation = null, + $build_params = null, + $xtra_headers = null, + $overwrite = false + ) + { + if ($separation === null) { + $separation = MAIL_MIME_CRLF; + } + $body = $this->get($build_params); + $head = $this->txtHeaders($xtra_headers, $overwrite); + $mail = $head . $separation . $body; + return $mail; + } + + + /** + * Builds the multipart message from the list ($this->_parts) and + * returns the mime content. + * + * @param array $build_params Build parameters that change the way the email + * is built. Should be associative. Can contain: + * head_encoding - What encoding to use for the headers. + * Options: quoted-printable or base64 + * Default is quoted-printable + * text_encoding - What encoding to use for plain text + * Options: 7bit, 8bit, + * base64, or quoted-printable + * Default is 7bit + * html_encoding - What encoding to use for html + * Options: 7bit, 8bit, + * base64, or quoted-printable + * Default is quoted-printable + * 7bit_wrap - Number of characters before text is + * wrapped in 7bit encoding + * Default is 998 + * html_charset - The character set to use for html. + * Default is iso-8859-1 + * text_charset - The character set to use for text. + * Default is iso-8859-1 + * head_charset - The character set to use for headers. + * Default is iso-8859-1 + * + * @return string The mime content + * @access public + */ + function &get($build_params = null) + { + if (isset($build_params)) { + while (list($key, $value) = each($build_params)) { + $this->_build_params[$key] = $value; + } + } + + if (isset($this->_headers['From'])){ + $domain = @strstr($this->_headers['From'],'@'); + //Bug #11381: Illegal characters in domain ID + $domain = str_replace(array("<", ">", "&", "(", ")", " ", "\"", "'"), "", $domain); + $domain = urlencode($domain); + foreach($this->_html_images as $i => $img){ + $this->_html_images[$i]['cid'] = $this->_html_images[$i]['cid'] . $domain; + } + } + + if (count($this->_html_images) AND isset($this->_htmlbody)) { + foreach ($this->_html_images as $key => $value) { + $regex = array(); + $regex[] = '#(\s)((?i)src|background|href(?-i))\s*=\s*(["\']?)' . + preg_quote($value['name'], '#') . '\3#'; + $regex[] = '#(?i)url(?-i)\(\s*(["\']?)' . + preg_quote($value['name'], '#') . '\1\s*\)#'; + + $rep = array(); + $rep[] = '\1\2=\3cid:' . $value['cid'] .'\3'; + $rep[] = 'url(\1cid:' . $value['cid'] . '\2)'; + + $this->_htmlbody = preg_replace($regex, $rep, $this->_htmlbody); + $this->_html_images[$key]['name'] = + basename($this->_html_images[$key]['name']); + } + } + + $null = null; + $attachments = count($this->_parts) ? true : false; + $html_images = count($this->_html_images) ? true : false; + $html = strlen($this->_htmlbody) ? true : false; + $text = (!$html AND strlen($this->_txtbody)) ? true : false; + + switch (true) { + case $text AND !$attachments: + $message =& $this->_addTextPart($null, $this->_txtbody); + break; + + case !$text AND !$html AND $attachments: + $message =& $this->_addMixedPart(); + for ($i = 0; $i < count($this->_parts); $i++) { + $this->_addAttachmentPart($message, $this->_parts[$i]); + } + break; + + case $text AND $attachments: + $message =& $this->_addMixedPart(); + $this->_addTextPart($message, $this->_txtbody); + for ($i = 0; $i < count($this->_parts); $i++) { + $this->_addAttachmentPart($message, $this->_parts[$i]); + } + break; + + case $html AND !$attachments AND !$html_images: + if (isset($this->_txtbody)) { + $message =& $this->_addAlternativePart($null); + $this->_addTextPart($message, $this->_txtbody); + $this->_addHtmlPart($message); + } else { + $message =& $this->_addHtmlPart($null); + } + break; + + case $html AND !$attachments AND $html_images: + $message =& $this->_addRelatedPart($null); + if (isset($this->_txtbody)) { + $alt =& $this->_addAlternativePart($message); + $this->_addTextPart($alt, $this->_txtbody); + $this->_addHtmlPart($alt); + } else { + $this->_addHtmlPart($message); + } + for ($i = 0; $i < count($this->_html_images); $i++) { + $this->_addHtmlImagePart($message, $this->_html_images[$i]); + } + break; + + case $html AND $attachments AND !$html_images: + $message =& $this->_addMixedPart(); + if (isset($this->_txtbody)) { + $alt =& $this->_addAlternativePart($message); + $this->_addTextPart($alt, $this->_txtbody); + $this->_addHtmlPart($alt); + } else { + $this->_addHtmlPart($message); + } + for ($i = 0; $i < count($this->_parts); $i++) { + $this->_addAttachmentPart($message, $this->_parts[$i]); + } + break; + + case $html AND $attachments AND $html_images: + $message =& $this->_addMixedPart(); + if (isset($this->_txtbody)) { + $alt =& $this->_addAlternativePart($message); + $this->_addTextPart($alt, $this->_txtbody); + $rel =& $this->_addRelatedPart($alt); + } else { + $rel =& $this->_addRelatedPart($message); + } + $this->_addHtmlPart($rel); + for ($i = 0; $i < count($this->_html_images); $i++) { + $this->_addHtmlImagePart($rel, $this->_html_images[$i]); + } + for ($i = 0; $i < count($this->_parts); $i++) { + $this->_addAttachmentPart($message, $this->_parts[$i]); + } + break; + + } + + if (isset($message)) { + $output = $message->encode(); + + $this->_headers = array_merge($this->_headers, + $output['headers']); + $body = $output['body']; + return $body; + + } else { + $ret = false; + return $ret; + } + } + + /** + * Returns an array with the headers needed to prepend to the email + * (MIME-Version and Content-Type). Format of argument is: + * $array['header-name'] = 'header-value'; + * + * @param array $xtra_headers Assoc array with any extra headers. + * Optional. + * @param bool $overwrite Overwrite already existing headers. + * + * @return array Assoc array with the mime headers + * @access public + */ + function &headers($xtra_headers = null, $overwrite = false) + { + // Content-Type header should already be present, + // So just add mime version header + $headers['MIME-Version'] = '1.0'; + if (isset($xtra_headers)) { + $headers = array_merge($headers, $xtra_headers); + } + if ($overwrite) { + $this->_headers = array_merge($this->_headers, $headers); + } else { + $this->_headers = array_merge($headers, $this->_headers); + } + + $encodedHeaders = $this->_encodeHeaders($this->_headers); + return $encodedHeaders; + } + + /** + * Get the text version of the headers + * (usefull if you want to use the PHP mail() function) + * + * @param array $xtra_headers Assoc array with any extra headers. + * Optional. + * @param bool $overwrite Overwrite the existing heaers with new. + * + * @return string Plain text headers + * @access public + */ + function txtHeaders($xtra_headers = null, $overwrite = false) + { + $headers = $this->headers($xtra_headers, $overwrite); + + $ret = ''; + foreach ($headers as $key => $val) { + $ret .= "$key: $val" . MAIL_MIME_CRLF; + } + return $ret; + } + + /** + * Sets the Subject header + * + * @param string $subject String to set the subject to. + * + * @return void + * @access public + */ + function setSubject($subject) + { + $this->_headers['Subject'] = $subject; + } + + /** + * Set an email to the From (the sender) header + * + * @param string $email The email address to use + * + * @return void + * @access public + */ + function setFrom($email) + { + $this->_headers['From'] = $email; + } + + /** + * Add an email to the Cc (carbon copy) header + * (multiple calls to this method are allowed) + * + * @param string $email The email direction to add + * + * @return void + * @access public + */ + function addCc($email) + { + if (isset($this->_headers['Cc'])) { + $this->_headers['Cc'] .= ", $email"; + } else { + $this->_headers['Cc'] = $email; + } + } + + /** + * Add an email to the Bcc (blank carbon copy) header + * (multiple calls to this method are allowed) + * + * @param string $email The email direction to add + * + * @return void + * @access public + */ + function addBcc($email) + { + if (isset($this->_headers['Bcc'])) { + $this->_headers['Bcc'] .= ", $email"; + } else { + $this->_headers['Bcc'] = $email; + } + } + + /** + * Since the PHP send function requires you to specifiy + * recipients (To: header) separately from the other + * headers, the To: header is not properly encoded. + * To fix this, you can use this public method to + * encode your recipients before sending to the send + * function + * + * @param string $recipients A comma-delimited list of recipients + * + * @return string Encoded data + * @access public + */ + function encodeRecipients($recipients) + { + $input = array("To" => $recipients); + $retval = $this->_encodeHeaders($input); + return $retval["To"] ; + } + + /** + * Encodes a header as per RFC2047 + * + * @param array $input The header data to encode + * @param array $params Extra build parameters + * + * @return array Encoded data + * @access private + */ + function _encodeHeaders($input, $params = array()) + { + + $build_params = $this->_build_params; + while (list($key, $value) = each($params)) { + $build_params[$key] = $value; + } + //$hdr_name: Name of the heaer + //$hdr_value: Full line of header value. + //$hdr_value_out: The recombined $hdr_val-atoms, or the encoded string. + + $useIconv = true; + if (isset($build_params['ignore-iconv'])) { + $useIconv = !$build_params['ignore-iconv']; + } + foreach ($input as $hdr_name => $hdr_value) { + if (preg_match('#([\x80-\xFF]){1}#', $hdr_value)) { + if (function_exists('iconv_mime_encode') && $useIconv) { + $imePrefs = array(); + if ($build_params['head_encoding'] == 'base64') { + $imePrefs['scheme'] = 'B'; + } else { + $imePrefs['scheme'] = 'Q'; + } + $imePrefs['input-charset'] = $build_params['head_charset']; + $imePrefs['output-charset'] = $build_params['head_charset']; + $imePrefs['line-length'] = 74; + $imePrefs['line-break-chars'] = "\r\n"; //Specified in RFC2047 + + $hdr_value = iconv_mime_encode($hdr_name, $hdr_value, $imePrefs); + $hdr_value = preg_replace("#^{$hdr_name}\:\ #", "", $hdr_value); + } elseif ($build_params['head_encoding'] == 'base64') { + //Base64 encoding has been selected. + //Base64 encode the entire string + $hdr_value = base64_encode($hdr_value); + + //Generate the header using the specified params and dynamicly + //determine the maximum length of such strings. + //75 is the value specified in the RFC. The first -2 is there so + //the later regexp doesn't break any of the translated chars. + //The -2 on the first line-regexp is to compensate for the ": " + //between the header-name and the header value + $prefix = '=?' . $build_params['head_charset'] . '?B?'; + $suffix = '?='; + $maxLength = 75 - strlen($prefix . $suffix) - 2; + $maxLength1stLine = $maxLength - strlen($hdr_name) - 2; + + //We can cut base4 every 4 characters, so the real max + //we can get must be rounded down. + $maxLength = $maxLength - ($maxLength % 4); + $maxLength1stLine = $maxLength1stLine - ($maxLength1stLine % 4); + + $cutpoint = $maxLength1stLine; + $hdr_value_out = $hdr_value; + $output = ""; + while ($hdr_value_out) { + //Split translated string at every $maxLength + $part = substr($hdr_value_out, 0, $cutpoint); + $hdr_value_out = substr($hdr_value_out, $cutpoint); + $cutpoint = $maxLength; + //RFC 2047 specifies that any split header should + //be seperated by a CRLF SPACE. + if ($output) { + $output .= "\r\n "; + } + $output .= $prefix . $part . $suffix; + } + $hdr_value = $output; + } else { + //quoted-printable encoding has been selected + + //Fix for Bug #10298, Ota Mares <om@viazenetti.de> + //Check if there is a double quote at beginning or end of + //the string to prevent that an open or closing quote gets + //ignored because it is encapsuled by an encoding pre/suffix. + //Remove the double quote and set the specific prefix or + //suffix variable so that we can concat the encoded string and + //the double quotes back together to get the intended string. + $quotePrefix = $quoteSuffix = ''; + if ($hdr_value{0} == '"') { + $hdr_value = substr($hdr_value, 1); + $quotePrefix = '"'; + } + if ($hdr_value{strlen($hdr_value)-1} == '"') { + $hdr_value = substr($hdr_value, 0, -1); + $quoteSuffix = '"'; + } + + //Generate the header using the specified params and dynamicly + //determine the maximum length of such strings. + //75 is the value specified in the RFC. The -2 is there so + //the later regexp doesn't break any of the translated chars. + //The -2 on the first line-regexp is to compensate for the ": " + //between the header-name and the header value + $prefix = '=?' . $build_params['head_charset'] . '?Q?'; + $suffix = '?='; + $maxLength = 75 - strlen($prefix . $suffix) - 2 - 1; + $maxLength1stLine = $maxLength - strlen($hdr_name) - 2; + $maxLength = $maxLength - 1; + + //Replace all special characters used by the encoder. + $search = array('=', '_', '?', ' '); + $replace = array('=3D', '=5F', '=3F', '_'); + $hdr_value = str_replace($search, $replace, $hdr_value); + + //Replace all extended characters (\x80-xFF) with their + //ASCII values. + $hdr_value = preg_replace('#([\x80-\xFF])#e', + '"=" . strtoupper(dechex(ord("\1")))', + $hdr_value); + + //This regexp will break QP-encoded text at every $maxLength + //but will not break any encoded letters. + $reg1st = "|(.{0,$maxLength1stLine}[^\=][^\=])|"; + $reg2nd = "|(.{0,$maxLength}[^\=][^\=])|"; + //Fix for Bug #10298, Ota Mares <om@viazenetti.de> + //Concat the double quotes and encoded string together + $hdr_value = $quotePrefix . $hdr_value . $quoteSuffix; + + + $hdr_value_out = $hdr_value; + $realMax = $maxLength1stLine + strlen($prefix . $suffix); + if (strlen($hdr_value_out) >= $realMax) { + //Begin with the regexp for the first line. + $reg = $reg1st; + $output = ""; + while ($hdr_value_out) { + //Split translated string at every $maxLength + //But make sure not to break any translated chars. + $found = preg_match($reg, $hdr_value_out, $matches); + + //After this first line, we need to use a different + //regexp for the first line. + $reg = $reg2nd; + + //Save the found part and encapsulate it in the + //prefix & suffix. Then remove the part from the + //$hdr_value_out variable. + if ($found) { + $part = $matches[0]; + $len = strlen($matches[0]); + $hdr_value_out = substr($hdr_value_out, $len); + } else { + $part = $hdr_value_out; + $hdr_value_out = ""; + } + + //RFC 2047 specifies that any split header should + //be seperated by a CRLF SPACE + if ($output) { + $output .= "\r\n "; + } + $output .= $prefix . $part . $suffix; + } + $hdr_value_out = $output; + } else { + $hdr_value_out = $prefix . $hdr_value_out . $suffix; + } + $hdr_value = $hdr_value_out; + } + } + $input[$hdr_name] = $hdr_value; + } + return $input; + } + + /** + * Set the object's end-of-line and define the constant if applicable. + * + * @param string $eol End Of Line sequence + * + * @return void + * @access private + */ + function _setEOL($eol) + { + $this->_eol = $eol; + if (!defined('MAIL_MIME_CRLF')) { + define('MAIL_MIME_CRLF', $this->_eol, true); + } + } + + + +} // End of class diff --git a/lib/PEAR/Mail/mimeDecode.php b/lib/PEAR/Mail/mimeDecode.php new file mode 100644 index 0000000000..55aead9027 --- /dev/null +++ b/lib/PEAR/Mail/mimeDecode.php @@ -0,0 +1,849 @@ +<?php +/** + * The Mail_mimeDecode class is used to decode mail/mime messages + * + * This class will parse a raw mime email and return + * the structure. Returned structure is similar to + * that returned by imap_fetchstructure(). + * + * +----------------------------- IMPORTANT ------------------------------+ + * | Usage of this class compared to native php extensions such as | + * | mailparse or imap, is slow and may be feature deficient. If available| + * | you are STRONGLY recommended to use the php extensions. | + * +----------------------------------------------------------------------+ + * + * Compatible with PHP versions 4 and 5 + * + * LICENSE: This LICENSE is in the BSD license style. + * Copyright (c) 2002-2003, Richard Heyes <richard@phpguru.org> + * Copyright (c) 2003-2006, PEAR <pear-group@php.net> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - Neither the name of the authors, nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + * + * @category Mail + * @package Mail_Mime + * @author Richard Heyes <richard@phpguru.org> + * @author George Schlossnagle <george@omniti.com> + * @author Cipriano Groenendal <cipri@php.net> + * @author Sean Coates <sean@php.net> + * @copyright 2003-2006 PEAR <pear-group@php.net> + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version CVS: $Id: mimeDecode.php,v 1.48 2006/12/03 13:43:33 cipri Exp $ + * @link http://pear.php.net/package/Mail_mime + */ + + +/** + * require PEAR + * + * This package depends on PEAR to raise errors. + */ +require_once 'PEAR.php'; + + +/** + * The Mail_mimeDecode class is used to decode mail/mime messages + * + * This class will parse a raw mime email and return the structure. + * Returned structure is similar to that returned by imap_fetchstructure(). + * + * +----------------------------- IMPORTANT ------------------------------+ + * | Usage of this class compared to native php extensions such as | + * | mailparse or imap, is slow and may be feature deficient. If available| + * | you are STRONGLY recommended to use the php extensions. | + * +----------------------------------------------------------------------+ + * + * @category Mail + * @package Mail_Mime + * @author Richard Heyes <richard@phpguru.org> + * @author George Schlossnagle <george@omniti.com> + * @author Cipriano Groenendal <cipri@php.net> + * @author Sean Coates <sean@php.net> + * @copyright 2003-2006 PEAR <pear-group@php.net> + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/Mail_mime + */ +class Mail_mimeDecode extends PEAR +{ + /** + * The raw email to decode + * + * @var string + * @access private + */ + var $_input; + + /** + * The header part of the input + * + * @var string + * @access private + */ + var $_header; + + /** + * The body part of the input + * + * @var string + * @access private + */ + var $_body; + + /** + * If an error occurs, this is used to store the message + * + * @var string + * @access private + */ + var $_error; + + /** + * Flag to determine whether to include bodies in the + * returned object. + * + * @var boolean + * @access private + */ + var $_include_bodies; + + /** + * Flag to determine whether to decode bodies + * + * @var boolean + * @access private + */ + var $_decode_bodies; + + /** + * Flag to determine whether to decode headers + * + * @var boolean + * @access private + */ + var $_decode_headers; + + /** + * Constructor. + * + * Sets up the object, initialise the variables, and splits and + * stores the header and body of the input. + * + * @param string The input to decode + * @access public + */ + function Mail_mimeDecode($input) + { + list($header, $body) = $this->_splitBodyHeader($input); + + $this->_input = $input; + $this->_header = $header; + $this->_body = $body; + $this->_decode_bodies = false; + $this->_include_bodies = true; + } + + /** + * Begins the decoding process. If called statically + * it will create an object and call the decode() method + * of it. + * + * @param array An array of various parameters that determine + * various things: + * include_bodies - Whether to include the body in the returned + * object. + * decode_bodies - Whether to decode the bodies + * of the parts. (Transfer encoding) + * decode_headers - Whether to decode headers + * input - If called statically, this will be treated + * as the input + * @return object Decoded results + * @access public + */ + function decode($params = null) + { + // determine if this method has been called statically + $isStatic = !(isset($this) && get_class($this) == __CLASS__); + + // Have we been called statically? + // If so, create an object and pass details to that. + if ($isStatic AND isset($params['input'])) { + + $obj = new Mail_mimeDecode($params['input']); + $structure = $obj->decode($params); + + // Called statically but no input + } elseif ($isStatic) { + return PEAR::raiseError('Called statically and no input given'); + + // Called via an object + } else { + $this->_include_bodies = isset($params['include_bodies']) ? + $params['include_bodies'] : false; + $this->_decode_bodies = isset($params['decode_bodies']) ? + $params['decode_bodies'] : false; + $this->_decode_headers = isset($params['decode_headers']) ? + $params['decode_headers'] : false; + + $structure = $this->_decode($this->_header, $this->_body); + if ($structure === false) { + $structure = $this->raiseError($this->_error); + } + } + + return $structure; + } + + /** + * Performs the decoding. Decodes the body string passed to it + * If it finds certain content-types it will call itself in a + * recursive fashion + * + * @param string Header section + * @param string Body section + * @return object Results of decoding process + * @access private + */ + function _decode($headers, $body, $default_ctype = 'text/plain') + { + $return = new stdClass; + $return->headers = array(); + $headers = $this->_parseHeaders($headers); + + foreach ($headers as $value) { + if (isset($return->headers[strtolower($value['name'])]) AND !is_array($return->headers[strtolower($value['name'])])) { + $return->headers[strtolower($value['name'])] = array($return->headers[strtolower($value['name'])]); + $return->headers[strtolower($value['name'])][] = $value['value']; + + } elseif (isset($return->headers[strtolower($value['name'])])) { + $return->headers[strtolower($value['name'])][] = $value['value']; + + } else { + $return->headers[strtolower($value['name'])] = $value['value']; + } + } + + reset($headers); + while (list($key, $value) = each($headers)) { + $headers[$key]['name'] = strtolower($headers[$key]['name']); + switch ($headers[$key]['name']) { + + case 'content-type': + $content_type = $this->_parseHeaderValue($headers[$key]['value']); + + if (preg_match('/([0-9a-z+.-]+)\/([0-9a-z+.-]+)/i', $content_type['value'], $regs)) { + $return->ctype_primary = $regs[1]; + $return->ctype_secondary = $regs[2]; + } + + if (isset($content_type['other'])) { + while (list($p_name, $p_value) = each($content_type['other'])) { + $return->ctype_parameters[$p_name] = $p_value; + } + } + break; + + case 'content-disposition': + $content_disposition = $this->_parseHeaderValue($headers[$key]['value']); + $return->disposition = $content_disposition['value']; + if (isset($content_disposition['other'])) { + while (list($p_name, $p_value) = each($content_disposition['other'])) { + $return->d_parameters[$p_name] = $p_value; + } + } + break; + + case 'content-transfer-encoding': + $content_transfer_encoding = $this->_parseHeaderValue($headers[$key]['value']); + break; + } + } + + if (isset($content_type)) { + switch (strtolower($content_type['value'])) { + case 'text/plain': + $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit'; + $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body) : null; + break; + + case 'text/html': + $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit'; + $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body) : null; + break; + + case 'multipart/parallel': + case 'multipart/appledouble': // Appledouble mail + case 'multipart/report': // RFC1892 + case 'multipart/signed': // PGP + case 'multipart/digest': + case 'multipart/alternative': + case 'multipart/related': + case 'multipart/mixed': + if(!isset($content_type['other']['boundary'])){ + $this->_error = 'No boundary found for ' . $content_type['value'] . ' part'; + return false; + } + + $default_ctype = (strtolower($content_type['value']) === 'multipart/digest') ? 'message/rfc822' : 'text/plain'; + + $parts = $this->_boundarySplit($body, $content_type['other']['boundary']); + for ($i = 0; $i < count($parts); $i++) { + list($part_header, $part_body) = $this->_splitBodyHeader($parts[$i]); + $part = $this->_decode($part_header, $part_body, $default_ctype); + if($part === false) + $part = $this->raiseError($this->_error); + $return->parts[] = $part; + } + break; + + case 'message/rfc822': + $obj = new Mail_mimeDecode($body); + $return->parts[] = $obj->decode(array('include_bodies' => $this->_include_bodies, + 'decode_bodies' => $this->_decode_bodies, + 'decode_headers' => $this->_decode_headers)); + unset($obj); + break; + + default: + if(!isset($content_transfer_encoding['value'])) + $content_transfer_encoding['value'] = '7bit'; + $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $content_transfer_encoding['value']) : $body) : null; + break; + } + + } else { + $ctype = explode('/', $default_ctype); + $return->ctype_primary = $ctype[0]; + $return->ctype_secondary = $ctype[1]; + $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body) : $body) : null; + } + + return $return; + } + + /** + * Given the output of the above function, this will return an + * array of references to the parts, indexed by mime number. + * + * @param object $structure The structure to go through + * @param string $mime_number Internal use only. + * @return array Mime numbers + */ + function &getMimeNumbers(&$structure, $no_refs = false, $mime_number = '', $prepend = '') + { + $return = array(); + if (!empty($structure->parts)) { + if ($mime_number != '') { + $structure->mime_id = $prepend . $mime_number; + $return[$prepend . $mime_number] = &$structure; + } + for ($i = 0; $i < count($structure->parts); $i++) { + + + if (!empty($structure->headers['content-type']) AND substr(strtolower($structure->headers['content-type']), 0, 8) == 'message/') { + $prepend = $prepend . $mime_number . '.'; + $_mime_number = ''; + } else { + $_mime_number = ($mime_number == '' ? $i + 1 : sprintf('%s.%s', $mime_number, $i + 1)); + } + + $arr = &Mail_mimeDecode::getMimeNumbers($structure->parts[$i], $no_refs, $_mime_number, $prepend); + foreach ($arr as $key => $val) { + $no_refs ? $return[$key] = '' : $return[$key] = &$arr[$key]; + } + } + } else { + if ($mime_number == '') { + $mime_number = '1'; + } + $structure->mime_id = $prepend . $mime_number; + $no_refs ? $return[$prepend . $mime_number] = '' : $return[$prepend . $mime_number] = &$structure; + } + + return $return; + } + + /** + * Given a string containing a header and body + * section, this function will split them (at the first + * blank line) and return them. + * + * @param string Input to split apart + * @return array Contains header and body section + * @access private + */ + function _splitBodyHeader($input) + { + if (preg_match("/^(.*?)\r?\n\r?\n(.*)/s", $input, $match)) { + return array($match[1], $match[2]); + } + $this->_error = 'Could not split header and body'; + return false; + } + + /** + * Parse headers given in $input and return + * as assoc array. + * + * @param string Headers to parse + * @return array Contains parsed headers + * @access private + */ + function _parseHeaders($input) + { + + if ($input !== '') { + // Unfold the input + $input = preg_replace("/\r?\n/", "\r\n", $input); + $input = preg_replace("/\r\n(\t| )+/", ' ', $input); + $headers = explode("\r\n", trim($input)); + + foreach ($headers as $value) { + $hdr_name = substr($value, 0, $pos = strpos($value, ':')); + $hdr_value = substr($value, $pos+1); + if($hdr_value[0] == ' ') + $hdr_value = substr($hdr_value, 1); + + $return[] = array( + 'name' => $hdr_name, + 'value' => $this->_decode_headers ? $this->_decodeHeader($hdr_value) : $hdr_value + ); + } + } else { + $return = array(); + } + + return $return; + } + + /** + * Function to parse a header value, + * extract first part, and any secondary + * parts (after ;) This function is not as + * robust as it could be. Eg. header comments + * in the wrong place will probably break it. + * + * @param string Header value to parse + * @return array Contains parsed result + * @access private + */ + function _parseHeaderValue($input) + { + + if (($pos = strpos($input, ';')) !== false) { + + $return['value'] = trim(substr($input, 0, $pos)); + $input = trim(substr($input, $pos+1)); + + if (strlen($input) > 0) { + + // This splits on a semi-colon, if there's no preceeding backslash + // Now works with quoted values; had to glue the \; breaks in PHP + // the regex is already bordering on incomprehensible + $splitRegex = '/([^;\'"]*[\'"]([^\'"]*([^\'"]*)*)[\'"][^;\'"]*|([^;]+))(;|$)/'; + preg_match_all($splitRegex, $input, $matches); + $parameters = array(); + for ($i=0; $i<count($matches[0]); $i++) { + $param = $matches[0][$i]; + while (substr($param, -2) == '\;') { + $param .= $matches[0][++$i]; + } + $parameters[] = $param; + } + + for ($i = 0; $i < count($parameters); $i++) { + $param_name = trim(substr($parameters[$i], 0, $pos = strpos($parameters[$i], '=')), "'\";\t\\ "); + $param_value = trim(str_replace('\;', ';', substr($parameters[$i], $pos + 1)), "'\";\t\\ "); + if ($param_value[0] == '"') { + $param_value = substr($param_value, 1, -1); + } + $return['other'][$param_name] = $param_value; + $return['other'][strtolower($param_name)] = $param_value; + } + } + } else { + $return['value'] = trim($input); + } + + return $return; + } + + /** + * This function splits the input based + * on the given boundary + * + * @param string Input to parse + * @return array Contains array of resulting mime parts + * @access private + */ + function _boundarySplit($input, $boundary) + { + $parts = array(); + + $bs_possible = substr($boundary, 2, -2); + $bs_check = '\"' . $bs_possible . '\"'; + + if ($boundary == $bs_check) { + $boundary = $bs_possible; + } + + $tmp = explode('--' . $boundary, $input); + + for ($i = 1; $i < count($tmp) - 1; $i++) { + $parts[] = $tmp[$i]; + } + + return $parts; + } + + /** + * Given a header, this function will decode it + * according to RFC2047. Probably not *exactly* + * conformant, but it does pass all the given + * examples (in RFC2047). + * + * @param string Input header value to decode + * @return string Decoded header value + * @access private + */ + function _decodeHeader($input) + { + // Remove white space between encoded-words + $input = preg_replace('/(=\?[^?]+\?(q|b)\?[^?]*\?=)(\s)+=\?/i', '\1=?', $input); + + // For each encoded-word... + while (preg_match('/(=\?([^?]+)\?(q|b)\?([^?]*)\?=)/i', $input, $matches)) { + + $encoded = $matches[1]; + $charset = $matches[2]; + $encoding = $matches[3]; + $text = $matches[4]; + + switch (strtolower($encoding)) { + case 'b': + $text = base64_decode($text); + break; + + case 'q': + $text = str_replace('_', ' ', $text); + preg_match_all('/=([a-f0-9]{2})/i', $text, $matches); + foreach($matches[1] as $value) + $text = str_replace('='.$value, chr(hexdec($value)), $text); + break; + } + + $input = str_replace($encoded, $text, $input); + } + + return $input; + } + + /** + * Given a body string and an encoding type, + * this function will decode and return it. + * + * @param string Input body to decode + * @param string Encoding type to use. + * @return string Decoded body + * @access private + */ + function _decodeBody($input, $encoding = '7bit') + { + switch (strtolower($encoding)) { + case '7bit': + return $input; + break; + + case 'quoted-printable': + return $this->_quotedPrintableDecode($input); + break; + + case 'base64': + return base64_decode($input); + break; + + default: + return $input; + } + } + + /** + * Given a quoted-printable string, this + * function will decode and return it. + * + * @param string Input body to decode + * @return string Decoded body + * @access private + */ + function _quotedPrintableDecode($input) + { + // Remove soft line breaks + $input = preg_replace("/=\r?\n/", '', $input); + + // Replace encoded characters + $input = preg_replace('/=([a-f0-9]{2})/ie', "chr(hexdec('\\1'))", $input); + + return $input; + } + + /** + * Checks the input for uuencoded files and returns + * an array of them. Can be called statically, eg: + * + * $files =& Mail_mimeDecode::uudecode($some_text); + * + * It will check for the begin 666 ... end syntax + * however and won't just blindly decode whatever you + * pass it. + * + * @param string Input body to look for attahcments in + * @return array Decoded bodies, filenames and permissions + * @access public + * @author Unknown + */ + function &uudecode($input) + { + // Find all uuencoded sections + preg_match_all("/begin ([0-7]{3}) (.+)\r?\n(.+)\r?\nend/Us", $input, $matches); + + for ($j = 0; $j < count($matches[3]); $j++) { + + $str = $matches[3][$j]; + $filename = $matches[2][$j]; + $fileperm = $matches[1][$j]; + + $file = ''; + $str = preg_split("/\r?\n/", trim($str)); + $strlen = count($str); + + for ($i = 0; $i < $strlen; $i++) { + $pos = 1; + $d = 0; + $len=(int)(((ord(substr($str[$i],0,1)) -32) - ' ') & 077); + + while (($d + 3 <= $len) AND ($pos + 4 <= strlen($str[$i]))) { + $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20); + $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20); + $c2 = (ord(substr($str[$i],$pos+2,1)) ^ 0x20); + $c3 = (ord(substr($str[$i],$pos+3,1)) ^ 0x20); + $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4)); + + $file .= chr(((($c1 - ' ') & 077) << 4) | ((($c2 - ' ') & 077) >> 2)); + + $file .= chr(((($c2 - ' ') & 077) << 6) | (($c3 - ' ') & 077)); + + $pos += 4; + $d += 3; + } + + if (($d + 2 <= $len) && ($pos + 3 <= strlen($str[$i]))) { + $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20); + $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20); + $c2 = (ord(substr($str[$i],$pos+2,1)) ^ 0x20); + $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4)); + + $file .= chr(((($c1 - ' ') & 077) << 4) | ((($c2 - ' ') & 077) >> 2)); + + $pos += 3; + $d += 2; + } + + if (($d + 1 <= $len) && ($pos + 2 <= strlen($str[$i]))) { + $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20); + $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20); + $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4)); + + } + } + $files[] = array('filename' => $filename, 'fileperm' => $fileperm, 'filedata' => $file); + } + + return $files; + } + + /** + * getSendArray() returns the arguments required for Mail::send() + * used to build the arguments for a mail::send() call + * + * Usage: + * $mailtext = Full email (for example generated by a template) + * $decoder = new Mail_mimeDecode($mailtext); + * $parts = $decoder->getSendArray(); + * if (!PEAR::isError($parts) { + * list($recipents,$headers,$body) = $parts; + * $mail = Mail::factory('smtp'); + * $mail->send($recipents,$headers,$body); + * } else { + * echo $parts->message; + * } + * @return mixed array of recipeint, headers,body or Pear_Error + * @access public + * @author Alan Knowles <alan@akbkhome.com> + */ + function getSendArray() + { + // prevent warning if this is not set + $this->_decode_headers = FALSE; + $headerlist =$this->_parseHeaders($this->_header); + $to = ""; + if (!$headerlist) { + return $this->raiseError("Message did not contain headers"); + } + foreach($headerlist as $item) { + $header[$item['name']] = $item['value']; + switch (strtolower($item['name'])) { + case "to": + case "cc": + case "bcc": + $to = ",".$item['value']; + default: + break; + } + } + if ($to == "") { + return $this->raiseError("Message did not contain any recipents"); + } + $to = substr($to,1); + return array($to,$header,$this->_body); + } + + /** + * Returns a xml copy of the output of + * Mail_mimeDecode::decode. Pass the output in as the + * argument. This function can be called statically. Eg: + * + * $output = $obj->decode(); + * $xml = Mail_mimeDecode::getXML($output); + * + * The DTD used for this should have been in the package. Or + * alternatively you can get it from cvs, or here: + * http://www.phpguru.org/xmail/xmail.dtd. + * + * @param object Input to convert to xml. This should be the + * output of the Mail_mimeDecode::decode function + * @return string XML version of input + * @access public + */ + function getXML($input) + { + $crlf = "\r\n"; + $output = '<?xml version=\'1.0\'?>' . $crlf . + '<!DOCTYPE email SYSTEM "http://www.phpguru.org/xmail/xmail.dtd">' . $crlf . + '<email>' . $crlf . + Mail_mimeDecode::_getXML($input) . + '</email>'; + + return $output; + } + + /** + * Function that does the actual conversion to xml. Does a single + * mimepart at a time. + * + * @param object Input to convert to xml. This is a mimepart object. + * It may or may not contain subparts. + * @param integer Number of tabs to indent + * @return string XML version of input + * @access private + */ + function _getXML($input, $indent = 1) + { + $htab = "\t"; + $crlf = "\r\n"; + $output = ''; + $headers = @(array)$input->headers; + + foreach ($headers as $hdr_name => $hdr_value) { + + // Multiple headers with this name + if (is_array($headers[$hdr_name])) { + for ($i = 0; $i < count($hdr_value); $i++) { + $output .= Mail_mimeDecode::_getXML_helper($hdr_name, $hdr_value[$i], $indent); + } + + // Only one header of this sort + } else { + $output .= Mail_mimeDecode::_getXML_helper($hdr_name, $hdr_value, $indent); + } + } + + if (!empty($input->parts)) { + for ($i = 0; $i < count($input->parts); $i++) { + $output .= $crlf . str_repeat($htab, $indent) . '<mimepart>' . $crlf . + Mail_mimeDecode::_getXML($input->parts[$i], $indent+1) . + str_repeat($htab, $indent) . '</mimepart>' . $crlf; + } + } elseif (isset($input->body)) { + $output .= $crlf . str_repeat($htab, $indent) . '<body><![CDATA[' . + $input->body . ']]></body>' . $crlf; + } + + return $output; + } + + /** + * Helper function to _getXML(). Returns xml of a header. + * + * @param string Name of header + * @param string Value of header + * @param integer Number of tabs to indent + * @return string XML version of input + * @access private + */ + function _getXML_helper($hdr_name, $hdr_value, $indent) + { + $htab = "\t"; + $crlf = "\r\n"; + $return = ''; + + $new_hdr_value = ($hdr_name != 'received') ? Mail_mimeDecode::_parseHeaderValue($hdr_value) : array('value' => $hdr_value); + $new_hdr_name = str_replace(' ', '-', ucwords(str_replace('-', ' ', $hdr_name))); + + // Sort out any parameters + if (!empty($new_hdr_value['other'])) { + foreach ($new_hdr_value['other'] as $paramname => $paramvalue) { + $params[] = str_repeat($htab, $indent) . $htab . '<parameter>' . $crlf . + str_repeat($htab, $indent) . $htab . $htab . '<paramname>' . htmlspecialchars($paramname) . '</paramname>' . $crlf . + str_repeat($htab, $indent) . $htab . $htab . '<paramvalue>' . htmlspecialchars($paramvalue) . '</paramvalue>' . $crlf . + str_repeat($htab, $indent) . $htab . '</parameter>' . $crlf; + } + + $params = implode('', $params); + } else { + $params = ''; + } + + $return = str_repeat($htab, $indent) . '<header>' . $crlf . + str_repeat($htab, $indent) . $htab . '<headername>' . htmlspecialchars($new_hdr_name) . '</headername>' . $crlf . + str_repeat($htab, $indent) . $htab . '<headervalue>' . htmlspecialchars($new_hdr_value['value']) . '</headervalue>' . $crlf . + $params . + str_repeat($htab, $indent) . '</header>' . $crlf; + + return $return; + } + +} // End of class diff --git a/lib/PEAR/Mail/mimePart.php b/lib/PEAR/Mail/mimePart.php new file mode 100644 index 0000000000..7ad54e5ef5 --- /dev/null +++ b/lib/PEAR/Mail/mimePart.php @@ -0,0 +1,439 @@ +<?php +/** + * The Mail_mimePart class is used to create MIME E-mail messages + * + * This class enables you to manipulate and build a mime email + * from the ground up. The Mail_Mime class is a userfriendly api + * to this class for people who aren't interested in the internals + * of mime mail. + * This class however allows full control over the email. + * + * Compatible with PHP versions 4 and 5 + * + * LICENSE: This LICENSE is in the BSD license style. + * Copyright (c) 2002-2003, Richard Heyes <richard@phpguru.org> + * Copyright (c) 2003-2006, PEAR <pear-group@php.net> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - Neither the name of the authors, nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + * + * @category Mail + * @package Mail_Mime + * @author Richard Heyes <richard@phpguru.org> + * @author Cipriano Groenendal <cipri@php.net> + * @author Sean Coates <sean@php.net> + * @copyright 2003-2006 PEAR <pear-group@php.net> + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version CVS: $Id: mimePart.php,v 1.25 2007/05/14 21:43:08 cipri Exp $ + * @link http://pear.php.net/package/Mail_mime + */ + + +/** + * The Mail_mimePart class is used to create MIME E-mail messages + * + * This class enables you to manipulate and build a mime email + * from the ground up. The Mail_Mime class is a userfriendly api + * to this class for people who aren't interested in the internals + * of mime mail. + * This class however allows full control over the email. + * + * @category Mail + * @package Mail_Mime + * @author Richard Heyes <richard@phpguru.org> + * @author Cipriano Groenendal <cipri@php.net> + * @author Sean Coates <sean@php.net> + * @copyright 2003-2006 PEAR <pear-group@php.net> + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/Mail_mime + */ +class Mail_mimePart { + + /** + * The encoding type of this part + * + * @var string + * @access private + */ + var $_encoding; + + /** + * An array of subparts + * + * @var array + * @access private + */ + var $_subparts; + + /** + * The output of this part after being built + * + * @var string + * @access private + */ + var $_encoded; + + /** + * Headers for this part + * + * @var array + * @access private + */ + var $_headers; + + /** + * The body of this part (not encoded) + * + * @var string + * @access private + */ + var $_body; + + /** + * Constructor. + * + * Sets up the object. + * + * @param $body - The body of the mime part if any. + * @param $params - An associative array of parameters: + * content_type - The content type for this part eg multipart/mixed + * encoding - The encoding to use, 7bit, 8bit, base64, or quoted-printable + * cid - Content ID to apply + * disposition - Content disposition, inline or attachment + * dfilename - Optional filename parameter for content disposition + * description - Content description + * charset - Character set to use + * @access public + */ + function Mail_mimePart($body = '', $params = array()) + { + if (!defined('MAIL_MIMEPART_CRLF')) { + define('MAIL_MIMEPART_CRLF', defined('MAIL_MIME_CRLF') ? MAIL_MIME_CRLF : "\r\n", TRUE); + } + + $contentType = array(); + $contentDisp = array(); + foreach ($params as $key => $value) { + switch ($key) { + case 'content_type': + $contentType['type'] = $value; + //$headers['Content-Type'] = $value . (isset($charset) ? '; charset="' . $charset . '"' : ''); + break; + + case 'encoding': + $this->_encoding = $value; + $headers['Content-Transfer-Encoding'] = $value; + break; + + case 'cid': + $headers['Content-ID'] = '<' . $value . '>'; + break; + + case 'disposition': + $contentDisp['disp'] = $value; + break; + + case 'dfilename': + $contentDisp['filename'] = $value; + $contentType['name'] = $value; + break; + + case 'description': + $headers['Content-Description'] = $value; + break; + + case 'charset': + $contentType['charset'] = $value; + $contentDisp['charset'] = $value; + break; + + case 'language': + $contentType['language'] = $value; + $contentDisp['language'] = $value; + break; + + case 'location': + $headers['Content-Location'] = $value; + break; + + } + } + if (isset($contentType['type'])) { + $headers['Content-Type'] = $contentType['type']; + if (isset($contentType['name'])) { + $headers['Content-Type'] .= ';' . MAIL_MIMEPART_CRLF; + $headers['Content-Type'] .= $this->_buildHeaderParam('name', $contentType['name'], + isset($contentType['charset']) ? $contentType['charset'] : 'US-ASCII', + isset($contentType['language']) ? $contentType['language'] : NULL); + } elseif (isset($contentType['charset'])) { + $headers['Content-Type'] .= "; charset=\"{$contentType['charset']}\""; + } + } + + + if (isset($contentDisp['disp'])) { + $headers['Content-Disposition'] = $contentDisp['disp']; + if (isset($contentDisp['filename'])) { + $headers['Content-Disposition'] .= ';' . MAIL_MIMEPART_CRLF; + $headers['Content-Disposition'] .= $this->_buildHeaderParam('filename', $contentDisp['filename'], + isset($contentDisp['charset']) ? $contentDisp['charset'] : 'US-ASCII', + isset($contentDisp['language']) ? $contentDisp['language'] : NULL); + } + } + + + + + // Default content-type + if (!isset($headers['Content-Type'])) { + $headers['Content-Type'] = 'text/plain'; + } + + //Default encoding + if (!isset($this->_encoding)) { + $this->_encoding = '7bit'; + } + + // Assign stuff to member variables + $this->_encoded = array(); + $this->_headers = $headers; + $this->_body = $body; + } + + /** + * encode() + * + * Encodes and returns the email. Also stores + * it in the encoded member variable + * + * @return An associative array containing two elements, + * body and headers. The headers element is itself + * an indexed array. + * @access public + */ + function encode() + { + $encoded =& $this->_encoded; + + if (count($this->_subparts)) { + srand((double)microtime()*1000000); + $boundary = '=_' . md5(rand() . microtime()); + $this->_headers['Content-Type'] .= ';' . MAIL_MIMEPART_CRLF . "\t" . 'boundary="' . $boundary . '"'; + + // Add body parts to $subparts + for ($i = 0; $i < count($this->_subparts); $i++) { + $headers = array(); + $tmp = $this->_subparts[$i]->encode(); + foreach ($tmp['headers'] as $key => $value) { + $headers[] = $key . ': ' . $value; + } + $subparts[] = implode(MAIL_MIMEPART_CRLF, $headers) . MAIL_MIMEPART_CRLF . MAIL_MIMEPART_CRLF . $tmp['body'] . MAIL_MIMEPART_CRLF; + } + + $encoded['body'] = '--' . $boundary . MAIL_MIMEPART_CRLF . + rtrim(implode('--' . $boundary . MAIL_MIMEPART_CRLF , $subparts), MAIL_MIMEPART_CRLF) . MAIL_MIMEPART_CRLF . + '--' . $boundary.'--' . MAIL_MIMEPART_CRLF; + + } else { + $encoded['body'] = $this->_getEncodedData($this->_body, $this->_encoding); + } + + // Add headers to $encoded + $encoded['headers'] =& $this->_headers; + + return $encoded; + } + + /** + * &addSubPart() + * + * Adds a subpart to current mime part and returns + * a reference to it + * + * @param $body The body of the subpart, if any. + * @param $params The parameters for the subpart, same + * as the $params argument for constructor. + * @return A reference to the part you just added. It is + * crucial if using multipart/* in your subparts that + * you use =& in your script when calling this function, + * otherwise you will not be able to add further subparts. + * @access public + */ + function &addSubPart($body, $params) + { + $this->_subparts[] = new Mail_mimePart($body, $params); + return $this->_subparts[count($this->_subparts) - 1]; + } + + /** + * _getEncodedData() + * + * Returns encoded data based upon encoding passed to it + * + * @param $data The data to encode. + * @param $encoding The encoding type to use, 7bit, base64, + * or quoted-printable. + * @access private + */ + function _getEncodedData($data, $encoding) + { + switch ($encoding) { + case '8bit': + case '7bit': + return $data; + break; + + case 'quoted-printable': + return $this->_quotedPrintableEncode($data); + break; + + case 'base64': + return rtrim(chunk_split(base64_encode($data), 76, MAIL_MIMEPART_CRLF)); + break; + + default: + return $data; + } + } + + /** + * quotedPrintableEncode() + * + * Encodes data to quoted-printable standard. + * + * @param $input The data to encode + * @param $line_max Optional max line length. Should + * not be more than 76 chars + * + * @access private + */ + function _quotedPrintableEncode($input , $line_max = 76) + { + $lines = preg_split("/\r?\n/", $input); + $eol = MAIL_MIMEPART_CRLF; + $escape = '='; + $output = ''; + + while (list(, $line) = each($lines)) { + + $line = preg_split('||', $line, -1, PREG_SPLIT_NO_EMPTY); + $linlen = count($line); + $newline = ''; + + for ($i = 0; $i < $linlen; $i++) { + $char = $line[$i]; + $dec = ord($char); + + if (($dec == 32) AND ($i == ($linlen - 1))) { // convert space at eol only + $char = '=20'; + + } elseif (($dec == 9) AND ($i == ($linlen - 1))) { // convert tab at eol only + $char = '=09'; + } elseif ($dec == 9) { + ; // Do nothing if a tab. + } elseif (($dec == 61) OR ($dec < 32 ) OR ($dec > 126)) { + $char = $escape . strtoupper(sprintf('%02s', dechex($dec))); + } elseif (($dec == 46) AND ($newline == '')) { + //Bug #9722: convert full-stop at bol + //Some Windows servers need this, won't break anything (cipri) + $char = '=2E'; + } + + if ((strlen($newline) + strlen($char)) >= $line_max) { // MAIL_MIMEPART_CRLF is not counted + $output .= $newline . $escape . $eol; // soft line break; " =\r\n" is okay + $newline = ''; + } + $newline .= $char; + } // end of for + $output .= $newline . $eol; + } + $output = substr($output, 0, -1 * strlen($eol)); // Don't want last crlf + return $output; + } + + /** + * _buildHeaderParam() + * + * Encodes the paramater of a header. + * + * @param $name The name of the header-parameter + * @param $value The value of the paramter + * @param $charset The characterset of $value + * @param $language The language used in $value + * @param $maxLength The maximum length of a line. Defauls to 75 + * + * @access private + */ + function _buildHeaderParam($name, $value, $charset=NULL, $language=NULL, $maxLength=75) + { + //If we find chars to encode, or if charset or language + //is not any of the defaults, we need to encode the value. + $shouldEncode = 0; + $secondAsterisk = ''; + if (preg_match('#([\x80-\xFF]){1}#', $value)) { + $shouldEncode = 1; + } elseif ($charset && (strtolower($charset) != 'us-ascii')) { + $shouldEncode = 1; + } elseif ($language && ($language != 'en' && $language != 'en-us')) { + $shouldEncode = 1; + } + if ($shouldEncode) { + $search = array('%', ' ', "\t"); + $replace = array('%25', '%20', '%09'); + $encValue = str_replace($search, $replace, $value); + $encValue = preg_replace('#([\x80-\xFF])#e', '"%" . strtoupper(dechex(ord("\1")))', $encValue); + $value = "$charset'$language'$encValue"; + $secondAsterisk = '*'; + } + $header = " {$name}{$secondAsterisk}=\"{$value}\"; "; + if (strlen($header) <= $maxLength) { + return $header; + } + + $preLength = strlen(" {$name}*0{$secondAsterisk}=\""); + $sufLength = strlen("\";"); + $maxLength = MAX(16, $maxLength - $preLength - $sufLength - 2); + $maxLengthReg = "|(.{0,$maxLength}[^\%][^\%])|"; + + $headers = array(); + $headCount = 0; + while ($value) { + $matches = array(); + $found = preg_match($maxLengthReg, $value, $matches); + if ($found) { + $headers[] = " {$name}*{$headCount}{$secondAsterisk}=\"{$matches[0]}\""; + $value = substr($value, strlen($matches[0])); + } else { + $headers[] = " {$name}*{$headCount}{$secondAsterisk}=\"{$value}\""; + $value = ""; + } + $headCount++; + } + $headers = implode(MAIL_MIMEPART_CRLF, $headers) . ';'; + return $headers; + } +} // End of class diff --git a/lib/PEAR/Mail/xmail.dtd b/lib/PEAR/Mail/xmail.dtd new file mode 100644 index 0000000000..9779796167 --- /dev/null +++ b/lib/PEAR/Mail/xmail.dtd @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> + +<!ENTITY lt "&#60;"> +<!ENTITY gt ">"> +<!ENTITY amp "&#38;"> +<!ENTITY apos "'"> +<!ENTITY quot """> +<!ENTITY crlf " "> + +<!ELEMENT email (header+, (body | mimepart+))> +<!ELEMENT mimepart (header+, (body | mimepart+))> +<!ELEMENT body (#PCDATA)> +<!ELEMENT header ((headername|headervalue|parameter)*)> +<!ELEMENT headername (#PCDATA)> +<!ELEMENT headervalue (#PCDATA)> +<!ELEMENT parameter ((paramname|paramvalue)+)> +<!ELEMENT paramvalue (#PCDATA)> +<!ELEMENT paramname (#PCDATA)> + diff --git a/lib/PEAR/Mail/xmail.xsl b/lib/PEAR/Mail/xmail.xsl new file mode 100644 index 0000000000..0e4ffe680d --- /dev/null +++ b/lib/PEAR/Mail/xmail.xsl @@ -0,0 +1,70 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format"> +<xsl:output method="text" omit-xml-declaration="yes" indent="no"/> +<xsl:preserve-space elements="headervalue paramvalue body"/> + + <xsl:template name="mimepart"> + + <xsl:variable name="boundary"> + <xsl:for-each select="./header"> + <xsl:if test="string(./headername) = 'Content-Type'"> + <xsl:for-each select="./parameter"> + <xsl:if test="string(./paramname) = 'boundary'"> + <xsl:value-of select="paramvalue"/> + </xsl:if> + </xsl:for-each> + </xsl:if> + </xsl:for-each> + </xsl:variable> + + <xsl:for-each select="header"> + + <xsl:value-of select="headername"/> + <xsl:text>: </xsl:text> + <xsl:value-of select="headervalue"/> + + <xsl:if test="count(./parameter) = 0"> + <xsl:text> </xsl:text> + </xsl:if> + + <xsl:for-each select="parameter"> + <xsl:text>; </xsl:text> + <xsl:value-of select="paramname"/> + <xsl:text>="</xsl:text> + <xsl:value-of select="paramvalue"/> + <xsl:text>"</xsl:text> + </xsl:for-each> + + <xsl:if test="count(./parameter) > 0"> + <xsl:text> </xsl:text> + </xsl:if> + + </xsl:for-each> + + <xsl:text> </xsl:text> + + <!-- Which to do, print a body or process subparts? --> + <xsl:choose> + <xsl:when test="count(./mimepart) = 0"> + <xsl:value-of select="body"/> + <xsl:text> </xsl:text> + </xsl:when> + + <xsl:otherwise> + <xsl:for-each select="mimepart"> + <xsl:text>--</xsl:text><xsl:value-of select="$boundary"/><xsl:text> </xsl:text> + <xsl:call-template name="mimepart"/> + </xsl:for-each> + + <xsl:text>--</xsl:text><xsl:value-of select="$boundary"/><xsl:text>-- </xsl:text> + + </xsl:otherwise> + </xsl:choose> + </xsl:template> + +<!-- This is where the stylesheet really starts, matching the top level email element --> + <xsl:template match="email"> + <xsl:call-template name="mimepart"/> + </xsl:template> + +</xsl:stylesheet> \ No newline at end of file diff --git a/lib/PEAR/Net/Socket.php b/lib/PEAR/Net/Socket.php new file mode 100644 index 0000000000..73bb4dd11f --- /dev/null +++ b/lib/PEAR/Net/Socket.php @@ -0,0 +1,592 @@ +<?php +// +// +----------------------------------------------------------------------+ +// | PHP Version 4 | +// +----------------------------------------------------------------------+ +// | Copyright (c) 1997-2003 The PHP Group | +// +----------------------------------------------------------------------+ +// | This source file is subject to version 2.0 of the PHP license, | +// | that is bundled with this package in the file LICENSE, and is | +// | available at through the world-wide-web at | +// | http://www.php.net/license/2_02.txt. | +// | If you did not receive a copy of the PHP license and are unable to | +// | obtain it through the world-wide-web, please send a note to | +// | license@php.net so we can mail you a copy immediately. | +// +----------------------------------------------------------------------+ +// | Authors: Stig Bakken <ssb@php.net> | +// | Chuck Hagenbuch <chuck@horde.org> | +// +----------------------------------------------------------------------+ +// +// $Id: Socket.php,v 1.38 2008/02/15 18:24:17 chagenbu Exp $ + +require_once 'PEAR.php'; + +define('NET_SOCKET_READ', 1); +define('NET_SOCKET_WRITE', 2); +define('NET_SOCKET_ERROR', 4); + +/** + * Generalized Socket class. + * + * @version 1.1 + * @author Stig Bakken <ssb@php.net> + * @author Chuck Hagenbuch <chuck@horde.org> + */ +class Net_Socket extends PEAR { + + /** + * Socket file pointer. + * @var resource $fp + */ + var $fp = null; + + /** + * Whether the socket is blocking. Defaults to true. + * @var boolean $blocking + */ + var $blocking = true; + + /** + * Whether the socket is persistent. Defaults to false. + * @var boolean $persistent + */ + var $persistent = false; + + /** + * The IP address to connect to. + * @var string $addr + */ + var $addr = ''; + + /** + * The port number to connect to. + * @var integer $port + */ + var $port = 0; + + /** + * Number of seconds to wait on socket connections before assuming + * there's no more data. Defaults to no timeout. + * @var integer $timeout + */ + var $timeout = false; + + /** + * Number of bytes to read at a time in readLine() and + * readAll(). Defaults to 2048. + * @var integer $lineLength + */ + var $lineLength = 2048; + + /** + * Connect to the specified port. If called when the socket is + * already connected, it disconnects and connects again. + * + * @param string $addr IP address or host name. + * @param integer $port TCP port number. + * @param boolean $persistent (optional) Whether the connection is + * persistent (kept open between requests + * by the web server). + * @param integer $timeout (optional) How long to wait for data. + * @param array $options See options for stream_context_create. + * + * @access public + * + * @return boolean | PEAR_Error True on success or a PEAR_Error on failure. + */ + function connect($addr, $port = 0, $persistent = null, $timeout = null, $options = null) + { + if (is_resource($this->fp)) { + @fclose($this->fp); + $this->fp = null; + } + + if (!$addr) { + return $this->raiseError('$addr cannot be empty'); + } elseif (strspn($addr, '.0123456789') == strlen($addr) || + strstr($addr, '/') !== false) { + $this->addr = $addr; + } else { + $this->addr = @gethostbyname($addr); + } + + $this->port = $port % 65536; + + if ($persistent !== null) { + $this->persistent = $persistent; + } + + if ($timeout !== null) { + $this->timeout = $timeout; + } + + $openfunc = $this->persistent ? 'pfsockopen' : 'fsockopen'; + $errno = 0; + $errstr = ''; + $old_track_errors = @ini_set('track_errors', 1); + if ($options && function_exists('stream_context_create')) { + if ($this->timeout) { + $timeout = $this->timeout; + } else { + $timeout = 0; + } + $context = stream_context_create($options); + + // Since PHP 5 fsockopen doesn't allow context specification + if (function_exists('stream_socket_client')) { + $flags = $this->persistent ? STREAM_CLIENT_PERSISTENT : STREAM_CLIENT_CONNECT; + $addr = $this->addr . ':' . $this->port; + $fp = stream_socket_client($addr, $errno, $errstr, $timeout, $flags, $context); + } else { + $fp = @$openfunc($this->addr, $this->port, $errno, $errstr, $timeout, $context); + } + } else { + if ($this->timeout) { + $fp = @$openfunc($this->addr, $this->port, $errno, $errstr, $this->timeout); + } else { + $fp = @$openfunc($this->addr, $this->port, $errno, $errstr); + } + } + + if (!$fp) { + if ($errno == 0 && isset($php_errormsg)) { + $errstr = $php_errormsg; + } + @ini_set('track_errors', $old_track_errors); + return $this->raiseError($errstr, $errno); + } + + @ini_set('track_errors', $old_track_errors); + $this->fp = $fp; + + return $this->setBlocking($this->blocking); + } + + /** + * Disconnects from the peer, closes the socket. + * + * @access public + * @return mixed true on success or a PEAR_Error instance otherwise + */ + function disconnect() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + @fclose($this->fp); + $this->fp = null; + return true; + } + + /** + * Find out if the socket is in blocking mode. + * + * @access public + * @return boolean The current blocking mode. + */ + function isBlocking() + { + return $this->blocking; + } + + /** + * Sets whether the socket connection should be blocking or + * not. A read call to a non-blocking socket will return immediately + * if there is no data available, whereas it will block until there + * is data for blocking sockets. + * + * @param boolean $mode True for blocking sockets, false for nonblocking. + * @access public + * @return mixed true on success or a PEAR_Error instance otherwise + */ + function setBlocking($mode) + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $this->blocking = $mode; + socket_set_blocking($this->fp, $this->blocking); + return true; + } + + /** + * Sets the timeout value on socket descriptor, + * expressed in the sum of seconds and microseconds + * + * @param integer $seconds Seconds. + * @param integer $microseconds Microseconds. + * @access public + * @return mixed true on success or a PEAR_Error instance otherwise + */ + function setTimeout($seconds, $microseconds) + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + return socket_set_timeout($this->fp, $seconds, $microseconds); + } + + /** + * Sets the file buffering size on the stream. + * See php's stream_set_write_buffer for more information. + * + * @param integer $size Write buffer size. + * @access public + * @return mixed on success or an PEAR_Error object otherwise + */ + function setWriteBuffer($size) + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $returned = stream_set_write_buffer($this->fp, $size); + if ($returned == 0) { + return true; + } + return $this->raiseError('Cannot set write buffer.'); + } + + /** + * Returns information about an existing socket resource. + * Currently returns four entries in the result array: + * + * <p> + * timed_out (bool) - The socket timed out waiting for data<br> + * blocked (bool) - The socket was blocked<br> + * eof (bool) - Indicates EOF event<br> + * unread_bytes (int) - Number of bytes left in the socket buffer<br> + * </p> + * + * @access public + * @return mixed Array containing information about existing socket resource or a PEAR_Error instance otherwise + */ + function getStatus() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + return socket_get_status($this->fp); + } + + /** + * Get a specified line of data + * + * @access public + * @return $size bytes of data from the socket, or a PEAR_Error if + * not connected. + */ + function gets($size) + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + return @fgets($this->fp, $size); + } + + /** + * Read a specified amount of data. This is guaranteed to return, + * and has the added benefit of getting everything in one fread() + * chunk; if you know the size of the data you're getting + * beforehand, this is definitely the way to go. + * + * @param integer $size The number of bytes to read from the socket. + * @access public + * @return $size bytes of data from the socket, or a PEAR_Error if + * not connected. + */ + function read($size) + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + return @fread($this->fp, $size); + } + + /** + * Write a specified amount of data. + * + * @param string $data Data to write. + * @param integer $blocksize Amount of data to write at once. + * NULL means all at once. + * + * @access public + * @return mixed If the socket is not connected, returns an instance of PEAR_Error + * If the write succeeds, returns the number of bytes written + * If the write fails, returns false. + */ + function write($data, $blocksize = null) + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + if (is_null($blocksize) && !OS_WINDOWS) { + return @fwrite($this->fp, $data); + } else { + if (is_null($blocksize)) { + $blocksize = 1024; + } + + $pos = 0; + $size = strlen($data); + while ($pos < $size) { + $written = @fwrite($this->fp, substr($data, $pos, $blocksize)); + if ($written === false) { + return false; + } + $pos += $written; + } + + return $pos; + } + } + + /** + * Write a line of data to the socket, followed by a trailing "\r\n". + * + * @access public + * @return mixed fputs result, or an error + */ + function writeLine($data) + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + return fwrite($this->fp, $data . "\r\n"); + } + + /** + * Tests for end-of-file on a socket descriptor. + * + * Also returns true if the socket is disconnected. + * + * @access public + * @return bool + */ + function eof() + { + return (!is_resource($this->fp) || feof($this->fp)); + } + + /** + * Reads a byte of data + * + * @access public + * @return 1 byte of data from the socket, or a PEAR_Error if + * not connected. + */ + function readByte() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + return ord(@fread($this->fp, 1)); + } + + /** + * Reads a word of data + * + * @access public + * @return 1 word of data from the socket, or a PEAR_Error if + * not connected. + */ + function readWord() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $buf = @fread($this->fp, 2); + return (ord($buf[0]) + (ord($buf[1]) << 8)); + } + + /** + * Reads an int of data + * + * @access public + * @return integer 1 int of data from the socket, or a PEAR_Error if + * not connected. + */ + function readInt() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $buf = @fread($this->fp, 4); + return (ord($buf[0]) + (ord($buf[1]) << 8) + + (ord($buf[2]) << 16) + (ord($buf[3]) << 24)); + } + + /** + * Reads a zero-terminated string of data + * + * @access public + * @return string, or a PEAR_Error if + * not connected. + */ + function readString() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $string = ''; + while (($char = @fread($this->fp, 1)) != "\x00") { + $string .= $char; + } + return $string; + } + + /** + * Reads an IP Address and returns it in a dot formatted string + * + * @access public + * @return Dot formatted string, or a PEAR_Error if + * not connected. + */ + function readIPAddress() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $buf = @fread($this->fp, 4); + return sprintf('%d.%d.%d.%d', ord($buf[0]), ord($buf[1]), + ord($buf[2]), ord($buf[3])); + } + + /** + * Read until either the end of the socket or a newline, whichever + * comes first. Strips the trailing newline from the returned data. + * + * @access public + * @return All available data up to a newline, without that + * newline, or until the end of the socket, or a PEAR_Error if + * not connected. + */ + function readLine() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $line = ''; + $timeout = time() + $this->timeout; + while (!feof($this->fp) && (!$this->timeout || time() < $timeout)) { + $line .= @fgets($this->fp, $this->lineLength); + if (substr($line, -1) == "\n") { + return rtrim($line, "\r\n"); + } + } + return $line; + } + + /** + * Read until the socket closes, or until there is no more data in + * the inner PHP buffer. If the inner buffer is empty, in blocking + * mode we wait for at least 1 byte of data. Therefore, in + * blocking mode, if there is no data at all to be read, this + * function will never exit (unless the socket is closed on the + * remote end). + * + * @access public + * + * @return string All data until the socket closes, or a PEAR_Error if + * not connected. + */ + function readAll() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $data = ''; + while (!feof($this->fp)) { + $data .= @fread($this->fp, $this->lineLength); + } + return $data; + } + + /** + * Runs the equivalent of the select() system call on the socket + * with a timeout specified by tv_sec and tv_usec. + * + * @param integer $state Which of read/write/error to check for. + * @param integer $tv_sec Number of seconds for timeout. + * @param integer $tv_usec Number of microseconds for timeout. + * + * @access public + * @return False if select fails, integer describing which of read/write/error + * are ready, or PEAR_Error if not connected. + */ + function select($state, $tv_sec, $tv_usec = 0) + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $read = null; + $write = null; + $except = null; + if ($state & NET_SOCKET_READ) { + $read[] = $this->fp; + } + if ($state & NET_SOCKET_WRITE) { + $write[] = $this->fp; + } + if ($state & NET_SOCKET_ERROR) { + $except[] = $this->fp; + } + if (false === ($sr = stream_select($read, $write, $except, $tv_sec, $tv_usec))) { + return false; + } + + $result = 0; + if (count($read)) { + $result |= NET_SOCKET_READ; + } + if (count($write)) { + $result |= NET_SOCKET_WRITE; + } + if (count($except)) { + $result |= NET_SOCKET_ERROR; + } + return $result; + } + + /** + * Turns encryption on/off on a connected socket. + * + * @param bool $enabled Set this parameter to true to enable encryption + * and false to disable encryption. + * @param integer $type Type of encryption. See + * http://se.php.net/manual/en/function.stream-socket-enable-crypto.php for values. + * + * @access public + * @return false on error, true on success and 0 if there isn't enough data and the + * user should try again (non-blocking sockets only). A PEAR_Error object + * is returned if the socket is not connected + */ + function enableCrypto($enabled, $type) + { + if (version_compare(phpversion(), "5.1.0", ">=")) { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + return @stream_socket_enable_crypto($this->fp, $enabled, $type); + } else { + return $this->raiseError('Net_Socket::enableCrypto() requires php version >= 5.1.0'); + } + } + +} diff --git a/lib/PEAR/Net/URL.php b/lib/PEAR/Net/URL.php new file mode 100644 index 0000000000..0c964a9f42 --- /dev/null +++ b/lib/PEAR/Net/URL.php @@ -0,0 +1,485 @@ +<?php +// +-----------------------------------------------------------------------+ +// | Copyright (c) 2002-2004, Richard Heyes | +// | All rights reserved. | +// | | +// | Redistribution and use in source and binary forms, with or without | +// | modification, are permitted provided that the following conditions | +// | are met: | +// | | +// | o Redistributions of source code must retain the above copyright | +// | notice, this list of conditions and the following disclaimer. | +// | o Redistributions in binary form must reproduce the above copyright | +// | notice, this list of conditions and the following disclaimer in the | +// | documentation and/or other materials provided with the distribution.| +// | o The names of the authors may not be used to endorse or promote | +// | products derived from this software without specific prior written | +// | permission. | +// | | +// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | +// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | +// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | +// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | +// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | +// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | +// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | +// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | +// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | +// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | +// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | +// | | +// +-----------------------------------------------------------------------+ +// | Author: Richard Heyes <richard at php net> | +// +-----------------------------------------------------------------------+ +// +// $Id: URL.php,v 1.49 2007/06/28 14:43:07 davidc Exp $ +// +// Net_URL Class + + +class Net_URL +{ + var $options = array('encode_query_keys' => false); + /** + * Full url + * @var string + */ + var $url; + + /** + * Protocol + * @var string + */ + var $protocol; + + /** + * Username + * @var string + */ + var $username; + + /** + * Password + * @var string + */ + var $password; + + /** + * Host + * @var string + */ + var $host; + + /** + * Port + * @var integer + */ + var $port; + + /** + * Path + * @var string + */ + var $path; + + /** + * Query string + * @var array + */ + var $querystring; + + /** + * Anchor + * @var string + */ + var $anchor; + + /** + * Whether to use [] + * @var bool + */ + var $useBrackets; + + /** + * PHP4 Constructor + * + * @see __construct() + */ +// function Net_URL($url = null, $useBrackets = true) +// { +// $this->__construct($url, $useBrackets); +// } + + /** + * PHP5 Constructor + * + * Parses the given url and stores the various parts + * Defaults are used in certain cases + * + * @param string $url Optional URL + * @param bool $useBrackets Whether to use square brackets when + * multiple querystrings with the same name + * exist + */ + function __construct($url = null, $useBrackets = true) + { + $this->url = $url; + $this->useBrackets = $useBrackets; + + $this->initialize(); + } + + function initialize() + { + $HTTP_SERVER_VARS = !empty($_SERVER) ? $_SERVER : $GLOBALS['HTTP_SERVER_VARS']; + + $this->user = ''; + $this->pass = ''; + $this->host = ''; + $this->port = 80; + $this->path = ''; + $this->querystring = array(); + $this->anchor = ''; + + // Only use defaults if not an absolute URL given + if (!preg_match('/^[a-z0-9]+:\/\//i', $this->url)) { + $this->protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' ? 'https' : 'http'); + + /** + * Figure out host/port + */ + if (!empty($HTTP_SERVER_VARS['HTTP_HOST']) && + preg_match('/^(.*)(:([0-9]+))?$/U', $HTTP_SERVER_VARS['HTTP_HOST'], $matches)) + { + $host = $matches[1]; + if (!empty($matches[3])) { + $port = $matches[3]; + } else { + $port = $this->getStandardPort($this->protocol); + } + } + + $this->user = ''; + $this->pass = ''; + $this->host = !empty($host) ? $host : (isset($HTTP_SERVER_VARS['SERVER_NAME']) ? $HTTP_SERVER_VARS['SERVER_NAME'] : 'localhost'); + $this->port = !empty($port) ? $port : (isset($HTTP_SERVER_VARS['SERVER_PORT']) ? $HTTP_SERVER_VARS['SERVER_PORT'] : $this->getStandardPort($this->protocol)); + $this->path = !empty($HTTP_SERVER_VARS['PHP_SELF']) ? $HTTP_SERVER_VARS['PHP_SELF'] : '/'; + $this->querystring = isset($HTTP_SERVER_VARS['QUERY_STRING']) ? $this->_parseRawQuerystring($HTTP_SERVER_VARS['QUERY_STRING']) : null; + $this->anchor = ''; + } + + // Parse the url and store the various parts + if (!empty($this->url)) { + $urlinfo = parse_url($this->url); + + // Default querystring + $this->querystring = array(); + + foreach ($urlinfo as $key => $value) { + switch ($key) { + case 'scheme': + $this->protocol = $value; + $this->port = $this->getStandardPort($value); + break; + + case 'user': + case 'pass': + case 'host': + case 'port': + $this->$key = $value; + break; + + case 'path': + if ($value{0} == '/') { + $this->path = $value; + } else { + $path = dirname($this->path) == DIRECTORY_SEPARATOR ? '' : dirname($this->path); + $this->path = sprintf('%s/%s', $path, $value); + } + break; + + case 'query': + $this->querystring = $this->_parseRawQueryString($value); + break; + + case 'fragment': + $this->anchor = $value; + break; + } + } + } + } + /** + * Returns full url + * + * @return string Full url + * @access public + */ + function getURL() + { + $querystring = $this->getQueryString(); + + $this->url = $this->protocol . '://' + . $this->user . (!empty($this->pass) ? ':' : '') + . $this->pass . (!empty($this->user) ? '@' : '') + . $this->host . ($this->port == $this->getStandardPort($this->protocol) ? '' : ':' . $this->port) + . $this->path + . (!empty($querystring) ? '?' . $querystring : '') + . (!empty($this->anchor) ? '#' . $this->anchor : ''); + + return $this->url; + } + + /** + * Adds or updates a querystring item (URL parameter). + * Automatically encodes parameters with rawurlencode() if $preencoded + * is false. + * You can pass an array to $value, it gets mapped via [] in the URL if + * $this->useBrackets is activated. + * + * @param string $name Name of item + * @param string $value Value of item + * @param bool $preencoded Whether value is urlencoded or not, default = not + * @access public + */ + function addQueryString($name, $value, $preencoded = false) + { + if ($this->getOption('encode_query_keys')) { + $name = rawurlencode($name); + } + + if ($preencoded) { + $this->querystring[$name] = $value; + } else { + $this->querystring[$name] = is_array($value) ? array_map('rawurlencode', $value): rawurlencode($value); + } + } + + /** + * Removes a querystring item + * + * @param string $name Name of item + * @access public + */ + function removeQueryString($name) + { + if ($this->getOption('encode_query_keys')) { + $name = rawurlencode($name); + } + + if (isset($this->querystring[$name])) { + unset($this->querystring[$name]); + } + } + + /** + * Sets the querystring to literally what you supply + * + * @param string $querystring The querystring data. Should be of the format foo=bar&x=y etc + * @access public + */ + function addRawQueryString($querystring) + { + $this->querystring = $this->_parseRawQueryString($querystring); + } + + /** + * Returns flat querystring + * + * @return string Querystring + * @access public + */ + function getQueryString() + { + if (!empty($this->querystring)) { + foreach ($this->querystring as $name => $value) { + // Encode var name + $name = rawurlencode($name); + + if (is_array($value)) { + foreach ($value as $k => $v) { + $querystring[] = $this->useBrackets ? sprintf('%s[%s]=%s', $name, $k, $v) : ($name . '=' . $v); + } + } elseif (!is_null($value)) { + $querystring[] = $name . '=' . $value; + } else { + $querystring[] = $name; + } + } + $querystring = implode(ini_get('arg_separator.output'), $querystring); + } else { + $querystring = ''; + } + + return $querystring; + } + + /** + * Parses raw querystring and returns an array of it + * + * @param string $querystring The querystring to parse + * @return array An array of the querystring data + * @access private + */ + function _parseRawQuerystring($querystring) + { + $parts = preg_split('/[' . preg_quote(ini_get('arg_separator.input'), '/') . ']/', $querystring, -1, PREG_SPLIT_NO_EMPTY); + $return = array(); + + foreach ($parts as $part) { + if (strpos($part, '=') !== false) { + $value = substr($part, strpos($part, '=') + 1); + $key = substr($part, 0, strpos($part, '=')); + } else { + $value = null; + $key = $part; + } + + if (!$this->getOption('encode_query_keys')) { + $key = rawurldecode($key); + } + + if (preg_match('#^(.*)\[([0-9a-z_-]*)\]#i', $key, $matches)) { + $key = $matches[1]; + $idx = $matches[2]; + + // Ensure is an array + if (empty($return[$key]) || !is_array($return[$key])) { + $return[$key] = array(); + } + + // Add data + if ($idx === '') { + $return[$key][] = $value; + } else { + $return[$key][$idx] = $value; + } + } elseif (!$this->useBrackets AND !empty($return[$key])) { + $return[$key] = (array)$return[$key]; + $return[$key][] = $value; + } else { + $return[$key] = $value; + } + } + + return $return; + } + + /** + * Resolves //, ../ and ./ from a path and returns + * the result. Eg: + * + * /foo/bar/../boo.php => /foo/boo.php + * /foo/bar/../../boo.php => /boo.php + * /foo/bar/.././/boo.php => /foo/boo.php + * + * This method can also be called statically. + * + * @param string $path URL path to resolve + * @return string The result + */ + function resolvePath($path) + { + $path = explode('/', str_replace('//', '/', $path)); + + for ($i=0; $i<count($path); $i++) { + if ($path[$i] == '.') { + unset($path[$i]); + $path = array_values($path); + $i--; + + } elseif ($path[$i] == '..' AND ($i > 1 OR ($i == 1 AND $path[0] != '') ) ) { + unset($path[$i]); + unset($path[$i-1]); + $path = array_values($path); + $i -= 2; + + } elseif ($path[$i] == '..' AND $i == 1 AND $path[0] == '') { + unset($path[$i]); + $path = array_values($path); + $i--; + + } else { + continue; + } + } + + return implode('/', $path); + } + + /** + * Returns the standard port number for a protocol + * + * @param string $scheme The protocol to lookup + * @return integer Port number or NULL if no scheme matches + * + * @author Philippe Jausions <Philippe.Jausions@11abacus.com> + */ + function getStandardPort($scheme) + { + switch (strtolower($scheme)) { + case 'http': return 80; + case 'https': return 443; + case 'ftp': return 21; + case 'imap': return 143; + case 'imaps': return 993; + case 'pop3': return 110; + case 'pop3s': return 995; + default: return null; + } + } + + /** + * Forces the URL to a particular protocol + * + * @param string $protocol Protocol to force the URL to + * @param integer $port Optional port (standard port is used by default) + */ + function setProtocol($protocol, $port = null) + { + $this->protocol = $protocol; + $this->port = is_null($port) ? $this->getStandardPort($protocol) : $port; + } + + /** + * Set an option + * + * This function set an option + * to be used thorough the script. + * + * @access public + * @param string $optionName The optionname to set + * @param string $value The value of this option. + */ + function setOption($optionName, $value) + { + if (!array_key_exists($optionName, $this->options)) { + return false; + } + + $this->options[$optionName] = $value; + $this->initialize(); + } + + /** + * Get an option + * + * This function gets an option + * from the $this->options array + * and return it's value. + * + * @access public + * @param string $opionName The name of the option to retrieve + * @see $this->options + */ + function getOption($optionName) + { + if (!isset($this->options[$optionName])) { + return false; + } + + return $this->options[$optionName]; + } + +} +?> diff --git a/lib/PEAR/PEAR.php b/lib/PEAR/PEAR.php new file mode 100644 index 0000000000..b4633bf3b8 --- /dev/null +++ b/lib/PEAR/PEAR.php @@ -0,0 +1,1118 @@ +<?php +/** + * PEAR, the PHP Extension and Application Repository + * + * PEAR class and PEAR_Error class + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.0 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_0.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category pear + * @package PEAR + * @author Sterling Hughes <sterling@php.net> + * @author Stig Bakken <ssb@php.net> + * @author Tomas V.V.Cox <cox@idecnet.com> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2008 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: PEAR.php,v 1.104 2008/01/03 20:26:34 cellog Exp $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/**#@+ + * ERROR constants + */ +define('PEAR_ERROR_RETURN', 1); +define('PEAR_ERROR_PRINT', 2); +define('PEAR_ERROR_TRIGGER', 4); +define('PEAR_ERROR_DIE', 8); +define('PEAR_ERROR_CALLBACK', 16); +/** + * WARNING: obsolete + * @deprecated + */ +define('PEAR_ERROR_EXCEPTION', 32); +/**#@-*/ +define('PEAR_ZE2', (function_exists('version_compare') && + version_compare(zend_version(), "2-dev", "ge"))); + +if (substr(PHP_OS, 0, 3) == 'WIN') { + define('OS_WINDOWS', true); + define('OS_UNIX', false); + define('PEAR_OS', 'Windows'); +} else { + define('OS_WINDOWS', false); + define('OS_UNIX', true); + define('PEAR_OS', 'Unix'); // blatant assumption +} + +// instant backwards compatibility +if (!defined('PATH_SEPARATOR')) { + if (OS_WINDOWS) { + define('PATH_SEPARATOR', ';'); + } else { + define('PATH_SEPARATOR', ':'); + } +} + +$GLOBALS['_PEAR_default_error_mode'] = PEAR_ERROR_RETURN; +$GLOBALS['_PEAR_default_error_options'] = E_USER_NOTICE; +$GLOBALS['_PEAR_destructor_object_list'] = array(); +$GLOBALS['_PEAR_shutdown_funcs'] = array(); +$GLOBALS['_PEAR_error_handler_stack'] = array(); + +@ini_set('track_errors', true); + +/** + * Base class for other PEAR classes. Provides rudimentary + * emulation of destructors. + * + * If you want a destructor in your class, inherit PEAR and make a + * destructor method called _yourclassname (same name as the + * constructor, but with a "_" prefix). Also, in your constructor you + * have to call the PEAR constructor: $this->PEAR();. + * The destructor method will be called without parameters. Note that + * at in some SAPI implementations (such as Apache), any output during + * the request shutdown (in which destructors are called) seems to be + * discarded. If you need to get any debug information from your + * destructor, use error_log(), syslog() or something similar. + * + * IMPORTANT! To use the emulated destructors you need to create the + * objects by reference: $obj =& new PEAR_child; + * + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Tomas V.V. Cox <cox@idecnet.com> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2006 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 1.7.2 + * @link http://pear.php.net/package/PEAR + * @see PEAR_Error + * @since Class available since PHP 4.0.2 + * @link http://pear.php.net/manual/en/core.pear.php#core.pear.pear + */ +class PEAR +{ + // {{{ properties + + /** + * Whether to enable internal debug messages. + * + * @var bool + * @access private + */ + var $_debug = false; + + /** + * Default error mode for this object. + * + * @var int + * @access private + */ + var $_default_error_mode = null; + + /** + * Default error options used for this object when error mode + * is PEAR_ERROR_TRIGGER. + * + * @var int + * @access private + */ + var $_default_error_options = null; + + /** + * Default error handler (callback) for this object, if error mode is + * PEAR_ERROR_CALLBACK. + * + * @var string + * @access private + */ + var $_default_error_handler = ''; + + /** + * Which class to use for error objects. + * + * @var string + * @access private + */ + var $_error_class = 'PEAR_Error'; + + /** + * An array of expected errors. + * + * @var array + * @access private + */ + var $_expected_errors = array(); + + // }}} + + // {{{ constructor + + /** + * Constructor. Registers this object in + * $_PEAR_destructor_object_list for destructor emulation if a + * destructor object exists. + * + * @param string $error_class (optional) which class to use for + * error objects, defaults to PEAR_Error. + * @access public + * @return void + */ + function PEAR($error_class = null) + { + $classname = strtolower(get_class($this)); + if ($this->_debug) { + print "PEAR constructor called, class=$classname\n"; + } + if ($error_class !== null) { + $this->_error_class = $error_class; + } + while ($classname && strcasecmp($classname, "pear")) { + $destructor = "_$classname"; + if (method_exists($this, $destructor)) { + global $_PEAR_destructor_object_list; + $_PEAR_destructor_object_list[] = &$this; + if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) { + register_shutdown_function("_PEAR_call_destructors"); + $GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true; + } + break; + } else { + $classname = get_parent_class($classname); + } + } + } + + // }}} + // {{{ destructor + + /** + * Destructor (the emulated type of...). Does nothing right now, + * but is included for forward compatibility, so subclass + * destructors should always call it. + * + * See the note in the class desciption about output from + * destructors. + * + * @access public + * @return void + */ + function _PEAR() { + if ($this->_debug) { + printf("PEAR destructor called, class=%s\n", strtolower(get_class($this))); + } + } + + // }}} + // {{{ getStaticProperty() + + /** + * If you have a class that's mostly/entirely static, and you need static + * properties, you can use this method to simulate them. Eg. in your method(s) + * do this: $myVar = &PEAR::getStaticProperty('myclass', 'myVar'); + * You MUST use a reference, or they will not persist! + * + * @access public + * @param string $class The calling classname, to prevent clashes + * @param string $var The variable to retrieve. + * @return mixed A reference to the variable. If not set it will be + * auto initialised to NULL. + */ + public static function &getStaticProperty($class, $var) + { + static $properties; + if (!isset($properties[$class])) { + $properties[$class] = array(); + } + if (!array_key_exists($var, $properties[$class])) { + $properties[$class][$var] = null; + } + return $properties[$class][$var]; + } + + // }}} + // {{{ registerShutdownFunc() + + /** + * Use this function to register a shutdown method for static + * classes. + * + * @access public + * @param mixed $func The function name (or array of class/method) to call + * @param mixed $args The arguments to pass to the function + * @return void + */ + function registerShutdownFunc($func, $args = array()) + { + // if we are called statically, there is a potential + // that no shutdown func is registered. Bug #6445 + if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) { + register_shutdown_function("_PEAR_call_destructors"); + $GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true; + } + $GLOBALS['_PEAR_shutdown_funcs'][] = array($func, $args); + } + + // }}} + // {{{ isError() + + /** + * Tell whether a value is a PEAR error. + * + * @param mixed $data the value to test + * @param int $code if $data is an error object, return true + * only if $code is a string and + * $obj->getMessage() == $code or + * $code is an integer and $obj->getCode() == $code + * @access public + * @return bool true if parameter is an error + */ + public static function isError($data, $code = null) + { + if ($data instanceof PEAR_Error) { + if (is_null($code)) { + return true; + } elseif (is_string($code)) { + return $data->getMessage() == $code; + } else { + return $data->getCode() == $code; + } + } + return false; + } + + // }}} + // {{{ setErrorHandling() + + /** + * Sets how errors generated by this object should be handled. + * Can be invoked both in objects and statically. If called + * statically, setErrorHandling sets the default behaviour for all + * PEAR objects. If called in an object, setErrorHandling sets + * the default behaviour for that object. + * + * @param int $mode + * One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT, + * PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE, + * PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION. + * + * @param mixed $options + * When $mode is PEAR_ERROR_TRIGGER, this is the error level (one + * of E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR). + * + * When $mode is PEAR_ERROR_CALLBACK, this parameter is expected + * to be the callback function or method. A callback + * function is a string with the name of the function, a + * callback method is an array of two elements: the element + * at index 0 is the object, and the element at index 1 is + * the name of the method to call in the object. + * + * When $mode is PEAR_ERROR_PRINT or PEAR_ERROR_DIE, this is + * a printf format string used when printing the error + * message. + * + * @access public + * @return void + * @see PEAR_ERROR_RETURN + * @see PEAR_ERROR_PRINT + * @see PEAR_ERROR_TRIGGER + * @see PEAR_ERROR_DIE + * @see PEAR_ERROR_CALLBACK + * @see PEAR_ERROR_EXCEPTION + * + * @since PHP 4.0.5 + */ + + function setErrorHandling($mode = null, $options = null) + { + if (isset($this) && is_a($this, 'PEAR')) { + $setmode = &$this->_default_error_mode; + $setoptions = &$this->_default_error_options; + } else { + $setmode = &$GLOBALS['_PEAR_default_error_mode']; + $setoptions = &$GLOBALS['_PEAR_default_error_options']; + } + + switch ($mode) { + case PEAR_ERROR_EXCEPTION: + case PEAR_ERROR_RETURN: + case PEAR_ERROR_PRINT: + case PEAR_ERROR_TRIGGER: + case PEAR_ERROR_DIE: + case null: + $setmode = $mode; + $setoptions = $options; + break; + + case PEAR_ERROR_CALLBACK: + $setmode = $mode; + // class/object method callback + if (is_callable($options)) { + $setoptions = $options; + } else { + trigger_error("invalid error callback", E_USER_WARNING); + } + break; + + default: + trigger_error("invalid error mode", E_USER_WARNING); + break; + } + } + + // }}} + // {{{ expectError() + + /** + * This method is used to tell which errors you expect to get. + * Expected errors are always returned with error mode + * PEAR_ERROR_RETURN. Expected error codes are stored in a stack, + * and this method pushes a new element onto it. The list of + * expected errors are in effect until they are popped off the + * stack with the popExpect() method. + * + * Note that this method can not be called statically + * + * @param mixed $code a single error code or an array of error codes to expect + * + * @return int the new depth of the "expected errors" stack + * @access public + */ + function expectError($code = '*') + { + if (is_array($code)) { + array_push($this->_expected_errors, $code); + } else { + array_push($this->_expected_errors, array($code)); + } + return sizeof($this->_expected_errors); + } + + // }}} + // {{{ popExpect() + + /** + * This method pops one element off the expected error codes + * stack. + * + * @return array the list of error codes that were popped + */ + function popExpect() + { + return array_pop($this->_expected_errors); + } + + // }}} + // {{{ _checkDelExpect() + + /** + * This method checks unsets an error code if available + * + * @param mixed error code + * @return bool true if the error code was unset, false otherwise + * @access private + * @since PHP 4.3.0 + */ + function _checkDelExpect($error_code) + { + $deleted = false; + + foreach ($this->_expected_errors AS $key => $error_array) { + if (in_array($error_code, $error_array)) { + unset($this->_expected_errors[$key][array_search($error_code, $error_array)]); + $deleted = true; + } + + // clean up empty arrays + if (0 == count($this->_expected_errors[$key])) { + unset($this->_expected_errors[$key]); + } + } + return $deleted; + } + + // }}} + // {{{ delExpect() + + /** + * This method deletes all occurences of the specified element from + * the expected error codes stack. + * + * @param mixed $error_code error code that should be deleted + * @return mixed list of error codes that were deleted or error + * @access public + * @since PHP 4.3.0 + */ + function delExpect($error_code) + { + $deleted = false; + + if ((is_array($error_code) && (0 != count($error_code)))) { + // $error_code is a non-empty array here; + // we walk through it trying to unset all + // values + foreach($error_code as $key => $error) { + if ($this->_checkDelExpect($error)) { + $deleted = true; + } else { + $deleted = false; + } + } + return $deleted ? true : PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME + } elseif (!empty($error_code)) { + // $error_code comes alone, trying to unset it + if ($this->_checkDelExpect($error_code)) { + return true; + } else { + return PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME + } + } else { + // $error_code is empty + return PEAR::raiseError("The expected error you submitted is empty"); // IMPROVE ME + } + } + + // }}} + // {{{ raiseError() + + /** + * This method is a wrapper that returns an instance of the + * configured error class with this object's default error + * handling applied. If the $mode and $options parameters are not + * specified, the object's defaults are used. + * + * @param mixed $message a text error message or a PEAR error object + * + * @param int $code a numeric error code (it is up to your class + * to define these if you want to use codes) + * + * @param int $mode One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT, + * PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE, + * PEAR_ERROR_CALLBACK, PEAR_ERROR_EXCEPTION. + * + * @param mixed $options If $mode is PEAR_ERROR_TRIGGER, this parameter + * specifies the PHP-internal error level (one of + * E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR). + * If $mode is PEAR_ERROR_CALLBACK, this + * parameter specifies the callback function or + * method. In other error modes this parameter + * is ignored. + * + * @param string $userinfo If you need to pass along for example debug + * information, this parameter is meant for that. + * + * @param string $error_class The returned error object will be + * instantiated from this class, if specified. + * + * @param bool $skipmsg If true, raiseError will only pass error codes, + * the error message parameter will be dropped. + * + * @access public + * @return object a PEAR error object + * @see PEAR::setErrorHandling + * @since PHP 4.0.5 + */ + public static function raiseError($message = null, + $code = null, + $mode = null, + $options = null, + $userinfo = null, + $error_class = null, + $skipmsg = false) + { + // The error is yet a PEAR error object + if (is_object($message)) { + $code = $message->getCode(); + $userinfo = $message->getUserInfo(); + $error_class = $message->getType(); + $message->error_message_prefix = ''; + $message = $message->getMessage(); + } + + if (isset($this) && isset($this->_expected_errors) && sizeof($this->_expected_errors) > 0 && sizeof($exp = end($this->_expected_errors))) { + if ($exp[0] == "*" || + (is_int(reset($exp)) && in_array($code, $exp)) || + (is_string(reset($exp)) && in_array($message, $exp))) { + $mode = PEAR_ERROR_RETURN; + } + } + // No mode given, try global ones + if ($mode === null) { + // Class error handler + if (isset($this) && isset($this->_default_error_mode)) { + $mode = $this->_default_error_mode; + $options = $this->_default_error_options; + // Global error handler + } elseif (isset($GLOBALS['_PEAR_default_error_mode'])) { + $mode = $GLOBALS['_PEAR_default_error_mode']; + $options = $GLOBALS['_PEAR_default_error_options']; + } + } + + if ($error_class !== null) { + $ec = $error_class; + } elseif (isset($this) && isset($this->_error_class)) { + $ec = $this->_error_class; + } else { + $ec = 'PEAR_Error'; + } + if (intval(PHP_VERSION) < 5) { + // little non-eval hack to fix bug #12147 + include 'PEAR/FixPHP5PEARWarnings.php'; + return $a; + } + if ($skipmsg) { + $a = new $ec($code, $mode, $options, $userinfo); + } else { + $a = new $ec($message, $code, $mode, $options, $userinfo); + } + return $a; + } + + // }}} + // {{{ throwError() + + /** + * Simpler form of raiseError with fewer options. In most cases + * message, code and userinfo are enough. + * + * @param string $message + * + */ + function &throwError($message = null, + $code = null, + $userinfo = null) + { + if (isset($this) && is_a($this, 'PEAR')) { + $a = &$this->raiseError($message, $code, null, null, $userinfo); + return $a; + } else { + $a = &PEAR::raiseError($message, $code, null, null, $userinfo); + return $a; + } + } + + // }}} + function staticPushErrorHandling($mode, $options = null) + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + $def_mode = &$GLOBALS['_PEAR_default_error_mode']; + $def_options = &$GLOBALS['_PEAR_default_error_options']; + $stack[] = array($def_mode, $def_options); + switch ($mode) { + case PEAR_ERROR_EXCEPTION: + case PEAR_ERROR_RETURN: + case PEAR_ERROR_PRINT: + case PEAR_ERROR_TRIGGER: + case PEAR_ERROR_DIE: + case null: + $def_mode = $mode; + $def_options = $options; + break; + + case PEAR_ERROR_CALLBACK: + $def_mode = $mode; + // class/object method callback + if (is_callable($options)) { + $def_options = $options; + } else { + trigger_error("invalid error callback", E_USER_WARNING); + } + break; + + default: + trigger_error("invalid error mode", E_USER_WARNING); + break; + } + $stack[] = array($mode, $options); + return true; + } + + function staticPopErrorHandling() + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + $setmode = &$GLOBALS['_PEAR_default_error_mode']; + $setoptions = &$GLOBALS['_PEAR_default_error_options']; + array_pop($stack); + list($mode, $options) = $stack[sizeof($stack) - 1]; + array_pop($stack); + switch ($mode) { + case PEAR_ERROR_EXCEPTION: + case PEAR_ERROR_RETURN: + case PEAR_ERROR_PRINT: + case PEAR_ERROR_TRIGGER: + case PEAR_ERROR_DIE: + case null: + $setmode = $mode; + $setoptions = $options; + break; + + case PEAR_ERROR_CALLBACK: + $setmode = $mode; + // class/object method callback + if (is_callable($options)) { + $setoptions = $options; + } else { + trigger_error("invalid error callback", E_USER_WARNING); + } + break; + + default: + trigger_error("invalid error mode", E_USER_WARNING); + break; + } + return true; + } + + // {{{ pushErrorHandling() + + /** + * Push a new error handler on top of the error handler options stack. With this + * you can easily override the actual error handler for some code and restore + * it later with popErrorHandling. + * + * @param mixed $mode (same as setErrorHandling) + * @param mixed $options (same as setErrorHandling) + * + * @return bool Always true + * + * @see PEAR::setErrorHandling + */ + function pushErrorHandling($mode, $options = null) + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + if (isset($this) && is_a($this, 'PEAR')) { + $def_mode = &$this->_default_error_mode; + $def_options = &$this->_default_error_options; + } else { + $def_mode = &$GLOBALS['_PEAR_default_error_mode']; + $def_options = &$GLOBALS['_PEAR_default_error_options']; + } + $stack[] = array($def_mode, $def_options); + + if (isset($this) && is_a($this, 'PEAR')) { + $this->setErrorHandling($mode, $options); + } else { + PEAR::setErrorHandling($mode, $options); + } + $stack[] = array($mode, $options); + return true; + } + + // }}} + // {{{ popErrorHandling() + + /** + * Pop the last error handler used + * + * @return bool Always true + * + * @see PEAR::pushErrorHandling + */ + function popErrorHandling() + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + array_pop($stack); + list($mode, $options) = $stack[sizeof($stack) - 1]; + array_pop($stack); + if (isset($this) && is_a($this, 'PEAR')) { + $this->setErrorHandling($mode, $options); + } else { + PEAR::setErrorHandling($mode, $options); + } + return true; + } + + // }}} + // {{{ loadExtension() + + /** + * OS independant PHP extension load. Remember to take care + * on the correct extension name for case sensitive OSes. + * + * @param string $ext The extension name + * @return bool Success or not on the dl() call + */ + function loadExtension($ext) + { + if (!extension_loaded($ext)) { + // if either returns true dl() will produce a FATAL error, stop that + if ((ini_get('enable_dl') != 1) || (ini_get('safe_mode') == 1)) { + return false; + } + if (OS_WINDOWS) { + $suffix = '.dll'; + } elseif (PHP_OS == 'HP-UX') { + $suffix = '.sl'; + } elseif (PHP_OS == 'AIX') { + $suffix = '.a'; + } elseif (PHP_OS == 'OSX') { + $suffix = '.bundle'; + } else { + $suffix = '.so'; + } + return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix); + } + return true; + } + + // }}} +} + +// {{{ _PEAR_call_destructors() + +function _PEAR_call_destructors() +{ + global $_PEAR_destructor_object_list; + if (is_array($_PEAR_destructor_object_list) && + sizeof($_PEAR_destructor_object_list)) + { + reset($_PEAR_destructor_object_list); + if (PEAR::getStaticProperty('PEAR', 'destructlifo')) { + $_PEAR_destructor_object_list = array_reverse($_PEAR_destructor_object_list); + } + while (list($k, $objref) = each($_PEAR_destructor_object_list)) { + $classname = get_class($objref); + while ($classname) { + $destructor = "_$classname"; + if (method_exists($objref, $destructor)) { + $objref->$destructor(); + break; + } else { + $classname = get_parent_class($classname); + } + } + } + // Empty the object list to ensure that destructors are + // not called more than once. + $_PEAR_destructor_object_list = array(); + } + + // Now call the shutdown functions + if (is_array($GLOBALS['_PEAR_shutdown_funcs']) AND !empty($GLOBALS['_PEAR_shutdown_funcs'])) { + foreach ($GLOBALS['_PEAR_shutdown_funcs'] as $value) { + call_user_func_array($value[0], $value[1]); + } + } +} + +// }}} +/** + * Standard PEAR error class for PHP 4 + * + * This class is supserseded by {@link PEAR_Exception} in PHP 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Tomas V.V. Cox <cox@idecnet.com> + * @author Gregory Beaver <cellog@php.net> + * @copyright 1997-2006 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 1.7.2 + * @link http://pear.php.net/manual/en/core.pear.pear-error.php + * @see PEAR::raiseError(), PEAR::throwError() + * @since Class available since PHP 4.0.2 + */ +class PEAR_Error +{ + // {{{ properties + + var $error_message_prefix = ''; + var $mode = PEAR_ERROR_RETURN; + var $level = E_USER_NOTICE; + var $code = -1; + var $message = ''; + var $userinfo = ''; + var $backtrace = null; + + // }}} + // {{{ constructor + + /** + * PEAR_Error constructor + * + * @param string $message message + * + * @param int $code (optional) error code + * + * @param int $mode (optional) error mode, one of: PEAR_ERROR_RETURN, + * PEAR_ERROR_PRINT, PEAR_ERROR_DIE, PEAR_ERROR_TRIGGER, + * PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION + * + * @param mixed $options (optional) error level, _OR_ in the case of + * PEAR_ERROR_CALLBACK, the callback function or object/method + * tuple. + * + * @param string $userinfo (optional) additional user/debug info + * + * @access public + * + */ + function PEAR_Error($message = 'unknown error', $code = null, + $mode = null, $options = null, $userinfo = null) + { + if ($mode === null) { + $mode = PEAR_ERROR_RETURN; + } + $this->message = $message; + $this->code = $code; + $this->mode = $mode; + $this->userinfo = $userinfo; + if (!PEAR::getStaticProperty('PEAR_Error', 'skiptrace')) { + $this->backtrace = debug_backtrace(); + if (isset($this->backtrace[0]) && isset($this->backtrace[0]['object'])) { + unset($this->backtrace[0]['object']); + } + } + if ($mode & PEAR_ERROR_CALLBACK) { + $this->level = E_USER_NOTICE; + $this->callback = $options; + } else { + if ($options === null) { + $options = E_USER_NOTICE; + } + $this->level = $options; + $this->callback = null; + } + if ($this->mode & PEAR_ERROR_PRINT) { + if (is_null($options) || is_int($options)) { + $format = "%s"; + } else { + $format = $options; + } + printf($format, $this->getMessage()); + } + if ($this->mode & PEAR_ERROR_TRIGGER) { + trigger_error($this->getMessage(), $this->level); + } + if ($this->mode & PEAR_ERROR_DIE) { + $msg = $this->getMessage(); + if (is_null($options) || is_int($options)) { + $format = "%s"; + if (substr($msg, -1) != "\n") { + $msg .= "\n"; + } + } else { + $format = $options; + } + die(sprintf($format, $msg)); + } + if ($this->mode & PEAR_ERROR_CALLBACK) { + if (is_callable($this->callback)) { + call_user_func($this->callback, $this); + } + } + if ($this->mode & PEAR_ERROR_EXCEPTION) { + trigger_error("PEAR_ERROR_EXCEPTION is obsolete, use class PEAR_Exception for exceptions", E_USER_WARNING); + eval('$e = new Exception($this->message, $this->code);throw($e);'); + } + } + + // }}} + // {{{ getMode() + + /** + * Get the error mode from an error object. + * + * @return int error mode + * @access public + */ + function getMode() { + return $this->mode; + } + + // }}} + // {{{ getCallback() + + /** + * Get the callback function/method from an error object. + * + * @return mixed callback function or object/method array + * @access public + */ + function getCallback() { + return $this->callback; + } + + // }}} + // {{{ getMessage() + + + /** + * Get the error message from an error object. + * + * @return string full error message + * @access public + */ + function getMessage() + { + return ($this->error_message_prefix . $this->message); + } + + + // }}} + // {{{ getCode() + + /** + * Get error code from an error object + * + * @return int error code + * @access public + */ + function getCode() + { + return $this->code; + } + + // }}} + // {{{ getType() + + /** + * Get the name of this error/exception. + * + * @return string error/exception name (type) + * @access public + */ + function getType() + { + return get_class($this); + } + + // }}} + // {{{ getUserInfo() + + /** + * Get additional user-supplied information. + * + * @return string user-supplied information + * @access public + */ + function getUserInfo() + { + return $this->userinfo; + } + + // }}} + // {{{ getDebugInfo() + + /** + * Get additional debug information supplied by the application. + * + * @return string debug information + * @access public + */ + function getDebugInfo() + { + return $this->getUserInfo(); + } + + // }}} + // {{{ getBacktrace() + + /** + * Get the call backtrace from where the error was generated. + * Supported with PHP 4.3.0 or newer. + * + * @param int $frame (optional) what frame to fetch + * @return array Backtrace, or NULL if not available. + * @access public + */ + function getBacktrace($frame = null) + { + if (defined('PEAR_IGNORE_BACKTRACE')) { + return null; + } + if ($frame === null) { + return $this->backtrace; + } + return $this->backtrace[$frame]; + } + + // }}} + // {{{ addUserInfo() + + function addUserInfo($info) + { + if (empty($this->userinfo)) { + $this->userinfo = $info; + } else { + $this->userinfo .= " ** $info"; + } + } + + // }}} + // {{{ toString() + function __toString() + { + return $this->getMessage(); + } + // }}} + // {{{ toString() + + /** + * Make a string representation of this object. + * + * @return string a string with an object summary + * @access public + */ + function toString() { + $modes = array(); + $levels = array(E_USER_NOTICE => 'notice', + E_USER_WARNING => 'warning', + E_USER_ERROR => 'error'); + if ($this->mode & PEAR_ERROR_CALLBACK) { + if (is_array($this->callback)) { + $callback = (is_object($this->callback[0]) ? + strtolower(get_class($this->callback[0])) : + $this->callback[0]) . '::' . + $this->callback[1]; + } else { + $callback = $this->callback; + } + return sprintf('[%s: message="%s" code=%d mode=callback '. + 'callback=%s prefix="%s" info="%s"]', + strtolower(get_class($this)), $this->message, $this->code, + $callback, $this->error_message_prefix, + $this->userinfo); + } + if ($this->mode & PEAR_ERROR_PRINT) { + $modes[] = 'print'; + } + if ($this->mode & PEAR_ERROR_TRIGGER) { + $modes[] = 'trigger'; + } + if ($this->mode & PEAR_ERROR_DIE) { + $modes[] = 'die'; + } + if ($this->mode & PEAR_ERROR_RETURN) { + $modes[] = 'return'; + } + return sprintf('[%s: message="%s" code=%d mode=%s level=%s '. + 'prefix="%s" info="%s"]', + strtolower(get_class($this)), $this->message, $this->code, + implode("|", $modes), $levels[$this->level], + $this->error_message_prefix, + $this->userinfo); + } + + // }}} +} + +/* + * Local Variables: + * mode: php + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> diff --git a/lib/PEAR/PEAR/PEAR.php b/lib/PEAR/PEAR/PEAR.php new file mode 100644 index 0000000000..b4633bf3b8 --- /dev/null +++ b/lib/PEAR/PEAR/PEAR.php @@ -0,0 +1,1118 @@ +<?php +/** + * PEAR, the PHP Extension and Application Repository + * + * PEAR class and PEAR_Error class + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.0 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_0.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category pear + * @package PEAR + * @author Sterling Hughes <sterling@php.net> + * @author Stig Bakken <ssb@php.net> + * @author Tomas V.V.Cox <cox@idecnet.com> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2008 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: PEAR.php,v 1.104 2008/01/03 20:26:34 cellog Exp $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/**#@+ + * ERROR constants + */ +define('PEAR_ERROR_RETURN', 1); +define('PEAR_ERROR_PRINT', 2); +define('PEAR_ERROR_TRIGGER', 4); +define('PEAR_ERROR_DIE', 8); +define('PEAR_ERROR_CALLBACK', 16); +/** + * WARNING: obsolete + * @deprecated + */ +define('PEAR_ERROR_EXCEPTION', 32); +/**#@-*/ +define('PEAR_ZE2', (function_exists('version_compare') && + version_compare(zend_version(), "2-dev", "ge"))); + +if (substr(PHP_OS, 0, 3) == 'WIN') { + define('OS_WINDOWS', true); + define('OS_UNIX', false); + define('PEAR_OS', 'Windows'); +} else { + define('OS_WINDOWS', false); + define('OS_UNIX', true); + define('PEAR_OS', 'Unix'); // blatant assumption +} + +// instant backwards compatibility +if (!defined('PATH_SEPARATOR')) { + if (OS_WINDOWS) { + define('PATH_SEPARATOR', ';'); + } else { + define('PATH_SEPARATOR', ':'); + } +} + +$GLOBALS['_PEAR_default_error_mode'] = PEAR_ERROR_RETURN; +$GLOBALS['_PEAR_default_error_options'] = E_USER_NOTICE; +$GLOBALS['_PEAR_destructor_object_list'] = array(); +$GLOBALS['_PEAR_shutdown_funcs'] = array(); +$GLOBALS['_PEAR_error_handler_stack'] = array(); + +@ini_set('track_errors', true); + +/** + * Base class for other PEAR classes. Provides rudimentary + * emulation of destructors. + * + * If you want a destructor in your class, inherit PEAR and make a + * destructor method called _yourclassname (same name as the + * constructor, but with a "_" prefix). Also, in your constructor you + * have to call the PEAR constructor: $this->PEAR();. + * The destructor method will be called without parameters. Note that + * at in some SAPI implementations (such as Apache), any output during + * the request shutdown (in which destructors are called) seems to be + * discarded. If you need to get any debug information from your + * destructor, use error_log(), syslog() or something similar. + * + * IMPORTANT! To use the emulated destructors you need to create the + * objects by reference: $obj =& new PEAR_child; + * + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Tomas V.V. Cox <cox@idecnet.com> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2006 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 1.7.2 + * @link http://pear.php.net/package/PEAR + * @see PEAR_Error + * @since Class available since PHP 4.0.2 + * @link http://pear.php.net/manual/en/core.pear.php#core.pear.pear + */ +class PEAR +{ + // {{{ properties + + /** + * Whether to enable internal debug messages. + * + * @var bool + * @access private + */ + var $_debug = false; + + /** + * Default error mode for this object. + * + * @var int + * @access private + */ + var $_default_error_mode = null; + + /** + * Default error options used for this object when error mode + * is PEAR_ERROR_TRIGGER. + * + * @var int + * @access private + */ + var $_default_error_options = null; + + /** + * Default error handler (callback) for this object, if error mode is + * PEAR_ERROR_CALLBACK. + * + * @var string + * @access private + */ + var $_default_error_handler = ''; + + /** + * Which class to use for error objects. + * + * @var string + * @access private + */ + var $_error_class = 'PEAR_Error'; + + /** + * An array of expected errors. + * + * @var array + * @access private + */ + var $_expected_errors = array(); + + // }}} + + // {{{ constructor + + /** + * Constructor. Registers this object in + * $_PEAR_destructor_object_list for destructor emulation if a + * destructor object exists. + * + * @param string $error_class (optional) which class to use for + * error objects, defaults to PEAR_Error. + * @access public + * @return void + */ + function PEAR($error_class = null) + { + $classname = strtolower(get_class($this)); + if ($this->_debug) { + print "PEAR constructor called, class=$classname\n"; + } + if ($error_class !== null) { + $this->_error_class = $error_class; + } + while ($classname && strcasecmp($classname, "pear")) { + $destructor = "_$classname"; + if (method_exists($this, $destructor)) { + global $_PEAR_destructor_object_list; + $_PEAR_destructor_object_list[] = &$this; + if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) { + register_shutdown_function("_PEAR_call_destructors"); + $GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true; + } + break; + } else { + $classname = get_parent_class($classname); + } + } + } + + // }}} + // {{{ destructor + + /** + * Destructor (the emulated type of...). Does nothing right now, + * but is included for forward compatibility, so subclass + * destructors should always call it. + * + * See the note in the class desciption about output from + * destructors. + * + * @access public + * @return void + */ + function _PEAR() { + if ($this->_debug) { + printf("PEAR destructor called, class=%s\n", strtolower(get_class($this))); + } + } + + // }}} + // {{{ getStaticProperty() + + /** + * If you have a class that's mostly/entirely static, and you need static + * properties, you can use this method to simulate them. Eg. in your method(s) + * do this: $myVar = &PEAR::getStaticProperty('myclass', 'myVar'); + * You MUST use a reference, or they will not persist! + * + * @access public + * @param string $class The calling classname, to prevent clashes + * @param string $var The variable to retrieve. + * @return mixed A reference to the variable. If not set it will be + * auto initialised to NULL. + */ + public static function &getStaticProperty($class, $var) + { + static $properties; + if (!isset($properties[$class])) { + $properties[$class] = array(); + } + if (!array_key_exists($var, $properties[$class])) { + $properties[$class][$var] = null; + } + return $properties[$class][$var]; + } + + // }}} + // {{{ registerShutdownFunc() + + /** + * Use this function to register a shutdown method for static + * classes. + * + * @access public + * @param mixed $func The function name (or array of class/method) to call + * @param mixed $args The arguments to pass to the function + * @return void + */ + function registerShutdownFunc($func, $args = array()) + { + // if we are called statically, there is a potential + // that no shutdown func is registered. Bug #6445 + if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) { + register_shutdown_function("_PEAR_call_destructors"); + $GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true; + } + $GLOBALS['_PEAR_shutdown_funcs'][] = array($func, $args); + } + + // }}} + // {{{ isError() + + /** + * Tell whether a value is a PEAR error. + * + * @param mixed $data the value to test + * @param int $code if $data is an error object, return true + * only if $code is a string and + * $obj->getMessage() == $code or + * $code is an integer and $obj->getCode() == $code + * @access public + * @return bool true if parameter is an error + */ + public static function isError($data, $code = null) + { + if ($data instanceof PEAR_Error) { + if (is_null($code)) { + return true; + } elseif (is_string($code)) { + return $data->getMessage() == $code; + } else { + return $data->getCode() == $code; + } + } + return false; + } + + // }}} + // {{{ setErrorHandling() + + /** + * Sets how errors generated by this object should be handled. + * Can be invoked both in objects and statically. If called + * statically, setErrorHandling sets the default behaviour for all + * PEAR objects. If called in an object, setErrorHandling sets + * the default behaviour for that object. + * + * @param int $mode + * One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT, + * PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE, + * PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION. + * + * @param mixed $options + * When $mode is PEAR_ERROR_TRIGGER, this is the error level (one + * of E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR). + * + * When $mode is PEAR_ERROR_CALLBACK, this parameter is expected + * to be the callback function or method. A callback + * function is a string with the name of the function, a + * callback method is an array of two elements: the element + * at index 0 is the object, and the element at index 1 is + * the name of the method to call in the object. + * + * When $mode is PEAR_ERROR_PRINT or PEAR_ERROR_DIE, this is + * a printf format string used when printing the error + * message. + * + * @access public + * @return void + * @see PEAR_ERROR_RETURN + * @see PEAR_ERROR_PRINT + * @see PEAR_ERROR_TRIGGER + * @see PEAR_ERROR_DIE + * @see PEAR_ERROR_CALLBACK + * @see PEAR_ERROR_EXCEPTION + * + * @since PHP 4.0.5 + */ + + function setErrorHandling($mode = null, $options = null) + { + if (isset($this) && is_a($this, 'PEAR')) { + $setmode = &$this->_default_error_mode; + $setoptions = &$this->_default_error_options; + } else { + $setmode = &$GLOBALS['_PEAR_default_error_mode']; + $setoptions = &$GLOBALS['_PEAR_default_error_options']; + } + + switch ($mode) { + case PEAR_ERROR_EXCEPTION: + case PEAR_ERROR_RETURN: + case PEAR_ERROR_PRINT: + case PEAR_ERROR_TRIGGER: + case PEAR_ERROR_DIE: + case null: + $setmode = $mode; + $setoptions = $options; + break; + + case PEAR_ERROR_CALLBACK: + $setmode = $mode; + // class/object method callback + if (is_callable($options)) { + $setoptions = $options; + } else { + trigger_error("invalid error callback", E_USER_WARNING); + } + break; + + default: + trigger_error("invalid error mode", E_USER_WARNING); + break; + } + } + + // }}} + // {{{ expectError() + + /** + * This method is used to tell which errors you expect to get. + * Expected errors are always returned with error mode + * PEAR_ERROR_RETURN. Expected error codes are stored in a stack, + * and this method pushes a new element onto it. The list of + * expected errors are in effect until they are popped off the + * stack with the popExpect() method. + * + * Note that this method can not be called statically + * + * @param mixed $code a single error code or an array of error codes to expect + * + * @return int the new depth of the "expected errors" stack + * @access public + */ + function expectError($code = '*') + { + if (is_array($code)) { + array_push($this->_expected_errors, $code); + } else { + array_push($this->_expected_errors, array($code)); + } + return sizeof($this->_expected_errors); + } + + // }}} + // {{{ popExpect() + + /** + * This method pops one element off the expected error codes + * stack. + * + * @return array the list of error codes that were popped + */ + function popExpect() + { + return array_pop($this->_expected_errors); + } + + // }}} + // {{{ _checkDelExpect() + + /** + * This method checks unsets an error code if available + * + * @param mixed error code + * @return bool true if the error code was unset, false otherwise + * @access private + * @since PHP 4.3.0 + */ + function _checkDelExpect($error_code) + { + $deleted = false; + + foreach ($this->_expected_errors AS $key => $error_array) { + if (in_array($error_code, $error_array)) { + unset($this->_expected_errors[$key][array_search($error_code, $error_array)]); + $deleted = true; + } + + // clean up empty arrays + if (0 == count($this->_expected_errors[$key])) { + unset($this->_expected_errors[$key]); + } + } + return $deleted; + } + + // }}} + // {{{ delExpect() + + /** + * This method deletes all occurences of the specified element from + * the expected error codes stack. + * + * @param mixed $error_code error code that should be deleted + * @return mixed list of error codes that were deleted or error + * @access public + * @since PHP 4.3.0 + */ + function delExpect($error_code) + { + $deleted = false; + + if ((is_array($error_code) && (0 != count($error_code)))) { + // $error_code is a non-empty array here; + // we walk through it trying to unset all + // values + foreach($error_code as $key => $error) { + if ($this->_checkDelExpect($error)) { + $deleted = true; + } else { + $deleted = false; + } + } + return $deleted ? true : PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME + } elseif (!empty($error_code)) { + // $error_code comes alone, trying to unset it + if ($this->_checkDelExpect($error_code)) { + return true; + } else { + return PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME + } + } else { + // $error_code is empty + return PEAR::raiseError("The expected error you submitted is empty"); // IMPROVE ME + } + } + + // }}} + // {{{ raiseError() + + /** + * This method is a wrapper that returns an instance of the + * configured error class with this object's default error + * handling applied. If the $mode and $options parameters are not + * specified, the object's defaults are used. + * + * @param mixed $message a text error message or a PEAR error object + * + * @param int $code a numeric error code (it is up to your class + * to define these if you want to use codes) + * + * @param int $mode One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT, + * PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE, + * PEAR_ERROR_CALLBACK, PEAR_ERROR_EXCEPTION. + * + * @param mixed $options If $mode is PEAR_ERROR_TRIGGER, this parameter + * specifies the PHP-internal error level (one of + * E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR). + * If $mode is PEAR_ERROR_CALLBACK, this + * parameter specifies the callback function or + * method. In other error modes this parameter + * is ignored. + * + * @param string $userinfo If you need to pass along for example debug + * information, this parameter is meant for that. + * + * @param string $error_class The returned error object will be + * instantiated from this class, if specified. + * + * @param bool $skipmsg If true, raiseError will only pass error codes, + * the error message parameter will be dropped. + * + * @access public + * @return object a PEAR error object + * @see PEAR::setErrorHandling + * @since PHP 4.0.5 + */ + public static function raiseError($message = null, + $code = null, + $mode = null, + $options = null, + $userinfo = null, + $error_class = null, + $skipmsg = false) + { + // The error is yet a PEAR error object + if (is_object($message)) { + $code = $message->getCode(); + $userinfo = $message->getUserInfo(); + $error_class = $message->getType(); + $message->error_message_prefix = ''; + $message = $message->getMessage(); + } + + if (isset($this) && isset($this->_expected_errors) && sizeof($this->_expected_errors) > 0 && sizeof($exp = end($this->_expected_errors))) { + if ($exp[0] == "*" || + (is_int(reset($exp)) && in_array($code, $exp)) || + (is_string(reset($exp)) && in_array($message, $exp))) { + $mode = PEAR_ERROR_RETURN; + } + } + // No mode given, try global ones + if ($mode === null) { + // Class error handler + if (isset($this) && isset($this->_default_error_mode)) { + $mode = $this->_default_error_mode; + $options = $this->_default_error_options; + // Global error handler + } elseif (isset($GLOBALS['_PEAR_default_error_mode'])) { + $mode = $GLOBALS['_PEAR_default_error_mode']; + $options = $GLOBALS['_PEAR_default_error_options']; + } + } + + if ($error_class !== null) { + $ec = $error_class; + } elseif (isset($this) && isset($this->_error_class)) { + $ec = $this->_error_class; + } else { + $ec = 'PEAR_Error'; + } + if (intval(PHP_VERSION) < 5) { + // little non-eval hack to fix bug #12147 + include 'PEAR/FixPHP5PEARWarnings.php'; + return $a; + } + if ($skipmsg) { + $a = new $ec($code, $mode, $options, $userinfo); + } else { + $a = new $ec($message, $code, $mode, $options, $userinfo); + } + return $a; + } + + // }}} + // {{{ throwError() + + /** + * Simpler form of raiseError with fewer options. In most cases + * message, code and userinfo are enough. + * + * @param string $message + * + */ + function &throwError($message = null, + $code = null, + $userinfo = null) + { + if (isset($this) && is_a($this, 'PEAR')) { + $a = &$this->raiseError($message, $code, null, null, $userinfo); + return $a; + } else { + $a = &PEAR::raiseError($message, $code, null, null, $userinfo); + return $a; + } + } + + // }}} + function staticPushErrorHandling($mode, $options = null) + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + $def_mode = &$GLOBALS['_PEAR_default_error_mode']; + $def_options = &$GLOBALS['_PEAR_default_error_options']; + $stack[] = array($def_mode, $def_options); + switch ($mode) { + case PEAR_ERROR_EXCEPTION: + case PEAR_ERROR_RETURN: + case PEAR_ERROR_PRINT: + case PEAR_ERROR_TRIGGER: + case PEAR_ERROR_DIE: + case null: + $def_mode = $mode; + $def_options = $options; + break; + + case PEAR_ERROR_CALLBACK: + $def_mode = $mode; + // class/object method callback + if (is_callable($options)) { + $def_options = $options; + } else { + trigger_error("invalid error callback", E_USER_WARNING); + } + break; + + default: + trigger_error("invalid error mode", E_USER_WARNING); + break; + } + $stack[] = array($mode, $options); + return true; + } + + function staticPopErrorHandling() + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + $setmode = &$GLOBALS['_PEAR_default_error_mode']; + $setoptions = &$GLOBALS['_PEAR_default_error_options']; + array_pop($stack); + list($mode, $options) = $stack[sizeof($stack) - 1]; + array_pop($stack); + switch ($mode) { + case PEAR_ERROR_EXCEPTION: + case PEAR_ERROR_RETURN: + case PEAR_ERROR_PRINT: + case PEAR_ERROR_TRIGGER: + case PEAR_ERROR_DIE: + case null: + $setmode = $mode; + $setoptions = $options; + break; + + case PEAR_ERROR_CALLBACK: + $setmode = $mode; + // class/object method callback + if (is_callable($options)) { + $setoptions = $options; + } else { + trigger_error("invalid error callback", E_USER_WARNING); + } + break; + + default: + trigger_error("invalid error mode", E_USER_WARNING); + break; + } + return true; + } + + // {{{ pushErrorHandling() + + /** + * Push a new error handler on top of the error handler options stack. With this + * you can easily override the actual error handler for some code and restore + * it later with popErrorHandling. + * + * @param mixed $mode (same as setErrorHandling) + * @param mixed $options (same as setErrorHandling) + * + * @return bool Always true + * + * @see PEAR::setErrorHandling + */ + function pushErrorHandling($mode, $options = null) + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + if (isset($this) && is_a($this, 'PEAR')) { + $def_mode = &$this->_default_error_mode; + $def_options = &$this->_default_error_options; + } else { + $def_mode = &$GLOBALS['_PEAR_default_error_mode']; + $def_options = &$GLOBALS['_PEAR_default_error_options']; + } + $stack[] = array($def_mode, $def_options); + + if (isset($this) && is_a($this, 'PEAR')) { + $this->setErrorHandling($mode, $options); + } else { + PEAR::setErrorHandling($mode, $options); + } + $stack[] = array($mode, $options); + return true; + } + + // }}} + // {{{ popErrorHandling() + + /** + * Pop the last error handler used + * + * @return bool Always true + * + * @see PEAR::pushErrorHandling + */ + function popErrorHandling() + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + array_pop($stack); + list($mode, $options) = $stack[sizeof($stack) - 1]; + array_pop($stack); + if (isset($this) && is_a($this, 'PEAR')) { + $this->setErrorHandling($mode, $options); + } else { + PEAR::setErrorHandling($mode, $options); + } + return true; + } + + // }}} + // {{{ loadExtension() + + /** + * OS independant PHP extension load. Remember to take care + * on the correct extension name for case sensitive OSes. + * + * @param string $ext The extension name + * @return bool Success or not on the dl() call + */ + function loadExtension($ext) + { + if (!extension_loaded($ext)) { + // if either returns true dl() will produce a FATAL error, stop that + if ((ini_get('enable_dl') != 1) || (ini_get('safe_mode') == 1)) { + return false; + } + if (OS_WINDOWS) { + $suffix = '.dll'; + } elseif (PHP_OS == 'HP-UX') { + $suffix = '.sl'; + } elseif (PHP_OS == 'AIX') { + $suffix = '.a'; + } elseif (PHP_OS == 'OSX') { + $suffix = '.bundle'; + } else { + $suffix = '.so'; + } + return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix); + } + return true; + } + + // }}} +} + +// {{{ _PEAR_call_destructors() + +function _PEAR_call_destructors() +{ + global $_PEAR_destructor_object_list; + if (is_array($_PEAR_destructor_object_list) && + sizeof($_PEAR_destructor_object_list)) + { + reset($_PEAR_destructor_object_list); + if (PEAR::getStaticProperty('PEAR', 'destructlifo')) { + $_PEAR_destructor_object_list = array_reverse($_PEAR_destructor_object_list); + } + while (list($k, $objref) = each($_PEAR_destructor_object_list)) { + $classname = get_class($objref); + while ($classname) { + $destructor = "_$classname"; + if (method_exists($objref, $destructor)) { + $objref->$destructor(); + break; + } else { + $classname = get_parent_class($classname); + } + } + } + // Empty the object list to ensure that destructors are + // not called more than once. + $_PEAR_destructor_object_list = array(); + } + + // Now call the shutdown functions + if (is_array($GLOBALS['_PEAR_shutdown_funcs']) AND !empty($GLOBALS['_PEAR_shutdown_funcs'])) { + foreach ($GLOBALS['_PEAR_shutdown_funcs'] as $value) { + call_user_func_array($value[0], $value[1]); + } + } +} + +// }}} +/** + * Standard PEAR error class for PHP 4 + * + * This class is supserseded by {@link PEAR_Exception} in PHP 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Tomas V.V. Cox <cox@idecnet.com> + * @author Gregory Beaver <cellog@php.net> + * @copyright 1997-2006 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 1.7.2 + * @link http://pear.php.net/manual/en/core.pear.pear-error.php + * @see PEAR::raiseError(), PEAR::throwError() + * @since Class available since PHP 4.0.2 + */ +class PEAR_Error +{ + // {{{ properties + + var $error_message_prefix = ''; + var $mode = PEAR_ERROR_RETURN; + var $level = E_USER_NOTICE; + var $code = -1; + var $message = ''; + var $userinfo = ''; + var $backtrace = null; + + // }}} + // {{{ constructor + + /** + * PEAR_Error constructor + * + * @param string $message message + * + * @param int $code (optional) error code + * + * @param int $mode (optional) error mode, one of: PEAR_ERROR_RETURN, + * PEAR_ERROR_PRINT, PEAR_ERROR_DIE, PEAR_ERROR_TRIGGER, + * PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION + * + * @param mixed $options (optional) error level, _OR_ in the case of + * PEAR_ERROR_CALLBACK, the callback function or object/method + * tuple. + * + * @param string $userinfo (optional) additional user/debug info + * + * @access public + * + */ + function PEAR_Error($message = 'unknown error', $code = null, + $mode = null, $options = null, $userinfo = null) + { + if ($mode === null) { + $mode = PEAR_ERROR_RETURN; + } + $this->message = $message; + $this->code = $code; + $this->mode = $mode; + $this->userinfo = $userinfo; + if (!PEAR::getStaticProperty('PEAR_Error', 'skiptrace')) { + $this->backtrace = debug_backtrace(); + if (isset($this->backtrace[0]) && isset($this->backtrace[0]['object'])) { + unset($this->backtrace[0]['object']); + } + } + if ($mode & PEAR_ERROR_CALLBACK) { + $this->level = E_USER_NOTICE; + $this->callback = $options; + } else { + if ($options === null) { + $options = E_USER_NOTICE; + } + $this->level = $options; + $this->callback = null; + } + if ($this->mode & PEAR_ERROR_PRINT) { + if (is_null($options) || is_int($options)) { + $format = "%s"; + } else { + $format = $options; + } + printf($format, $this->getMessage()); + } + if ($this->mode & PEAR_ERROR_TRIGGER) { + trigger_error($this->getMessage(), $this->level); + } + if ($this->mode & PEAR_ERROR_DIE) { + $msg = $this->getMessage(); + if (is_null($options) || is_int($options)) { + $format = "%s"; + if (substr($msg, -1) != "\n") { + $msg .= "\n"; + } + } else { + $format = $options; + } + die(sprintf($format, $msg)); + } + if ($this->mode & PEAR_ERROR_CALLBACK) { + if (is_callable($this->callback)) { + call_user_func($this->callback, $this); + } + } + if ($this->mode & PEAR_ERROR_EXCEPTION) { + trigger_error("PEAR_ERROR_EXCEPTION is obsolete, use class PEAR_Exception for exceptions", E_USER_WARNING); + eval('$e = new Exception($this->message, $this->code);throw($e);'); + } + } + + // }}} + // {{{ getMode() + + /** + * Get the error mode from an error object. + * + * @return int error mode + * @access public + */ + function getMode() { + return $this->mode; + } + + // }}} + // {{{ getCallback() + + /** + * Get the callback function/method from an error object. + * + * @return mixed callback function or object/method array + * @access public + */ + function getCallback() { + return $this->callback; + } + + // }}} + // {{{ getMessage() + + + /** + * Get the error message from an error object. + * + * @return string full error message + * @access public + */ + function getMessage() + { + return ($this->error_message_prefix . $this->message); + } + + + // }}} + // {{{ getCode() + + /** + * Get error code from an error object + * + * @return int error code + * @access public + */ + function getCode() + { + return $this->code; + } + + // }}} + // {{{ getType() + + /** + * Get the name of this error/exception. + * + * @return string error/exception name (type) + * @access public + */ + function getType() + { + return get_class($this); + } + + // }}} + // {{{ getUserInfo() + + /** + * Get additional user-supplied information. + * + * @return string user-supplied information + * @access public + */ + function getUserInfo() + { + return $this->userinfo; + } + + // }}} + // {{{ getDebugInfo() + + /** + * Get additional debug information supplied by the application. + * + * @return string debug information + * @access public + */ + function getDebugInfo() + { + return $this->getUserInfo(); + } + + // }}} + // {{{ getBacktrace() + + /** + * Get the call backtrace from where the error was generated. + * Supported with PHP 4.3.0 or newer. + * + * @param int $frame (optional) what frame to fetch + * @return array Backtrace, or NULL if not available. + * @access public + */ + function getBacktrace($frame = null) + { + if (defined('PEAR_IGNORE_BACKTRACE')) { + return null; + } + if ($frame === null) { + return $this->backtrace; + } + return $this->backtrace[$frame]; + } + + // }}} + // {{{ addUserInfo() + + function addUserInfo($info) + { + if (empty($this->userinfo)) { + $this->userinfo = $info; + } else { + $this->userinfo .= " ** $info"; + } + } + + // }}} + // {{{ toString() + function __toString() + { + return $this->getMessage(); + } + // }}} + // {{{ toString() + + /** + * Make a string representation of this object. + * + * @return string a string with an object summary + * @access public + */ + function toString() { + $modes = array(); + $levels = array(E_USER_NOTICE => 'notice', + E_USER_WARNING => 'warning', + E_USER_ERROR => 'error'); + if ($this->mode & PEAR_ERROR_CALLBACK) { + if (is_array($this->callback)) { + $callback = (is_object($this->callback[0]) ? + strtolower(get_class($this->callback[0])) : + $this->callback[0]) . '::' . + $this->callback[1]; + } else { + $callback = $this->callback; + } + return sprintf('[%s: message="%s" code=%d mode=callback '. + 'callback=%s prefix="%s" info="%s"]', + strtolower(get_class($this)), $this->message, $this->code, + $callback, $this->error_message_prefix, + $this->userinfo); + } + if ($this->mode & PEAR_ERROR_PRINT) { + $modes[] = 'print'; + } + if ($this->mode & PEAR_ERROR_TRIGGER) { + $modes[] = 'trigger'; + } + if ($this->mode & PEAR_ERROR_DIE) { + $modes[] = 'die'; + } + if ($this->mode & PEAR_ERROR_RETURN) { + $modes[] = 'return'; + } + return sprintf('[%s: message="%s" code=%d mode=%s level=%s '. + 'prefix="%s" info="%s"]', + strtolower(get_class($this)), $this->message, $this->code, + implode("|", $modes), $levels[$this->level], + $this->error_message_prefix, + $this->userinfo); + } + + // }}} +} + +/* + * Local Variables: + * mode: php + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> diff --git a/lib/PEAR/SOAP/Base.php b/lib/PEAR/SOAP/Base.php new file mode 100644 index 0000000000..14f96c12ca --- /dev/null +++ b/lib/PEAR/SOAP/Base.php @@ -0,0 +1,1142 @@ +<?php +/** + * This file loads all required libraries, defines constants used across the + * SOAP package, and defines the base classes that most other classes of this + * package extend. + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 2.02 of the PHP license, + * that is bundled with this package in the file LICENSE, and is available at + * through the world-wide-web at http://www.php.net/license/2_02.txt. If you + * did not receive a copy of the PHP license and are unable to obtain it + * through the world-wide-web, please send a note to license@php.net so we can + * mail you a copy immediately. + * + * @category Web Services + * @package SOAP + * @author Dietrich Ayala <dietrich@ganx4.com> Original Author + * @author Shane Caraveo <Shane@Caraveo.com> Port to PEAR and more + * @author Chuck Hagenbuch <chuck@horde.org> Maintenance + * @author Jan Schneider <jan@horde.org> Maintenance + * @copyright 2003-2007 The PHP Group + * @license http://www.php.net/license/2_02.txt PHP License 2.02 + * @link http://pear.php.net/package/SOAP + */ + +/** Define linebreak sequence for the Mail_Mime package. */ +define('MAIL_MIMEPART_CRLF', "\r\n"); + +require_once 'PEAR.php'; + +if (!defined('INF')) { + define('INF', 1.8e307); +} +if (!defined('NAN')) { + define('NAN', 0.0); +} + +define('SOAP_LIBRARY_VERSION', '0.12.0'); +define('SOAP_LIBRARY_NAME', 'PEAR-SOAP 0.12.0-beta'); + +// Set schema version. +define('SOAP_XML_SCHEMA_VERSION', 'http://www.w3.org/2001/XMLSchema'); +define('SOAP_XML_SCHEMA_INSTANCE', 'http://www.w3.org/2001/XMLSchema-instance'); +define('SOAP_XML_SCHEMA_1999', 'http://www.w3.org/1999/XMLSchema'); +define('SOAP_SCHEMA', 'http://schemas.xmlsoap.org/wsdl/soap/'); +define('SOAP_SCHEMA_ENCODING', 'http://schemas.xmlsoap.org/soap/encoding/'); +define('SOAP_ENVELOP', 'http://schemas.xmlsoap.org/soap/envelope/'); + +define('SCHEMA_DISCO', 'http://schemas.xmlsoap.org/disco/'); +define('SCHEMA_DISCO_SCL', 'http://schemas.xmlsoap.org/disco/scl/'); + +define('SCHEMA_SOAP', 'http://schemas.xmlsoap.org/wsdl/soap/'); +define('SCHEMA_SOAP12', 'http://schemas.xmlsoap.org/wsdl/soap12/'); +define('SCHEMA_SOAP_HTTP', 'http://schemas.xmlsoap.org/soap/http'); +define('SCHEMA_WSDL_HTTP', 'http://schemas.xmlsoap.org/wsdl/http/'); +define('SCHEMA_MIME', 'http://schemas.xmlsoap.org/wsdl/mime/'); +define('SCHEMA_WSDL', 'http://schemas.xmlsoap.org/wsdl/'); +define('SCHEMA_DIME', 'http://schemas.xmlsoap.org/ws/2002/04/dime/wsdl/'); +define('SCHEMA_CONTENT', 'http://schemas.xmlsoap.org/ws/2002/04/content-type/'); +define('SCHEMA_REF', 'http://schemas.xmlsoap.org/ws/2002/04/reference/'); + +define('SOAP_DEFAULT_ENCODING', 'UTF-8'); + +/** + * @package SOAP + */ +class SOAP_Base_Object extends PEAR +{ + + /** + * Supported encodings, limited by XML extension. + * + * @var array $_encodings + */ + var $_encodings = array('ISO-8859-1', 'US-ASCII', 'UTF-8'); + + /** + * Fault code. + * + * @var string $_myfaultcode + */ + var $_myfaultcode = ''; + + /** + * Recent PEAR_Error object. + * + * @var PEAR_Error $fault + */ + var $fault = null; + + /** + * Constructor. + * + * @param string $faultcode Error code. + */ + function SOAP_Base_Object($faultcode = 'Client') + { + $this->_myfaultcode = $faultcode; + parent::PEAR('SOAP_Fault'); + } + + /** + * Raises a SOAP error. + * + * Please refer to the SOAP definition for an impression of what a certain + * parameter stands for. + * + * @param string|object $str Error message or object. + * @param string $detail Detailed error message. + * @param string $actorURI + * @param mixed $code + * @param mixed $mode + * @param mixed $options + * @param boolean $skipmsg + */ + function &_raiseSoapFault($str, $detail = '', $actorURI = '', $code = null, + $mode = null, $options = null, $skipmsg = false) + { + // Pass through previous faults. + $is_instance = isset($this) && $this instanceof SOAP_Base_Object; + if (is_object($str)) { + $fault = $str; + } else { + if (!$code) { + $code = $is_instance ? $this->_myfaultcode : 'Client'; + } + require_once 'SOAP/Fault.php'; + $fault = new SOAP_Fault($str, $code, $actorURI, $detail, $mode, + $options); + } + if ($is_instance) { + $this->fault = $fault; + } + + return $fault; + } + + function _isfault() + { + return $this->fault != null; + } + + function &_getfault() + { + return $this->fault; + } + +} + +/** + * Common base class of all SOAP classes. + * + * @access public + * @package SOAP + * @author Shane Caraveo <shane@php.net> Conversion to PEAR and updates + */ +class SOAP_Base extends SOAP_Base_Object +{ + var $_XMLSchema = array('http://www.w3.org/2001/XMLSchema', + 'http://www.w3.org/1999/XMLSchema'); + var $_XMLSchemaVersion = 'http://www.w3.org/2001/XMLSchema'; + + // load types into typemap array + var $_typemap = array( + 'http://www.w3.org/2001/XMLSchema' => array( + 'string' => 'string', + 'boolean' => 'boolean', + 'float' => 'float', + 'double' => 'float', + 'decimal' => 'float', + 'duration' => 'integer', + 'dateTime' => 'string', + 'time' => 'string', + 'date' => 'string', + 'gYearMonth' => 'integer', + 'gYear' => 'integer', + 'gMonthDay' => 'integer', + 'gDay' => 'integer', + 'gMonth' => 'integer', + 'hexBinary' => 'string', + 'base64Binary' => 'string', + // derived datatypes + 'normalizedString' => 'string', + 'token' => 'string', + 'language' => 'string', + 'NMTOKEN' => 'string', + 'NMTOKENS' => 'string', + 'Name' => 'string', + 'NCName' => 'string', + 'ID' => 'string', + 'IDREF' => 'string', + 'IDREFS' => 'string', + 'ENTITY' => 'string', + 'ENTITIES' => 'string', + 'integer' => 'integer', + 'nonPositiveInteger' => 'integer', + 'negativeInteger' => 'integer', + // longs (64bit ints) are not supported cross-platform. + 'long' => 'string', + 'int' => 'integer', + 'short' => 'integer', + 'byte' => 'string', + 'nonNegativeInteger' => 'integer', + 'unsignedLong' => 'integer', + 'unsignedInt' => 'integer', + 'unsignedShort' => 'integer', + 'unsignedByte' => 'integer', + 'positiveInteger' => 'integer', + 'anyType' => 'string', + 'anyURI' => 'string', + 'QName' => 'string' + ), + 'http://www.w3.org/1999/XMLSchema' => array( + 'i4' => 'integer', + 'int' => 'integer', + 'boolean' => 'boolean', + 'string' => 'string', + 'double' => 'float', + 'float' => 'float', + 'dateTime' => 'string', + 'timeInstant' => 'string', + 'base64Binary' => 'string', + 'base64' => 'string', + 'ur-type' => 'string' + ), + 'http://schemas.xmlsoap.org/soap/encoding/' => array( + 'base64' => 'string', + 'array' => 'array', + 'Array' => 'array', + 'Struct' => 'array') + ); + + /** + * Default class name to use for decoded response objects. + * + * @var string $_defaultObjectClassname + */ + var $_defaultObjectClassname = 'stdClass'; + + /** + * Hash with used namespaces. + * + * @var array + */ + var $_namespaces; + + /** + * The default namespace. + * + * @var string + */ + var $_namespace; + + var $_xmlEntities = array('&' => '&', + '<' => '<', + '>' => '>', + "'" => ''', + '"' => '"'); + + var $_doconversion = false; + + var $_attachments = array(); + + var $_wsdl = null; + + /** + * True if we use section 5 encoding, or false if this is literal. + * + * @var boolean $_section5 + */ + var $_section5 = true; + + // Handle type to class mapping. + var $_auto_translation = false; + var $_type_translation = array(); + + /** + * Constructor. + * + * @param string $faultcode Error code. + */ + function SOAP_Base($faultcode = 'Client') + { + parent::SOAP_Base_Object($faultcode); + $this->_resetNamespaces(); + } + + /** + * Sets the SOAP-ENV prefix and returns the current value. + * + * @access public + * + * @param string SOAP-ENV prefix + * + * @return string current SOAP-ENV prefix. + */ + function SOAPENVPrefix($prefix = null) + { + static $_soapenv_prefix = 'SOAP-ENV'; + if (!is_null($prefix)) { + $_soapenv_prefix = $prefix; + } + return $_soapenv_prefix; + } + + /** + * Sets the SOAP-ENC prefix and returns the current value. + * + * @access public + * + * @param string SOAP-ENC prefix + * + * @return string current SOAP-ENC prefix. + */ + function SOAPENCPrefix($prefix = null) + { + static $_soapenv_prefix = 'SOAP-ENC'; + if (!is_null($prefix)) { + $_soapenv_prefix = $prefix; + } + return $_soapenv_prefix; + } + + /** + * Sets the default namespace. + * + * @param string $namespace The default namespace. + */ + function setDefaultNamespace($namespace) + { + $this->_namespace = $namespace; + } + + function _resetNamespaces() + { + $this->_namespaces = array( + 'http://schemas.xmlsoap.org/soap/envelope/' => SOAP_BASE::SOAPENVPrefix(), + 'http://www.w3.org/2001/XMLSchema' => 'xsd', + 'http://www.w3.org/2001/XMLSchema-instance' => 'xsi', + 'http://schemas.xmlsoap.org/soap/encoding/' => SOAP_BASE::SOAPENCPrefix()); + } + + /** + * Sets the schema version used in the SOAP message. + * + * @access private + * @see $_XMLSchema + * + * @param string $schemaVersion The schema version. + */ + function _setSchemaVersion($schemaVersion) + { + if (!in_array($schemaVersion, $this->_XMLSchema)) { + return $this->_raiseSoapFault("unsuported XMLSchema $schemaVersion"); + } + $this->_XMLSchemaVersion = $schemaVersion; + $tmpNS = array_flip($this->_namespaces); + $tmpNS['xsd'] = $this->_XMLSchemaVersion; + $tmpNS['xsi'] = $this->_XMLSchemaVersion . '-instance'; + $this->_namespaces = array_flip($tmpNS); + } + + function _getNamespacePrefix($ns) + { + if ($this->_namespace && $ns == $this->_namespace) { + return ''; + } + if (isset($this->_namespaces[$ns])) { + return $this->_namespaces[$ns]; + } + $prefix = 'ns' . count($this->_namespaces); + $this->_namespaces[$ns] = $prefix; + return $prefix; + } + + function _getNamespaceForPrefix($prefix) + { + $flipped = array_flip($this->_namespaces); + if (isset($flipped[$prefix])) { + return $flipped[$prefix]; + } + return null; + } + + /** + * Serializes a value, array or object according to the rules set by this + * object. + * + * @see SOAP_Value + * + * @param mixed $value The actual value. + * @param QName $name The value name. + * @param QName $type The value type. + * @param array $options A list of encoding and serialization options. + * @param array $attributes A hash of additional attributes. + * @param string $artype The type of any array elements. + */ + function _serializeValue($value, $name = null, $type = null, + $options = array(), $attributes = array(), + $artype = '') + { + $namespaces = array(); + $arrayType = $array_depth = $xmlout_value = null; + $typePrefix = $elPrefix = $xmlout_arrayType = ''; + $xmlout_type = $xmlns = $ptype = $array_type_ns = ''; + + if (!$name->name || is_numeric($name->name)) { + $name->name = 'item'; + } + + if ($this->_wsdl) { + list($ptype, $arrayType, $array_type_ns, $array_depth) + = $this->_wsdl->getSchemaType($type, $name); + } + + if (!$arrayType) { + $arrayType = $artype; + } + if (!$ptype) { + $ptype = $this->_getType($value); + } + if (!$type) { + $type = new QName($ptype); + } + + if (strcasecmp($ptype, 'Struct') == 0 || + strcasecmp($type->name, 'Struct') == 0) { + // Struct + $vars = is_object($value) ? get_object_vars($value) : $value; + if (is_array($vars)) { + foreach (array_keys($vars) as $k) { + // Hide private vars. + if ($k[0] == '_') { + continue; + } + + if (is_object($vars[$k])) { + if (is_a($vars[$k], 'SOAP_Value')) { + $xmlout_value .= $vars[$k]->serialize($this); + } else { + // XXX get the members and serialize them instead + // converting to an array is more overhead than we + // should really do. + $xmlout_value .= $this->_serializeValue(get_object_vars($vars[$k]), new QName($k, $this->_section5 ? null : $name->namepace), null, $options); + } + } else { + $xmlout_value .= $this->_serializeValue($vars[$k], new QName($k, $this->_section5 ? null : $name->namespace), false, $options); + } + } + } + } elseif (strcasecmp($ptype, 'Array') == 0 || + strcasecmp($type->name, 'Array') == 0) { + // Array. + $type = new QName('Array', SOAP_SCHEMA_ENCODING); + $numtypes = 0; + $value = (array)$value; + // XXX this will be slow on larger arrays. Basically, it flattens + // arrays to allow us to serialize multi-dimensional arrays. We + // only do this if arrayType is set, which will typically only + // happen if we are using WSDL + if (isset($options['flatten']) || + ($arrayType && + (strchr($arrayType, ',') || strstr($arrayType, '][')))) { + $numtypes = $this->_multiArrayType($value, $arrayType, + $ar_size, $xmlout_value); + } + + $array_type = $array_type_prefix = ''; + if ($numtypes != 1) { + $arrayTypeQName = new QName($arrayType); + $arrayType = $arrayTypeQName->name; + $array_types = array(); + $array_val = null; + + // Serialize each array element. + $ar_size = count($value); + foreach ($value as $array_val) { + if (is_a($array_val, 'SOAP_Value')) { + $array_type = $array_val->type; + $array_types[$array_type] = 1; + $array_type_ns = $array_val->type_namespace; + $xmlout_value .= $array_val->serialize($this); + } else { + $array_type = $this->_getType($array_val); + $array_types[$array_type] = 1; + if (empty($options['keep_arrays_flat'])) { + $xmlout_value .= $this->_serializeValue($array_val, new QName('item', $this->_section5 ? null : $name->namespace), new QName($array_type), $options); + } else { + $xmlout_value .= $this->_serializeValue($array_val, $name, new QName($array_type), $options, $attributes); + } + } + } + + if (!$arrayType) { + $numtypes = count($array_types); + if ($numtypes == 1) { + $arrayType = $array_type; + } + // Using anyType is more interoperable. + if ($array_type == 'Struct') { + $array_type = ''; + } elseif ($array_type == 'Array') { + $arrayType = 'anyType'; + $array_type_prefix = 'xsd'; + } else { + if (!$arrayType) { + $arrayType = $array_type; + } + } + } + } + if (!$arrayType || $numtypes > 1) { + // Should reference what schema we're using. + $arrayType = 'xsd:anyType'; + } else { + if ($array_type_ns) { + $array_type_prefix = $this->_getNamespacePrefix($array_type_ns); + } elseif (isset($this->_typemap[$this->_XMLSchemaVersion][$arrayType])) { + $array_type_prefix = $this->_namespaces[$this->_XMLSchemaVersion]; + } elseif (isset($this->_typemap[SOAP_SCHEMA_ENCODING][$arrayType])) { + $array_type_prefix = SOAP_BASE::SOAPENCPrefix(); + } + if ($array_type_prefix) { + $arrayType = $array_type_prefix . ':' . $arrayType; + } + } + + $xmlout_arrayType = ' ' . SOAP_BASE::SOAPENCPrefix() + . ':arrayType="' . $arrayType; + if ($array_depth != null) { + for ($i = 0; $i < $array_depth; $i++) { + $xmlout_arrayType .= '[]'; + } + } + $xmlout_arrayType .= "[$ar_size]\""; + } elseif ($value instanceof SOAP_Value) { + $xmlout_value = $value->serialize($this); + } elseif ($type->name == 'string') { + $xmlout_value = htmlspecialchars($value); + } elseif ($type->name == 'rawstring') { + $xmlout_value = $value; + } elseif ($type->name == 'boolean') { + $xmlout_value = $value ? 'true' : 'false'; + } else { + $xmlout_value = $value; + } + + // Add namespaces. + if ($name->namespace) { + $elPrefix = $this->_getNamespacePrefix($name->namespace); + if ($elPrefix) { + $xmlout_name = $elPrefix . ':' . $name->name; + } else { + $xmlout_name = $name->name; + } + } else { + $xmlout_name = $name->name; + } + + if ($type->namespace) { + $typePrefix = false; + if (empty($options['no_type_prefix'])) { + $typePrefix = $this->_getNamespacePrefix($type->namespace); + } + if ($typePrefix) { + $xmlout_type = $typePrefix . ':' . $type->name; + } else { + $xmlout_type = $type->name; + } + } elseif ($type->name && + isset($this->_typemap[$this->_XMLSchemaVersion][$type->name])) { + $typePrefix = $this->_namespaces[$this->_XMLSchemaVersion]; + if ($typePrefix) { + $xmlout_type = $typePrefix . ':' . $type->name; + } else { + $xmlout_type = $type->name; + } + } + + // Handle additional attributes. + $xml_attr = ''; + if (count($attributes)) { + foreach ($attributes as $k => $v) { + $kqn = new QName($k); + $vqn = new QName($v); + $xml_attr .= ' ' . $kqn->fqn() . '="' . $vqn->fqn() . '"'; + } + } + + // Store the attachment for mime encoding. + if (isset($options['attachment']) && + !PEAR::isError($options['attachment'])) { + $this->_attachments[] = $options['attachment']; + } + + if ($this->_section5) { + if ($xmlout_type) { + $xmlout_type = " xsi:type=\"$xmlout_type\""; + } + if (is_null($xmlout_value)) { + $xml = "\r\n<$xmlout_name$xmlout_type$xmlns$xmlout_arrayType" . + "$xml_attr xsi:nil=\"true\"/>"; + } else { + $xml = "\r\n<$xmlout_name$xmlout_type$xmlns$xmlout_arrayType" . + "$xml_attr>$xmlout_value</$xmlout_name>"; + } + } elseif ($type->name == 'Array' && !empty($options['keep_arrays_flat'])) { + $xml = $xmlout_value; + } else { + if (is_null($xmlout_value)) { + $xml = "\r\n<$xmlout_name$xmlns$xml_attr/>"; + } else { + $xml = "\r\n<$xmlout_name$xmlns$xml_attr>" . + $xmlout_value . "</$xmlout_name>"; + } + } + + return $xml; + } + + /** + * Converts a PHP type to a SOAP type. + * + * @param mixed $value The value to inspect. + * + * @return string The value's SOAP type. + */ + function _getType($value) + { + $type = gettype($value); + switch ($type) { + case 'object': + if (is_a($value, 'soap_value')) { + $type = $value->type; + } else { + $type = 'Struct'; + } + break; + + case 'array': + // Hashes are always handled as structs. + if ($this->_isHash($value)) { + $type = 'Struct'; + break; + } + if (count($value) > 1) { + // For non-wsdl structs that are all the same type + reset($value); + $value1 = next($value); + $value2 = next($value); + if (is_a($value1, 'SOAP_Value') && + is_a($value2, 'SOAP_Value') && + $value1->name != $value2->name) { + // This is a struct, not an array. + $type = 'Struct'; + break; + } + } + $type = 'Array'; + break; + + case 'integer': + case 'long': + $type = 'int'; + break; + + case 'boolean': + break; + + case 'double': + // double is deprecated in PHP 4.2 and later. + $type = 'float'; + break; + + case 'null': + $type = ''; + break; + + case 'string': + default: + break; + } + + return $type; + } + + function _multiArrayType($value, &$type, &$size, &$xml) + { + if (is_array($value)) { + // Seems we have a multi dimensional array, figure it out if we + // do. + for ($i = 0, $c = count($value); $i < $c; ++$i) { + $this->_multiArrayType($value[$i], $type, $size, $xml); + } + + $sz = count($value); + if ($size) { + $size = $sz . ',' . $size; + } else { + $size = $sz; + } + return 1; + } elseif (is_object($value)) { + $type = $value->type; + $xml .= $value->serialize($this); + } else { + $type = $this->_getType($value); + $xml .= $this->_serializeValue($value, new QName('item'), new QName($type)); + } + $size = null; + + return 1; + } + + /** + * Returns whether a type is a base64 type. + * + * @param string $type A type name. + * + * @return boolean True if the type name is a base64 type. + */ + function _isBase64Type($type) + { + return $type == 'base64' || $type == 'base64Binary'; + } + + /** + * Returns whether an array is a hash. + * + * @param array $a An array to check. + * + * @return boolean True if the specified array is a hash. + */ + function _isHash($a) + { + foreach (array_keys($a) as $k) { + // Checking the type is faster than regexp. + if (!is_int($k)) { + return true; + } + } + return false; + } + + function _un_htmlentities($string) + { + $trans_tbl = get_html_translation_table(HTML_ENTITIES); + $trans_tbl = array_flip($trans_tbl); + return strtr($string, $trans_tbl); + } + + /** + * Converts a SOAP_Value object into a PHP value. + */ + function _decode($soapval) + { + if (!$soapval instanceof SOAP_Value) { + return $soapval; + } + + if (is_array($soapval->value)) { + $isstruct = $soapval->type != 'Array'; + if ($isstruct) { + $classname = $this->_defaultObjectClassname; + if (isset($this->_type_translation[$soapval->tqn->fqn()])) { + // This will force an error in PHP if the class does not + // exist. + $classname = $this->_type_translation[$soapval->tqn->fqn()]; + } elseif (isset($this->_type_translation[$soapval->type])) { + // This will force an error in PHP if the class does not + // exist. + $classname = $this->_type_translation[$soapval->type]; + } elseif ($this->_auto_translation) { + if (class_exists($soapval->type)) { + $classname = $soapval->type; + } elseif ($this->_wsdl) { + $t = $this->_wsdl->getComplexTypeNameForElement($soapval->name, $soapval->namespace); + if ($t && class_exists($t)) { + $classname = $t; + } + } + } + $return = new $classname; + } else { + $return = array(); + } + + foreach ($soapval->value as $item) { + if ($isstruct) { + if ($this->_wsdl) { + // Get this child's WSDL information. + // /$soapval->ns/$soapval->type/$item->ns/$item->name + $child_type = $this->_wsdl->getComplexTypeChildType( + $soapval->namespace, + $soapval->name, + $item->namespace, + $item->name); + if ($child_type) { + $item->type = $child_type; + } + } + if ($item->type == 'Array') { + if (isset($return->{$item->name}) && + is_object($return->{$item->name})) { + $return->{$item->name} = $this->_decode($item); + } elseif (isset($return->{$item->name}) && + is_array($return->{$item->name})) { + $return->{$item->name}[] = $this->_decode($item); + } elseif (isset($return->{$item->name})) { + $return->{$item->name} = array( + $return->{$item->name}, + $this->_decode($item) + ); + } elseif (is_array($return)) { + $return[] = $this->_decode($item); + } else { + $return->{$item->name} = $this->_decode($item); + } + } elseif (isset($return->{$item->name})) { + $d = $this->_decode($item); + if (count(get_object_vars($return)) == 1) { + $isstruct = false; + $return = array($return->{$item->name}, $d); + } else { + $return->{$item->name} = array($return->{$item->name}, $d); + } + } else { + $return->{$item->name} = $this->_decode($item); + } + // Set the attributes as members in the class. + if (method_exists($return, '__set_attribute')) { + foreach ($soapval->attributes as $key => $value) { + call_user_func_array(array(&$return, + '__set_attribute'), + array($key, $value)); + } + } + } else { + if ($soapval->arrayType && is_a($item, 'SOAP_Value')) { + if ($this->_isBase64Type($item->type) && + !$this->_isBase64Type($soapval->arrayType)) { + // Decode the value if we're losing the base64 + // type information. + $item->value = base64_decode($item->value); + } + $item->type = $soapval->arrayType; + } + $return[] = $this->_decode($item); + } + } + + return $return; + } + + if ($soapval->type == 'boolean') { + if ($soapval->value != '0' && + strcasecmp($soapval->value, 'false') != 0) { + $soapval->value = true; + } else { + $soapval->value = false; + } + } elseif ($soapval->type && + isset($this->_typemap[SOAP_XML_SCHEMA_VERSION][$soapval->type])) { + // If we can, set variable type. + settype($soapval->value, + $this->_typemap[SOAP_XML_SCHEMA_VERSION][$soapval->type]); + } elseif ($soapval->type == 'Struct') { + $soapval->value = null; + } + + return $soapval->value; + } + + /** + * Creates the SOAP envelope with the SOAP envelop data. + * + * @param SOAP_Value $method SOAP_Value instance with the method name as + * the name, and the method arguments as the + * value. + * @param array $headers A list of additional SOAP_Header objects. + * @param string $encoding The charset of the SOAP message. + * @param array $options A list of encoding/serialization options. + * + * @return string The complete SOAP message. + */ + function makeEnvelope($method, $headers, $encoding = SOAP_DEFAULT_ENCODING, + $options = array()) + { + $smsg = $header_xml = $ns_string = ''; + + if ($headers) { + for ($i = 0, $c = count($headers); $i < $c; $i++) { + $header_xml .= $headers[$i]->serialize($this); + } + $header_xml = sprintf("<%s:Header>\r\n%s\r\n</%s:Header>\r\n", + SOAP_BASE::SOAPENVPrefix(), $header_xml, + SOAP_BASE::SOAPENVPrefix()); + } + + if (!isset($options['input']) || $options['input'] == 'parse') { + if (is_array($method)) { + for ($i = 0, $c = count($method); $i < $c; $i++) { + $smsg .= $method[$i]->serialize($this); + } + } else { + $smsg = $method->serialize($this); + } + } else { + $smsg = $method; + } + $body = sprintf("<%s:Body>%s\r\n</%s:Body>\r\n", + SOAP_BASE::SOAPENVPrefix(), $smsg, + SOAP_BASE::SOAPENVPrefix()); + + foreach ($this->_namespaces as $k => $v) { + $ns_string .= "\r\n " . sprintf('xmlns:%s="%s"', $v, $k); + } + if ($this->_namespace) { + $ns_string .= "\r\n " . sprintf('xmlns="%s"', $this->_namespace); + } + + /* If 'use' == 'literal', do not put in the encodingStyle. This is + * denoted by $this->_section5 being false. 'use' can be defined at a + * more granular level than we are dealing with here, so this does not + * work for all services. */ + $xml = sprintf('<?xml version="1.0" encoding="%s"?>%s<%s:Envelope%s', + $encoding, "\r\n", SOAP_BASE::SOAPENVPrefix(), + $ns_string); + if ($this->_section5) { + $xml .= "\r\n " . sprintf('%s:encodingStyle="%s"', + SOAP_BASE::SOAPENVPrefix(), + SOAP_SCHEMA_ENCODING); + } + $xml .= sprintf('>%s%s%s</%s:Envelope>' . "\r\n", + "\r\n", $header_xml, $body, SOAP_BASE::SOAPENVPrefix()); + + return $xml; + } + + function _makeMimeMessage($xml, $encoding = SOAP_DEFAULT_ENCODING) + { + if (!@include_once 'Mail/mimePart.php') { + return $this->_raiseSoapFault('MIME messages are unsupported, the Mail_Mime package is not installed'); + } + + // Encode any attachments. See http://www.w3.org/TR/SOAP-attachments + // Now we have to mime encode the message. + $params = array('content_type' => 'multipart/related; type="text/xml"'); + $msg = new Mail_mimePart('', $params); + + // Add the xml part. + $params['content_type'] = 'text/xml'; + $params['charset'] = $encoding; + $msg->addSubPart($xml, $params); + + // Add the attachements + for ($i = 0, $c = count($this->_attachments); $i < $c; ++$i) { + $msg->addSubPart($this->_attachments[$i]['body'], + $this->_attachments[$i]); + } + + return $msg->encode(); + } + + // TODO: this needs to be used from the Transport system. + function _makeDIMEMessage($xml) + { + if (!@include_once 'Net/DIME.php') { + return $this->_raiseSoapFault('DIME messages are unsupported, the Net_DIME package is not installed'); + } + + // Encode any attachments. See + // http://search.ietf.org/internet-drafts/draft-nielsen-dime-soap-00.txt + // Now we have to DIME encode the message + $dime = new Net_DIME_Message(); + $msg = $dime->encodeData($xml, SOAP_ENVELOP, null, NET_DIME_TYPE_URI); + + // Add the attachments. + $c = count($this->_attachments); + for ($i = 0; $i < $c; $i++) { + $msg .= $dime->encodeData($this->_attachments[$i]['body'], + $this->_attachments[$i]['content_type'], + $this->_attachments[$i]['cid'], + NET_DIME_TYPE_MEDIA); + } + $msg .= $dime->endMessage(); + + return $msg; + } + + function _decodeMimeMessage(&$data, &$headers, &$attachments) + { + if (!@include_once 'Mail/mimeDecode.php') { + return $this->_raiseSoapFault('MIME messages are unsupported, the Mail_Mime package is not installed'); + } + + $params['include_bodies'] = true; + $params['decode_bodies'] = true; + $params['decode_headers'] = true; + + // Lame thing to have to do for decoding. + $decoder = new Mail_mimeDecode($data); + $structure = $decoder->decode($params); + + if (isset($structure->body)) { + $data = $structure->body; + $headers = $structure->headers; + + return; + } elseif (isset($structure->parts)) { + $data = $structure->parts[0]->body; + $headers = array_merge($structure->headers, + $structure->parts[0]->headers); + if (count($structure->parts) <= 1) { + return; + } + + $mime_parts = array_splice($structure->parts, 1); + // Prepare the parts for the SOAP parser. + for ($i = 0, $c = count($mime_parts); $i < $c; $i++) { + $p = $mime_parts[$i]; + if (isset($p->headers['content-location'])) { + // TODO: modify location per SwA note section 3 + // http://www.w3.org/TR/SOAP-attachments + $attachments[$p->headers['content-location']] = $p->body; + } else { + $cid = 'cid:' . substr($p->headers['content-id'], 1, -1); + $attachments[$cid] = $p->body; + } + } + + return; + } + + $this->_raiseSoapFault('Mime parsing error', '', '', 'Server'); + } + + function _decodeDIMEMessage(&$data, &$headers, &$attachments) + { + if (!@include_once 'Net/DIME.php') { + return $this->_raiseSoapFault('DIME messages are unsupported, the Net_DIME package is not installed'); + } + + // This SHOULD be moved to the transport layer, e.g. PHP itself should + // handle parsing DIME ;) + $dime = new Net_DIME_Message(); + $err = $dime->decodeData($data); + if (PEAR::isError($err)) { + $this->_raiseSoapFault('Failed to decode the DIME message!', '', '', 'Server'); + return; + } + if (strcasecmp($dime->parts[0]['type'], SOAP_ENVELOP) != 0) { + $this->_raiseSoapFault('DIME record 1 is not a SOAP envelop!', '', '', 'Server'); + return; + } + + $data = $dime->parts[0]['data']; + // Fake it for now. + $headers['content-type'] = 'text/xml'; + $c = count($dime->parts); + for ($i = 0; $i < $c; $i++) { + $part =& $dime->parts[$i]; + // We need to handle URI's better. + $id = strncmp($part['id'], 'cid:', 4) + ? 'cid:' . $part['id'] + : $part['id']; + $attachments[$id] = $part['data']; + } + } + + /** + * Explicitly sets the translation for a specific class. + * + * Auto translation works for all cases, but opens ANY class in the script + * to be used as a data type, and may not be desireable. + * + * @param string $type A SOAP type. + * @param string $class A PHP class name. + */ + function setTypeTranslation($type, $class = null) + { + $tq = new QName($type); + if (!$class) { + $class = $tq->name; + } + $this->_type_translation[$type]=$class; + } + +} + +/** + * Class used to handle QNAME values in XML. + * + * @package SOAP + * @author Shane Caraveo <shane@php.net> Conversion to PEAR and updates + */ +class QName +{ + var $name = ''; + var $ns = ''; + var $namespace = ''; + + function QName($name, $namespace = '') + { + if ($name && $name[0] == '{') { + preg_match('/\{(.*?)\}(.*)/', $name, $m); + $this->name = $m[2]; + $this->namespace = $m[1]; + } elseif (substr_count($name, ':') == 1) { + $s = explode(':', $name); + $s = array_reverse($s); + $this->name = $s[0]; + $this->ns = $s[1]; + $this->namespace = $namespace; + } else { + $this->name = $name; + $this->namespace = $namespace; + } + + // A little more magic than should be in a qname. + $p = strpos($this->name, '['); + if ($p) { + // TODO: Need to re-examine this logic later. + // Chop off []. + $this->arraySize = explode(',', substr($this->name, $p + 1, -$p - 2)); + $this->arrayInfo = substr($this->name, $p); + $this->name = substr($this->name, 0, $p); + } + } + + function fqn() + { + if ($this->namespace) { + return '{' . $this->namespace . '}' . $this->name; + } elseif ($this->ns) { + return $this->ns . ':' . $this->name; + } + return $this->name; + } + +} diff --git a/lib/PEAR/SOAP/Client.php b/lib/PEAR/SOAP/Client.php new file mode 100644 index 0000000000..54842be2a1 --- /dev/null +++ b/lib/PEAR/SOAP/Client.php @@ -0,0 +1,837 @@ +<?php +/** + * This file contains the code for the SOAP client. + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 2.02 of the PHP license, + * that is bundled with this package in the file LICENSE, and is available at + * through the world-wide-web at http://www.php.net/license/2_02.txt. If you + * did not receive a copy of the PHP license and are unable to obtain it + * through the world-wide-web, please send a note to license@php.net so we can + * mail you a copy immediately. + * + * @category Web Services + * @package SOAP + * @author Dietrich Ayala <dietrich@ganx4.com> Original Author + * @author Shane Caraveo <Shane@Caraveo.com> Port to PEAR and more + * @author Chuck Hagenbuch <chuck@horde.org> Maintenance + * @author Jan Schneider <jan@horde.org> Maintenance + * @copyright 2003-2005 The PHP Group + * @license http://www.php.net/license/2_02.txt PHP License 2.02 + * @link http://pear.php.net/package/SOAP + */ + +/** SOAP_Value */ +require_once 'SOAP/Value.php'; +require_once 'SOAP/Base.php'; +require_once 'SOAP/Transport.php'; +require_once 'SOAP/WSDL.php'; +require_once 'SOAP/Fault.php'; +require_once 'SOAP/Parser.php'; + +// Arnaud: the following code was taken from DataObject and adapted to suit + +// this will be horrifically slow!!!! +// NOTE: Overload SEGFAULTS ON PHP4 + Zend Optimizer +// these two are BC/FC handlers for call in PHP4/5 + +/** + * @package SOAP + */ +if (!class_exists('SOAP_Client_Overload', false)) { + if (substr(zend_version(), 0, 1) > 1) { + class SOAP_Client_Overload extends SOAP_Base { + function __call($method, $args) + { + $return = null; + $this->_call($method, $args, $return); + return $return; + } + } + } else { + if (!function_exists('clone')) { + eval('function clone($t) { return $t; }'); + } + eval(' + class SOAP_Client_Overload extends SOAP_Base { + function __call($method, $args, &$return) + { + return $this->_call($method, $args, $return); + } + }'); + } +} + +/** + * SOAP Client Class + * + * This class is the main interface for making soap requests. + * + * basic usage:<code> + * $soapclient = new SOAP_Client( string path [ , boolean wsdl] ); + * echo $soapclient->call( string methodname [ , array parameters] ); + * </code> + * or, if using PHP 5+ or the overload extension:<code> + * $soapclient = new SOAP_Client( string path [ , boolean wsdl] ); + * echo $soapclient->methodname( [ array parameters] ); + * </code> + * + * Originally based on SOAPx4 by Dietrich Ayala + * http://dietrich.ganx4.com/soapx4 + * + * @access public + * @package SOAP + * @author Shane Caraveo <shane@php.net> Conversion to PEAR and updates + * @author Stig Bakken <ssb@fast.no> Conversion to PEAR + * @author Dietrich Ayala <dietrich@ganx4.com> Original Author + */ +class SOAP_Client extends SOAP_Client_Overload +{ + /** + * Communication endpoint. + * + * Currently the following transport formats are supported: + * - HTTP + * - SMTP + * + * Example endpoints: + * http://www.example.com/soap/server.php + * https://www.example.com/soap/server.php + * mailto:soap@example.com + * + * @see SOAP_Client() + * @var string + */ + var $_endpoint = ''; + + /** + * The SOAP PORT name that is used by the client. + * + * @var string + */ + var $_portName = ''; + + /** + * Endpoint type e.g. 'wdsl'. + * + * @var string + */ + var $_endpointType = ''; + + /** + * The received xml. + * + * @var string + */ + var $xml; + + /** + * The outgoing and incoming data stream for debugging. + * + * @var string + */ + var $wire; + + /** + * The outgoing data stream for debugging. + * + * @var string + */ + var $_last_request = null; + + /** + * The incoming data stream for debugging. + * + * @var string + */ + var $_last_response = null; + + /** + * Options. + * + * @var array + */ + var $_options = array('trace' => false); + + /** + * The character encoding used for XML parser, etc. + * + * @var string + */ + var $_encoding = SOAP_DEFAULT_ENCODING; + + /** + * The array of SOAP_Headers that we are sending. + * + * @var array + */ + var $headersOut = null; + + /** + * The headers we recieved back in the response. + * + * @var array + */ + var $headersIn = null; + + /** + * Options for the HTTP_Request class (see HTTP/Request.php). + * + * @var array + */ + var $_proxy_params = array(); + + /** + * The SOAP_Transport instance. + * + * @var SOAP_Transport + */ + var $_soap_transport = null; + + /** + * Constructor. + * + * @access public + * + * @param string $endpoint An URL. + * @param boolean $wsdl Whether the endpoint is a WSDL file. + * @param string $portName The service's port name to use. + * @param array $proxy_params Options for the HTTP_Request class + * @see HTTP_Request + * @param boolean|string $cache Use WSDL caching? The cache directory if + * a string. + */ + function SOAP_Client($endpoint, $wsdl = false, $portName = false, + $proxy_params = array(), $cache = false) + { + parent::SOAP_Base('Client'); + + $this->_endpoint = $endpoint; + $this->_portName = $portName; + $this->_proxy_params = $proxy_params; + + // This hack should perhaps be removed as it might cause unexpected + // behaviour. + $wsdl = $wsdl + ? $wsdl + : strtolower(substr($endpoint, -4)) == 'wsdl'; + + // make values + if ($wsdl) { + $this->_endpointType = 'wsdl'; + // instantiate wsdl class + $this->_wsdl = new SOAP_WSDL($this->_endpoint, + $this->_proxy_params, + $cache); + if ($this->_wsdl->fault) { + $this->_raiseSoapFault($this->_wsdl->fault); + } + } + } + + function _reset() + { + $this->xml = null; + $this->wire = null; + $this->_last_request = null; + $this->_last_response = null; + $this->headersIn = null; + $this->headersOut = null; + } + + /** + * Sets the character encoding. + * + * Limited to 'UTF-8', 'US_ASCII' and 'ISO-8859-1'. + * + * @access public + * + * @param string encoding + * + * @return mixed SOAP_Fault on error. + */ + function setEncoding($encoding) + { + if (in_array($encoding, $this->_encodings)) { + $this->_encoding = $encoding; + return; + } + return $this->_raiseSoapFault('Invalid Encoding'); + } + + /** + * Adds a header to the envelope. + * + * @access public + * + * @param SOAP_Header $soap_value A SOAP_Header or an array with the + * elements 'name', 'namespace', + * 'mustunderstand', and 'actor' to send + * as a header. + */ + function addHeader($soap_value) + { + // Add a new header to the message. + if (is_a($soap_value, 'SOAP_Header')) { + $this->headersOut[] = $soap_value; + } elseif (is_array($soap_value)) { + // name, value, namespace, mustunderstand, actor + $this->headersOut[] = new SOAP_Header($soap_value[0], + null, + $soap_value[1], + $soap_value[2], + $soap_value[3]); + } else { + $this->_raiseSoapFault('Invalid parameter provided to addHeader(). Must be an array or a SOAP_Header.'); + } + } + + /** + * Calls a method on the SOAP endpoint. + * + * The namespace parameter is overloaded to accept an array of options + * that can contain data necessary for various transports if it is used as + * an array, it MAY contain a namespace value and a soapaction value. If + * it is overloaded, the soapaction parameter is ignored and MUST be + * placed in the options array. This is done to provide backwards + * compatibility with current clients, but may be removed in the future. + * The currently supported values are: + * - 'namespace' + * - 'soapaction' + * - 'timeout': HTTP socket timeout + * - 'transfer-encoding': SMTP transport, Content-Transfer-Encoding: header + * - 'from': SMTP transport, From: header + * - 'subject': SMTP transport, Subject: header + * - 'headers': SMTP transport, hash of extra SMTP headers + * - 'attachments': what encoding to use for attachments (Mime, Dime) + * - 'trace': whether to trace the SOAP communication + * - 'style': 'document' or 'rpc'; when set to 'document' the parameters + * are not wrapped inside a tag with the SOAP action name + * - 'use': 'literal' for literal encoding, anything else for section 5 + * encoding; when set to 'literal' SOAP types will be omitted. + * - 'keep_arrays_flat': use the tag name multiple times for each element + * when passing in an array in literal mode + * - 'no_type_prefix': supress adding of the namespace prefix + * + * @access public + * + * @param string $method The method to call. + * @param array $params The method parameters. + * @param string|array $namespace Namespace or hash with options. Note: + * most options need to be repeated for + * SOAP_Value instances. + * @param string $soapAction + * + * @return mixed The method result or a SOAP_Fault on error. + */ + function call($method, $params, $namespace = false, $soapAction = false) + { + $this->headersIn = null; + $this->_last_request = null; + $this->_last_response = null; + $this->wire = null; + $this->xml = null; + + $soap_data = $this->_generate($method, $params, $namespace, $soapAction); + if (PEAR::isError($soap_data)) { + $fault = $this->_raiseSoapFault($soap_data); + return $fault; + } + + // _generate() may have changed the endpoint if the WSDL has more + // than one service, so we need to see if we need to generate a new + // transport to hook to a different URI. Since the transport protocol + // can also change, we need to get an entirely new object. This could + // probably be optimized. + if (!$this->_soap_transport || + $this->_endpoint != $this->_soap_transport->url) { + $this->_soap_transport = SOAP_Transport::getTransport($this->_endpoint); + if (PEAR::isError($this->_soap_transport)) { + $fault = $this->_raiseSoapFault($this->_soap_transport); + $this->_soap_transport = null; + return $fault; + } + } + $this->_soap_transport->encoding = $this->_encoding; + + // Send the message. + $transport_options = array_merge_recursive($this->_proxy_params, + $this->_options); + $this->xml = $this->_soap_transport->send($soap_data, $transport_options); + + // Save the wire information for debugging. + if ($this->_options['trace']) { + $this->_last_request = $this->_soap_transport->outgoing_payload; + $this->_last_response = $this->_soap_transport->incoming_payload; + $this->wire = $this->getWire(); + } + if ($this->_soap_transport->fault) { + $fault = $this->_raiseSoapFault($this->xml); + return $fault; + } + + if (isset($this->_options['result']) && + $this->_options['result'] != 'parse') { + return $this->xml; + } + + $this->__result_encoding = $this->_soap_transport->result_encoding; + + $result = $this->parseResponse($this->xml, $this->__result_encoding, + $this->_soap_transport->attachments); + return $result; + } + + /** + * Sets an option to use with the transport layers. + * + * For example: + * <code> + * $soapclient->setOpt('curl', CURLOPT_VERBOSE, 1) + * </code> + * to pass a specific option to curl if using an SSL connection. + * + * @access public + * + * @param string $category Category to which the option applies or option + * name. + * @param string $option An option name if $category is a category name, + * an option value if $category is an option name. + * @param string $value An option value if $category is a category + * name. + */ + function setOpt($category, $option, $value = null) + { + if (!is_null($value)) { + if (!isset($this->_options[$category])) { + $this->_options[$category] = array(); + } + $this->_options[$category][$option] = $value; + } else { + $this->_options[$category] = $option; + } + } + + /** + * Call method supporting the overload extension. + * + * If the overload extension is loaded, you can call the client class with + * a soap method name: + * <code> + * $soap = new SOAP_Client(....); + * $value = $soap->getStockQuote('MSFT'); + * </code> + * + * @access public + * + * @param string $method The method to call. + * @param array $params The method parameters. + * @param mixed $return_value Will get the method's return value + * assigned. + * + * @return boolean Always true. + */ + function _call($method, $params, &$return_value) + { + // Overloading lowercases the method name, we need to look into the + // WSDL and try to find the correct method name to get the correct + // case for the call. + if ($this->_wsdl) { + $this->_wsdl->matchMethod($method); + } + + $return_value = $this->call($method, $params); + + return true; + } + + /** + * Returns the XML content of the last SOAP request. + * + * @return string The last request. + */ + function getLastRequest() + { + return $this->_last_request; + } + + /** + * Returns the XML content of the last SOAP response. + * + * @return string The last response. + */ + function getLastResponse() + { + return $this->_last_response; + } + + /** + * Sets the SOAP encoding. + * + * The default encoding is section 5 encoded. + * + * @param string $use Either 'literal' or 'encoded' (section 5). + */ + function setUse($use) + { + $this->_options['use'] = $use; + } + + /** + * Sets the SOAP encoding style. + * + * The default style is rpc. + * + * @param string $style Either 'document' or 'rpc'. + */ + function setStyle($style) + { + $this->_options['style'] = $style; + } + + /** + * Sets whether to trace the traffic on the transport level. + * + * @see getWire() + * + * @param boolean $trace + */ + function setTrace($trace) + { + $this->_options['trace'] = $trace; + } + + /** + * Generates the complete XML SOAP message for an RPC call. + * + * @see call() + * + * @param string $method The method to call. + * @param array $params The method parameters. + * @param string|array $namespace Namespace or hash with options. Note: + * most options need to be repeated for + * SOAP_Value instances. + * @param string $soapAction + * + * @return string The SOAP message including envelope. + */ + function _generate($method, $params, $namespace = false, + $soapAction = false) + { + $this->fault = null; + $this->_options['input'] = 'parse'; + $this->_options['result'] = 'parse'; + $this->_options['parameters'] = false; + + if ($params && !is_array($params)) { + $params = array($params); + } + + if (is_array($namespace)) { + // Options passed as a hash. + foreach ($namespace as $optname => $opt) { + $this->_options[strtolower($optname)] = $opt; + } + } else { + // We'll place $soapAction into our array for usage in the + // transport. + if ($soapAction) { + $this->_options['soapaction'] = $soapAction; + } + if ($namespace) { + $this->_options['namespace'] = $namespace; + } + } + if (isset($this->_options['namespace'])) { + $namespace = $this->_options['namespace']; + } else { + $namespace = false; + } + + if ($this->_endpointType == 'wsdl') { + $this->_setSchemaVersion($this->_wsdl->xsd); + + // Get port name. + if (!$this->_portName) { + $this->_portName = $this->_wsdl->getPortName($method); + } + if (PEAR::isError($this->_portName)) { + return $this->_raiseSoapFault($this->_portName); + } + + // Get endpoint. + $this->_endpoint = $this->_wsdl->getEndpoint($this->_portName); + if (PEAR::isError($this->_endpoint)) { + return $this->_raiseSoapFault($this->_endpoint); + } + + // Get operation data. + $opData = $this->_wsdl->getOperationData($this->_portName, $method); + + if (PEAR::isError($opData)) { + return $this->_raiseSoapFault($opData); + } + $namespace = isset($opData['namespace'])?$opData['namespace']:''; + $this->_options['style'] = $opData['style']; + $this->_options['use'] = $opData['input']['use']; + $this->_options['soapaction'] = $opData['soapAction']; + + // Set input parameters. + if ($this->_options['input'] == 'parse') { + $this->_options['parameters'] = $opData['parameters']; + $nparams = array(); + if (isset($opData['input']['parts']) && + count($opData['input']['parts'])) { + foreach ($opData['input']['parts'] as $name => $part) { + $xmlns = ''; + $attrs = array(); + // Is the name a complex type? + if (isset($part['element'])) { + $xmlns = $this->_wsdl->namespaces[$part['namespace']]; + $part = $this->_wsdl->elements[$part['namespace']][$part['type']]; + $name = $part['name']; + } + if (isset($params[$name]) || + $this->_wsdl->getDataHandler($name, $part['namespace'])) { + $nparams[$name] =& $params[$name]; + } else { + // We now force an associative array for + // parameters if using WSDL. + return $this->_raiseSoapFault("The named parameter $name is not in the call parameters."); + } + if (gettype($nparams[$name]) != 'object' || + !$nparams[$name] instanceof SOAP_Value) { + // Type is likely a qname, split it apart, and get + // the type namespace from WSDL. + $qname = new QName($part['type']); + if ($qname->ns) { + $type_namespace = $this->_wsdl->namespaces[$qname->ns]; + } elseif (isset($part['namespace'])) { + $type_namespace = $this->_wsdl->namespaces[$part['namespace']]; + } else { + $type_namespace = null; + } + $qname->namespace = $type_namespace; + $pqname = $name; + if ($xmlns) { + $pqname = '{' . $xmlns . '}' . $name; + } + $nparams[$name] = new SOAP_Value($pqname, + $qname->fqn(), + $nparams[$name], + $attrs); + } else { + // WSDL fixups to the SOAP value. + } + } + } + $params =& $nparams; + unset($nparams); + } + } else { + $this->_setSchemaVersion(SOAP_XML_SCHEMA_VERSION); + } + + // Serialize the message. + $this->_section5 = (!isset($this->_options['use']) || + $this->_options['use'] != 'literal'); + + if (!isset($this->_options['style']) || + $this->_options['style'] == 'rpc') { + $this->_options['style'] = 'rpc'; + $this->docparams = true; + $mqname = new QName($method, $namespace); + $methodValue = new SOAP_Value($mqname->fqn(), 'Struct', $params, + array(), $this->_options); + $soap_msg = $this->makeEnvelope($methodValue, + $this->headersOut, + $this->_encoding, + $this->_options); + } else { + if (!$params) { + $mqname = new QName($method, $namespace); + $params = new SOAP_Value($mqname->fqn(), 'Struct', null); + } elseif ($this->_options['input'] == 'parse') { + if (is_array($params)) { + $nparams = array(); + $keys = array_keys($params); + foreach ($keys as $k) { + if (gettype($params[$k]) != 'object') { + $nparams[] = new SOAP_Value($k, + false, + $params[$k]); + } else { + $nparams[] =& $params[$k]; + } + } + $params =& $nparams; + } + if ($this->_options['parameters']) { + $mqname = new QName($method, $namespace); + $params = new SOAP_Value($mqname->fqn(), + 'Struct', + $params); + } + } + $soap_msg = $this->makeEnvelope($params, + $this->headersOut, + $this->_encoding, + $this->_options); + } + $this->headersOut = null; + + if (PEAR::isError($soap_msg)) { + return $this->_raiseSoapFault($soap_msg); + } + + // Handle MIME or DIME encoding. + // TODO: DIME encoding should move to the transport, do it here for + // now and for ease of getting it done. + if (count($this->_attachments)) { + if ((isset($this->_options['attachments']) && + $this->_options['attachments'] == 'Mime') || + isset($this->_options['Mime'])) { + $soap_msg = $this->_makeMimeMessage($soap_msg, $this->_encoding); + } else { + // default is dime + $soap_msg = $this->_makeDIMEMessage($soap_msg, $this->_encoding); + $this->_options['headers']['Content-Type'] = 'application/dime'; + } + if (PEAR::isError($soap_msg)) { + return $this->_raiseSoapFault($soap_msg); + } + } + + // Instantiate client. + if (is_array($soap_msg)) { + $soap_data = $soap_msg['body']; + if (count($soap_msg['headers'])) { + if (isset($this->_options['headers'])) { + $this->_options['headers'] = array_merge($this->_options['headers'], $soap_msg['headers']); + } else { + $this->_options['headers'] = $soap_msg['headers']; + } + } + } else { + $soap_data = $soap_msg; + } + + return $soap_data; + } + + /** + * Parses a SOAP response. + * + * @see SOAP_Parser:: + * + * @param string $response XML content of SOAP response. + * @param string $encoding Character set encoding, defaults to 'UTF-8'. + * @param array $attachments List of attachments. + */ + function parseResponse($response, $encoding, $attachments) + { + // Parse the response. + $response = new SOAP_Parser($response, $encoding, $attachments); + if ($response->fault) { + $fault = $this->_raiseSoapFault($response->fault); + return $fault; + } + + // Return array of parameters. + $return = $response->getResponse(); + $headers = $response->getHeaders(); + if ($headers) { + $this->headersIn = $this->_decodeResponse($headers, false); + } + + $decoded = $this->_decodeResponse($return); + return $decoded; + } + + /** + * Converts a complex SOAP_Value into a PHP Array + * + * @param SOAP_Value $response Value object. + * @param boolean $shift + * + * @return array + */ + function _decodeResponse($response, $shift = true) + { + if (!$response) { + $decoded = null; + return $decoded; + } + + // Check for valid response. + if (PEAR::isError($response)) { + $fault = $this->_raiseSoapFault($response); + return $fault; + } elseif (!$response instanceof SOAP_Value) { + $fault = $this->_raiseSoapFault("Didn't get SOAP_Value object back from client"); + return $fault; + } + + // Decode to native php datatype. + $returnArray = $this->_decode($response); + + // Fault? + if (PEAR::isError($returnArray)) { + $fault = $this->_raiseSoapFault($returnArray); + return $fault; + } + + if (is_object($returnArray) && + strcasecmp(get_class($returnArray), 'stdClass') == 0) { + $returnArray = get_object_vars($returnArray); + } + + if (is_array($returnArray)) { + if (isset($returnArray['faultcode']) || + isset($returnArray[SOAP_BASE::SOAPENVPrefix().':faultcode'])) { + $faultcode = $faultstring = $faultdetail = $faultactor = ''; + foreach ($returnArray as $k => $v) { + if (stristr($k, 'faultcode')) $faultcode = $v; + if (stristr($k, 'faultstring')) $faultstring = $v; + if (stristr($k, 'detail')) $faultdetail = $v; + if (stristr($k, 'faultactor')) $faultactor = $v; + } + $fault = $this->_raiseSoapFault($faultstring, $faultdetail, + $faultactor, $faultcode); + return $fault; + } + // Return array of return values. + if ($shift && count($returnArray) == 1) { + $decoded = array_shift($returnArray); + return $decoded; + } + return $returnArray; + } + + return $returnArray; + } + + /** + * Returns the outgoing and incoming traffic on the transport level. + * + * Tracing has to be enabled. + * + * @see setTrace() + * + * @return string The complete traffic between the client and the server. + */ + function getWire() + { + if ($this->_options['trace'] && + ($this->_last_request || $this->_last_response)) { + return "OUTGOING:\n\n" . + $this->_last_request . + "\n\nINCOMING\n\n" . + preg_replace("/></",">\r\n<", $this->_last_response); + } + + return null; + } + +} diff --git a/lib/PEAR/SOAP/Fault.php b/lib/PEAR/SOAP/Fault.php new file mode 100644 index 0000000000..d9b1c9a500 --- /dev/null +++ b/lib/PEAR/SOAP/Fault.php @@ -0,0 +1,129 @@ +<?php +/** + * This file contains the SOAP_Fault class, used for all error objects in this + * package. + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 2.02 of the PHP license, + * that is bundled with this package in the file LICENSE, and is available at + * through the world-wide-web at http://www.php.net/license/2_02.txt. If you + * did not receive a copy of the PHP license and are unable to obtain it + * through the world-wide-web, please send a note to license@php.net so we can + * mail you a copy immediately. + * + * @category Web Services + * @package SOAP + * @author Dietrich Ayala <dietrich@ganx4.com> Original Author + * @author Shane Caraveo <Shane@Caraveo.com> Port to PEAR and more + * @author Chuck Hagenbuch <chuck@horde.org> Maintenance + * @author Jan Schneider <jan@horde.org> Maintenance + * @copyright 2003-2006 The PHP Group + * @license http://www.php.net/license/2_02.txt PHP License 2.02 + * @link http://pear.php.net/package/SOAP + */ + +/** PEAR_Error */ +require_once 'PEAR.php'; + +/** + * PEAR::Error wrapper used to match SOAP Faults to PEAR Errors + * + * SOAP_Fault can provide a complete backtrace of the error. Revealing these + * details in a public web services is a bad idea because it can be used by + * attackers. Thus you have to enable backtrace information in SOAP_Fault + * responses by putting the following code in your script after your + * "require_once 'SOAP/Server.php';" line: + * + * <code> + * $backtrace =& PEAR::getStaticProperty('SOAP_Fault', 'backtrace'); + * $backtrace = true; + * </code> + * + * @package SOAP + * @access public + * @author Shane Caraveo <Shane@Caraveo.com> Port to PEAR and more + * @author Dietrich Ayala <dietrich@ganx4.com> Original Author + */ +class SOAP_Fault extends PEAR_Error +{ + /** + * Constructor. + * + * @param string $faultstring Message string for fault. + * @param mixed $faultcode The faultcode. + * @param mixed $faultactor + * @param mixed $detail @see PEAR_Error + * @param array $mode @see PEAR_Error + * @param array $options @see PEAR_Error + */ + function SOAP_Fault($faultstring = 'unknown error', $faultcode = 'Client', + $faultactor = null, $detail = null, $mode = null, + $options = null) + { + parent::PEAR_Error($faultstring, $faultcode, $mode, $options, $detail); + if ($faultactor) { + $this->error_message_prefix = $faultactor; + } + } + + /** + * Returns a SOAP XML message that can be sent as a server response. + * + * @return string + */ + function message($encoding = SOAP_DEFAULT_ENCODING) + { + $msg = new SOAP_Base(); + $params = array(); + $params[] = new SOAP_Value('faultcode', 'QName', SOAP_BASE::SOAPENVPrefix().':' . $this->code); + $params[] = new SOAP_Value('faultstring', 'string', $this->message); + $params[] = new SOAP_Value('faultactor', 'anyURI', $this->error_message_prefix); + if (PEAR::getStaticProperty('SOAP_Fault', 'backtrace') && + isset($this->backtrace)) { + $params[] = new SOAP_Value('detail', 'string', $this->backtrace); + } else { + $params[] = new SOAP_Value('detail', 'string', $this->userinfo); + } + + $methodValue = new SOAP_Value('{' . SOAP_ENVELOP . '}Fault', 'Struct', $params); + $headers = null; + return $msg->makeEnvelope($methodValue, $headers, $encoding); + } + + /** + * Returns a simple native PHP array containing the fault data. + * + * @return array + */ + function getFault() + { + $fault = new stdClass(); + $fault->faultcode = $this->code; + $fault->faultstring = $this->message; + $fault->faultactor = $this->error_message_prefix; + $fault->detail = $this->userinfo; + return $fault; + } + + /** + * Returns the SOAP actor for the fault. + * + * @return string + */ + function getActor() + { + return $this->error_message_prefix; + } + + /** + * Returns the fault detail. + * + * @return string + */ + function getDetail() + { + return $this->userinfo; + } + +} diff --git a/lib/PEAR/SOAP/Parser.php b/lib/PEAR/SOAP/Parser.php new file mode 100644 index 0000000000..0cb89cbdbb --- /dev/null +++ b/lib/PEAR/SOAP/Parser.php @@ -0,0 +1,499 @@ +<?php +/** + * This file contains the code for the SOAP message parser. + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 2.02 of the PHP license, + * that is bundled with this package in the file LICENSE, and is available at + * through the world-wide-web at http://www.php.net/license/2_02.txt. If you + * did not receive a copy of the PHP license and are unable to obtain it + * through the world-wide-web, please send a note to license@php.net so we can + * mail you a copy immediately. + * + * @category Web Services + * @package SOAP + * @author Dietrich Ayala <dietrich@ganx4.com> Original Author + * @author Shane Caraveo <Shane@Caraveo.com> Port to PEAR and more + * @author Chuck Hagenbuch <chuck@horde.org> Maintenance + * @author Jan Schneider <jan@horde.org> Maintenance + * @copyright 2003-2005 The PHP Group + * @license http://www.php.net/license/2_02.txt PHP License 2.02 + * @link http://pear.php.net/package/SOAP + */ + +require_once 'SOAP/Base.php'; +require_once 'SOAP/Value.php'; + +/** + * SOAP Parser + * + * This class is used by SOAP::Message and SOAP::Server to parse soap + * packets. Originally based on SOAPx4 by Dietrich Ayala + * http://dietrich.ganx4.com/soapx4 + * + * @access public + * @package SOAP + * @author Shane Caraveo <shane@php.net> Conversion to PEAR and updates + * @author Dietrich Ayala <dietrich@ganx4.com> Original Author + */ +class SOAP_Parser extends SOAP_Base +{ + var $status = ''; + var $position = 0; + var $depth = 0; + var $default_namespace = ''; + var $message = array(); + var $depth_array = array(); + var $parent = 0; + var $root_struct_name = array(); + var $header_struct_name = array(); + var $curent_root_struct_name = ''; + var $root_struct = array(); + var $header_struct = array(); + var $curent_root_struct = 0; + var $references = array(); + var $need_references = array(); + + /** + * Used to handle non-root elements before root body element. + * + * @var integer + */ + var $bodyDepth; + + /** + * Constructor. + * + * @param string $xml XML content. + * @param string $encoding Character set encoding, defaults to 'UTF-8'. + * @param array $attachments List of attachments. + */ + function SOAP_Parser($xml, $encoding = SOAP_DEFAULT_ENCODING, + $attachments = null) + { + parent::SOAP_Base('Parser'); + $this->_setSchemaVersion(SOAP_XML_SCHEMA_VERSION); + + $this->attachments = $attachments; + + // Check the XML tag for encoding. + if (preg_match('/<\?xml[^>]+encoding\s*?=\s*?(\'([^\']*)\'|"([^"]*)")[^>]*?[\?]>/', $xml, $m)) { + $encoding = strtoupper($m[2] ? $m[2] : $m[3]); + } + + // Determine where in the message we are (envelope, header, body, + // method). Check whether content has been read. + if (!empty($xml)) { + // Prepare the XML parser. + $parser = xml_parser_create($encoding); + xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0); + xml_set_object($parser, $this); + xml_set_element_handler($parser, '_startElement', '_endElement'); + xml_set_character_data_handler($parser, '_characterData'); + + // Some lame SOAP implementations add nul bytes at the end of the + // SOAP stream, and expat chokes on that. + if ($xml[strlen($xml) - 1] == 0) { + $xml = trim($xml); + } + + // Parse the XML file. + if (!xml_parse($parser, $xml, true)) { + $err = sprintf('XML error on line %d col %d byte %d %s', + xml_get_current_line_number($parser), + xml_get_current_column_number($parser), + xml_get_current_byte_index($parser), + xml_error_string(xml_get_error_code($parser))); + $this->_raiseSoapFault($err, htmlspecialchars($xml)); + } + xml_parser_free($parser); + } + } + + /** + * Returns an array of responses. + * + * After parsing a SOAP message, use this to get the response. + * + * @return array + */ + function getResponse() + { + if (!empty($this->root_struct[0])) { + return $this->_buildResponse($this->root_struct[0]); + } else { + return $this->_raiseSoapFault('Cannot build response'); + } + } + + /** + * Returns an array of header responses. + * + * After parsing a SOAP message, use this to get the response. + * + * @return array + */ + function getHeaders() + { + if (!empty($this->header_struct[0])) { + return $this->_buildResponse($this->header_struct[0]); + } else { + // We don't fault if there are no headers; that can be handled by + // the application if necessary. + return null; + } + } + + /** + * Recurses to build a multi dimensional array. + * + * @see _buildResponse() + */ + function _domulti($d, &$ar, &$r, &$v, $ad = 0) + { + if ($d) { + $this->_domulti($d - 1, $ar, $r[$ar[$ad]], $v, $ad + 1); + } else { + $r = $v; + } + } + + /** + * Loops through the message, building response structures. + * + * @param integer $pos Position. + * + * @return SOAP_Value + */ + function _buildResponse($pos) + { + $response = null; + + if (isset($this->message[$pos]['children'])) { + $children = explode('|', $this->message[$pos]['children']); + foreach ($children as $c => $child_pos) { + if ($this->message[$child_pos]['type'] != null) { + $response[] = $this->_buildResponse($child_pos); + } + } + if (isset($this->message[$pos]['arraySize'])) { + $ardepth = count($this->message[$pos]['arraySize']); + if ($ardepth > 1) { + $ar = array_pad(array(), $ardepth, 0); + if (isset($this->message[$pos]['arrayOffset'])) { + for ($i = 0; $i < $ardepth; $i++) { + $ar[$i] += $this->message[$pos]['arrayOffset'][$i]; + } + } + $elc = count($response); + for ($i = 0; $i < $elc; $i++) { + // Recurse to build a multi dimensional array. + $this->_domulti($ardepth, $ar, $newresp, $response[$i]); + + // Increment our array pointers. + $ad = $ardepth - 1; + $ar[$ad]++; + while ($ad > 0 && + $ar[$ad] >= $this->message[$pos]['arraySize'][$ad]) { + $ar[$ad] = 0; + $ad--; + $ar[$ad]++; + } + } + $response = $newresp; + } elseif (isset($this->message[$pos]['arrayOffset']) && + $this->message[$pos]['arrayOffset'][0] > 0) { + // Check for padding. + $pad = $this->message[$pos]['arrayOffset'][0] + count($response) * -1; + $response = array_pad($response, $pad, null); + } + } + } + + // Build attributes. + $attrs = array(); + foreach ($this->message[$pos]['attrs'] as $atn => $atv) { + if (!strstr($atn, 'xmlns') && !strpos($atn, ':')) { + $attrs[$atn] = $atv; + } + } + + // Add current node's value. + $nqn = new QName($this->message[$pos]['name'], + $this->message[$pos]['namespace']); + $tqn = new QName($this->message[$pos]['type'], + $this->message[$pos]['type_namespace']); + if ($response) { + $response = new SOAP_Value($nqn->fqn(), $tqn->fqn(), $response, + $attrs); + if (isset($this->message[$pos]['arrayType'])) { + $response->arrayType = $this->message[$pos]['arrayType']; + } + } else { + // Check if value is an empty array + if ($tqn->name == 'Array') { + $response = new SOAP_Value($nqn->fqn(), $tqn->fqn(), array(), + $attrs); + //if ($pos == 4) var_dump($this->message[$pos], $response); + } else { + $response = new SOAP_Value($nqn->fqn(), $tqn->fqn(), + $this->message[$pos]['cdata'], + $attrs); + } + } + + // Handle header attribute that we need. + if (array_key_exists('actor', $this->message[$pos])) { + $response->actor = $this->message[$pos]['actor']; + } + if (array_key_exists('mustUnderstand', $this->message[$pos])) { + $response->mustunderstand = $this->message[$pos]['mustUnderstand']; + } + + return $response; + } + + /** + * Start element handler used with the XML parser. + */ + function _startElement($parser, $name, $attrs) + { + // Position in a total number of elements, starting from 0. + // Update class level position. + $pos = $this->position++; + + // And set mine. + $this->message[$pos] = array( + 'type' => '', + 'type_namespace' => '', + 'cdata' => '', + 'pos' => $pos, + 'id' => ''); + + // Parent/child/depth determinations. + + // depth = How many levels removed from root? + // Set mine as current global depth and increment global depth value. + $this->message[$pos]['depth'] = $this->depth++; + + // Else add self as child to whoever the current parent is. + if ($pos != 0) { + if (isset($this->message[$this->parent]['children'])) { + $this->message[$this->parent]['children'] .= '|' . $pos; + } else { + $this->message[$this->parent]['children'] = $pos; + } + } + + // Set my parent. + $this->message[$pos]['parent'] = $this->parent; + + // Set self as current value for this depth. + $this->depth_array[$this->depth] = $pos; + // Set self as current parent. + $this->parent = $pos; + $qname = new QName($name); + // Set status. + if (strcasecmp('envelope', $qname->name) == 0) { + $this->status = 'envelope'; + } elseif (strcasecmp('header', $qname->name) == 0) { + $this->status = 'header'; + $this->header_struct_name[] = $this->curent_root_struct_name = $qname->name; + $this->header_struct[] = $this->curent_root_struct = $pos; + $this->message[$pos]['type'] = 'Struct'; + } elseif (strcasecmp('body', $qname->name) == 0) { + $this->status = 'body'; + $this->bodyDepth = $this->depth; + + // Set method + } elseif ($this->status == 'body') { + // Is this element allowed to be a root? + // TODO: this needs to be optimized, we loop through $attrs twice + // now. + $can_root = $this->depth == $this->bodyDepth + 1; + if ($can_root) { + foreach ($attrs as $key => $value) { + if (stristr($key, ':root') && !$value) { + $can_root = false; + } + } + } + + if ($can_root) { + $this->status = 'method'; + $this->root_struct_name[] = $this->curent_root_struct_name = $qname->name; + $this->root_struct[] = $this->curent_root_struct = $pos; + $this->message[$pos]['type'] = 'Struct'; + } + } + + // Set my status. + $this->message[$pos]['status'] = $this->status; + + // Set name. + $this->message[$pos]['name'] = htmlspecialchars($qname->name); + + // Set attributes. + $this->message[$pos]['attrs'] = $attrs; + + // Loop through attributes, logging ns and type declarations. + foreach ($attrs as $key => $value) { + // If ns declarations, add to class level array of valid + // namespaces. + $kqn = new QName($key); + if ($kqn->ns == 'xmlns') { + $prefix = $kqn->name; + + if (in_array($value, $this->_XMLSchema)) { + $this->_setSchemaVersion($value); + } + + $this->_namespaces[$value] = $prefix; + + // Set method namespace. + } elseif ($key == 'xmlns') { + $qname->ns = $this->_getNamespacePrefix($value); + $qname->namespace = $value; + } elseif ($kqn->name == 'actor') { + $this->message[$pos]['actor'] = $value; + } elseif ($kqn->name == 'mustUnderstand') { + $this->message[$pos]['mustUnderstand'] = $value; + + // If it's a type declaration, set type. + } elseif ($kqn->name == 'type') { + $vqn = new QName($value); + $this->message[$pos]['type'] = $vqn->name; + $this->message[$pos]['type_namespace'] = $this->_getNamespaceForPrefix($vqn->ns); + + // Should do something here with the namespace of specified + // type? + + } elseif ($kqn->name == 'arrayType') { + $vqn = new QName($value); + $this->message[$pos]['type'] = 'Array'; + if (isset($vqn->arraySize)) { + $this->message[$pos]['arraySize'] = $vqn->arraySize; + } + $this->message[$pos]['arrayType'] = $vqn->name; + + } elseif ($kqn->name == 'offset') { + $this->message[$pos]['arrayOffset'] = split(',', substr($value, 1, strlen($value) - 2)); + + } elseif ($kqn->name == 'id') { + // Save id to reference array. + $this->references[$value] = $pos; + $this->message[$pos]['id'] = $value; + + } elseif ($kqn->name == 'href') { + if ($value[0] == '#') { + $ref = substr($value, 1); + if (isset($this->references[$ref])) { + // cdata, type, inval. + $ref_pos = $this->references[$ref]; + $this->message[$pos]['children'] = &$this->message[$ref_pos]['children']; + $this->message[$pos]['cdata'] = &$this->message[$ref_pos]['cdata']; + $this->message[$pos]['type'] = &$this->message[$ref_pos]['type']; + $this->message[$pos]['arraySize'] = &$this->message[$ref_pos]['arraySize']; + $this->message[$pos]['arrayType'] = &$this->message[$ref_pos]['arrayType']; + } else { + // Reverse reference, store in 'need reference'. + if (!isset($this->need_references[$ref])) { + $this->need_references[$ref] = array(); + } + $this->need_references[$ref][] = $pos; + } + } elseif (isset($this->attachments[$value])) { + $this->message[$pos]['cdata'] = $this->attachments[$value]; + } + } + } + // See if namespace is defined in tag. + if (isset($attrs['xmlns:' . $qname->ns])) { + $namespace = $attrs['xmlns:' . $qname->ns]; + } elseif ($qname->ns && !$qname->namespace) { + $namespace = $this->_getNamespaceForPrefix($qname->ns); + } else { + // Get namespace. + $namespace = $qname->namespace ? $qname->namespace : $this->default_namespace; + } + $this->message[$pos]['namespace'] = $namespace; + $this->default_namespace = $namespace; + } + + /** + * End element handler used with the XML parser. + */ + function _endElement($parser, $name) + { + // Position of current element is equal to the last value left in + // depth_array for my depth. + $pos = $this->depth_array[$this->depth]; + + // Bring depth down a notch. + $this->depth--; + $qname = new QName($name); + + // Get type if not explicitly declared in an xsi:type attribute. + // TODO: check on integrating WSDL validation here. + if ($this->message[$pos]['type'] == '') { + if (isset($this->message[$pos]['children'])) { + /* this is slow, need to look at some faster method + $children = explode('|', $this->message[$pos]['children']); + if (count($children) > 2 && + $this->message[$children[1]]['name'] == $this->message[$children[2]]['name']) { + $this->message[$pos]['type'] = 'Array'; + } else { + $this->message[$pos]['type'] = 'Struct'; + }*/ + $this->message[$pos]['type'] = 'Struct'; + } else { + $parent = $this->message[$pos]['parent']; + if ($this->message[$parent]['type'] == 'Array' && + isset($this->message[$parent]['arrayType'])) { + $this->message[$pos]['type'] = $this->message[$parent]['arrayType']; + } else { + $this->message[$pos]['type'] = 'string'; + } + } + } + + // If tag we are currently closing is the method wrapper. + if ($pos == $this->curent_root_struct) { + $this->status = 'body'; + } elseif ($qname->name == 'Body' || $qname->name == 'Header') { + $this->status = 'envelope'; + } + + // Set parent back to my parent. + $this->parent = $this->message[$pos]['parent']; + + // Handle any reverse references now. + $idref = $this->message[$pos]['id']; + + if ($idref != '' && isset($this->need_references[$idref])) { + foreach ($this->need_references[$idref] as $ref_pos) { + // XXX is this stuff there already? + $this->message[$ref_pos]['children'] = &$this->message[$pos]['children']; + $this->message[$ref_pos]['cdata'] = &$this->message[$pos]['cdata']; + $this->message[$ref_pos]['type'] = &$this->message[$pos]['type']; + $this->message[$ref_pos]['arraySize'] = &$this->message[$pos]['arraySize']; + $this->message[$ref_pos]['arrayType'] = &$this->message[$pos]['arrayType']; + } + } + } + + /** + * Element content handler used with the XML parser. + */ + function _characterData($parser, $data) + { + $pos = $this->depth_array[$this->depth]; + if (isset($this->message[$pos]['cdata'])) { + $this->message[$pos]['cdata'] .= $data; + } else { + $this->message[$pos]['cdata'] = $data; + } + } + +} diff --git a/lib/PEAR/SOAP/Transport.php b/lib/PEAR/SOAP/Transport.php new file mode 100644 index 0000000000..72681931f4 --- /dev/null +++ b/lib/PEAR/SOAP/Transport.php @@ -0,0 +1,147 @@ +<?php +/** + * This file contains the code for an abstract transport layer. + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 2.02 of the PHP license, + * that is bundled with this package in the file LICENSE, and is available at + * through the world-wide-web at http://www.php.net/license/2_02.txt. If you + * did not receive a copy of the PHP license and are unable to obtain it + * through the world-wide-web, please send a note to license@php.net so we can + * mail you a copy immediately. + * + * @category Web Services + * @package SOAP + * @author Dietrich Ayala <dietrich@ganx4.com> + * @author Shane Caraveo <Shane@Caraveo.com> + * @author Jan Schneider <jan@horde.org> + * @copyright 2003-2006 The PHP Group + * @license http://www.php.net/license/2_02.txt PHP License 2.02 + * @link http://pear.php.net/package/SOAP + */ + +require_once 'SOAP/Base.php'; + +/** + * SOAP Transport Layer + * + * This layer can use different protocols dependant on the endpoint url + * provided. + * + * No knowlege of the SOAP protocol is available at this level. + * No knowlege of the transport protocols is available at this level. + * + * @access public + * @package SOAP + * @author Shane Caraveo <shane@php.net> + * @author Jan Schneider <jan@horde.org> + */ +class SOAP_Transport extends SOAP_Base +{ + /** + * Connection endpoint URL. + * + * @var string + */ + var $url = ''; + + /** + * Array containing urlparts. + * + * @see parse_url() + * + * @var mixed + */ + var $urlparts = null; + + /** + * Incoming payload. + * + * @var string + */ + var $incoming_payload = ''; + + /** + * Outgoing payload. + * + * @var string + */ + var $outgoing_payload = ''; + + /** + * Request encoding. + * + * @var string + */ + var $encoding = SOAP_DEFAULT_ENCODING; + + /** + * Response encoding. + * + * We assume UTF-8 if no encoding is set. + * + * @var string + */ + var $result_encoding = 'UTF-8'; + + /** + * Decoded attachments from the reponse. + * + * @var array + */ + var $attachments; + + /** + * Request User-Agent. + * + * @var string + */ + var $_userAgent = SOAP_LIBRARY_NAME; + + /** + * Sends and receives SOAP data. + * + * @access public + * @abstract + * + * @param string Outgoing SOAP data. + * @param array Options. + * + * @return string|SOAP_Fault + */ + function send($msg, $options = null) + { + return $this->_raiseSoapFault('SOAP_Transport::send() not implemented.'); + } + + public static function getTransport($url, $encoding = SOAP_DEFAULT_ENCODING) + { + $urlparts = @parse_url($url); + + if (!$urlparts['scheme']) { + return SOAP_Base_Object::_raiseSoapFault("Invalid transport URI: $url"); + } + + if (strcasecmp($urlparts['scheme'], 'mailto') == 0) { + $transport_type = 'SMTP'; + } elseif (strcasecmp($urlparts['scheme'], 'https') == 0) { + $transport_type = 'HTTP'; + } else { + /* Handle other transport types */ + $transport_type = strtoupper($urlparts['scheme']); + } + $transport_class = "SOAP_Transport_$transport_type"; + if (!class_exists($transport_class)) { + if (!(@include_once('SOAP/Transport/' . basename($transport_type) . '.php'))) { + return SOAP_Base_Object::_raiseSoapFault("No Transport for {$urlparts['scheme']}"); + } + } + if (!class_exists($transport_class)) { + return SOAP_Base_Object::_raiseSoapFault("No Transport class $transport_class"); + } + + return new $transport_class($url, $encoding); + } + +} diff --git a/lib/PEAR/SOAP/Transport/HTTP.php b/lib/PEAR/SOAP/Transport/HTTP.php new file mode 100644 index 0000000000..49865f8fc8 --- /dev/null +++ b/lib/PEAR/SOAP/Transport/HTTP.php @@ -0,0 +1,624 @@ +<?php +/** + * This file contains the code for a HTTP transport layer. + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 2.02 of the PHP license, + * that is bundled with this package in the file LICENSE, and is available at + * through the world-wide-web at http://www.php.net/license/2_02.txt. If you + * did not receive a copy of the PHP license and are unable to obtain it + * through the world-wide-web, please send a note to license@php.net so we can + * mail you a copy immediately. + * + * @category Web Services + * @package SOAP + * @author Shane Caraveo <Shane@Caraveo.com> + * @author Jan Schneider <jan@horde.org> + * @copyright 2003-2006 The PHP Group + * @license http://www.php.net/license/2_02.txt PHP License 2.02 + * @link http://pear.php.net/package/SOAP + */ + +/** + * HTTP Transport class + * + * @package SOAP + * @category Web Services + */ + +/** + * Needed Classes + */ +require_once 'SOAP/Transport.php'; + +/** + * HTTP Transport for SOAP + * + * @access public + * @package SOAP + * @author Shane Caraveo <shane@php.net> + * @author Jan Schneider <jan@horde.org> + */ +class SOAP_Transport_HTTP extends SOAP_Transport +{ + /** + * Basic Auth string. + * + * @var array + */ + var $headers = array(); + + /** + * Cookies. + * + * @var array + */ + var $cookies; + + /** + * Connection timeout in seconds. 0 = none. + * + * @var integer + */ + var $timeout = 4; + + /** + * HTTP-Response Content-Type. + */ + var $result_content_type; + + var $result_headers = array(); + + var $result_cookies = array(); + + /** + * SOAP_Transport_HTTP Constructor + * + * @access public + * + * @param string $url HTTP url to SOAP endpoint. + * @param string $encoding Encoding to use. + */ + function SOAP_Transport_HTTP($url, $encoding = SOAP_DEFAULT_ENCODING) + { + parent::SOAP_Base('HTTP'); + $this->urlparts = @parse_url($url); + $this->url = $url; + $this->encoding = $encoding; + } + + /** + * Sends and receives SOAP data. + * + * @access public + * + * @param string Outgoing SOAP data. + * @param array Options. + * + * @return string|SOAP_Fault + */ + function send($msg, $options = array()) + { + $this->fault = null; + + if (!$this->_validateUrl()) { + return $this->fault; + } + + if (isset($options['timeout'])) { + $this->timeout = (int)$options['timeout']; + } + + if (strcasecmp($this->urlparts['scheme'], 'HTTP') == 0) { + return $this->_sendHTTP($msg, $options); + } elseif (strcasecmp($this->urlparts['scheme'], 'HTTPS') == 0) { + return $this->_sendHTTPS($msg, $options); + } + + return $this->_raiseSoapFault('Invalid url scheme ' . $this->url); + } + + /** + * Sets data for HTTP authentication, creates authorization header. + * + * @param string $username Username. + * @param string $password Response data, minus HTTP headers. + * + * @access public + */ + function setCredentials($username, $password) + { + $this->headers['Authorization'] = 'Basic ' . base64_encode($username . ':' . $password); + } + + /** + * Adds a cookie. + * + * @access public + * @param string $name Cookie name. + * @param mixed $value Cookie value. + */ + function addCookie($name, $value) + { + $this->cookies[$name] = $value; + } + + /** + * Generates the correct headers for the cookies. + * + * @access private + * + * @param array $options Cookie options. If 'nocookies' is set and true + * the cookies from the last response are added + * automatically. 'cookies' is name-value-hash with + * a list of cookies to add. + * + * @return string The cookie header value. + */ + function _generateCookieHeader($options) + { + $this->cookies = array(); + + if (empty($options['nocookies']) && + isset($this->result_cookies)) { + // Add the cookies we got from the last request. + foreach ($this->result_cookies as $cookie) { + if ($cookie['domain'] == $this->urlparts['host']) { + $this->cookies[$cookie['name']] = $cookie['value']; + } + } + } + + // Add cookies the user wants to set. + if (isset($options['cookies'])) { + foreach ($options['cookies'] as $cookie) { + if ($cookie['domain'] == $this->urlparts['host']) { + $this->cookies[$cookie['name']] = $cookie['value']; + } + } + } + + $cookies = ''; + foreach ($this->cookies as $name => $value) { + if (!empty($cookies)) { + $cookies .= '; '; + } + $cookies .= urlencode($name) . '=' . urlencode($value); + } + + return $cookies; + } + + /** + * Validate url data passed to constructor. + * + * @access private + * @return boolean + */ + function _validateUrl() + { + if (!is_array($this->urlparts) ) { + $this->_raiseSoapFault('Unable to parse URL ' . $this->url); + return false; + } + if (!isset($this->urlparts['host'])) { + $this->_raiseSoapFault('No host in URL ' . $this->url); + return false; + } + if (!isset($this->urlparts['port'])) { + if (strcasecmp($this->urlparts['scheme'], 'HTTP') == 0) { + $this->urlparts['port'] = 80; + } elseif (strcasecmp($this->urlparts['scheme'], 'HTTPS') == 0) { + $this->urlparts['port'] = 443; + } + + } + if (isset($this->urlparts['user'])) { + $this->setCredentials(urldecode($this->urlparts['user']), + urldecode($this->urlparts['pass'])); + } + if (!isset($this->urlparts['path']) || !$this->urlparts['path']) { + $this->urlparts['path'] = '/'; + } + + return true; + } + + /** + * Finds out what the encoding is. + * Sets the object property accordingly. + * + * @access private + * @param array $headers Headers. + */ + function _parseEncoding($headers) + { + $h = stristr($headers, 'Content-Type'); + preg_match_all('/^Content-Type:\s*(.*)$/im', $h, $ct, PREG_SET_ORDER); + $n = count($ct); + $ct = $ct[$n - 1]; + + // Strip the string of \r. + $this->result_content_type = str_replace("\r", '', $ct[1]); + + if (preg_match('/(.*?)(?:;\s?charset=)(.*)/i', + $this->result_content_type, + $m)) { + $this->result_content_type = $m[1]; + if (count($m) > 2) { + $enc = strtoupper(str_replace('"', '', $m[2])); + if (in_array($enc, $this->_encodings)) { + $this->result_encoding = $enc; + } + } + } + + // Deal with broken servers that don't set content type on faults. + if (!$this->result_content_type) { + $this->result_content_type = 'text/xml'; + } + } + + /** + * Parses the headers. + * + * @param array $headers The headers. + */ + function _parseHeaders($headers) + { + /* Largely borrowed from HTTP_Request. */ + $this->result_headers = array(); + $headers = split("\r?\n", $headers); + foreach ($headers as $value) { + if (strpos($value,':') === false) { + $this->result_headers[0] = $value; + continue; + } + list($name, $value) = split(':', $value); + $headername = strtolower($name); + $headervalue = trim($value); + $this->result_headers[$headername] = $headervalue; + + if ($headername == 'set-cookie') { + // Parse a SetCookie header to fill _cookies array. + $cookie = array('expires' => null, + 'domain' => $this->urlparts['host'], + 'path' => null, + 'secure' => false); + + if (!strpos($headervalue, ';')) { + // Only a name=value pair. + list($cookie['name'], $cookie['value']) = array_map('trim', explode('=', $headervalue)); + $cookie['name'] = urldecode($cookie['name']); + $cookie['value'] = urldecode($cookie['value']); + + } else { + // Some optional parameters are supplied. + $elements = explode(';', $headervalue); + list($cookie['name'], $cookie['value']) = array_map('trim', explode('=', $elements[0])); + $cookie['name'] = urldecode($cookie['name']); + $cookie['value'] = urldecode($cookie['value']); + + for ($i = 1; $i < count($elements);$i++) { + list($elName, $elValue) = array_map('trim', explode('=', $elements[$i])); + if ('secure' == $elName) { + $cookie['secure'] = true; + } elseif ('expires' == $elName) { + $cookie['expires'] = str_replace('"', '', $elValue); + } elseif ('path' == $elName OR 'domain' == $elName) { + $cookie[$elName] = urldecode($elValue); + } else { + $cookie[$elName] = $elValue; + } + } + } + $this->result_cookies[] = $cookie; + } + } + } + + /** + * Removes HTTP headers from response. + * + * @return boolean + * @access private + */ + function _parseResponse() + { + if (!preg_match("/^(.*?)\r?\n\r?\n(.*)/s", + $this->incoming_payload, + $match)) { + $this->_raiseSoapFault('Invalid HTTP Response'); + return false; + } + + $this->response = $match[2]; + // Find the response error, some servers response with 500 for + // SOAP faults. + $this->_parseHeaders($match[1]); + + list(, $code, $msg) = sscanf($this->result_headers[0], '%s %s %s'); + unset($this->result_headers[0]); + + switch($code) { + case 100: // Continue + $this->incoming_payload = $match[2]; + return $this->_parseResponse(); + case 200: + case 202: + $this->incoming_payload = trim($match[2]); + if (!strlen($this->incoming_payload)) { + /* Valid one-way message response. */ + return true; + } + break; + case 400: + $this->_raiseSoapFault("HTTP Response $code Bad Request"); + return false; + case 401: + $this->_raiseSoapFault("HTTP Response $code Authentication Failed"); + return false; + case 403: + $this->_raiseSoapFault("HTTP Response $code Forbidden"); + return false; + case 404: + $this->_raiseSoapFault("HTTP Response $code Not Found"); + return false; + case 407: + $this->_raiseSoapFault("HTTP Response $code Proxy Authentication Required"); + return false; + case 408: + $this->_raiseSoapFault("HTTP Response $code Request Timeout"); + return false; + case 410: + $this->_raiseSoapFault("HTTP Response $code Gone"); + return false; + default: + if ($code >= 400 && $code < 500) { + $this->_raiseSoapFault("HTTP Response $code Not Found, Server message: $msg"); + return false; + } + break; + } + + $this->_parseEncoding($match[1]); + + if ($this->result_content_type == 'application/dime') { + // XXX quick hack insertion of DIME + if (PEAR::isError($this->_decodeDIMEMessage($this->response, $this->headers, $this->attachments))) { + // _decodeDIMEMessage already raised $this->fault + return false; + } + $this->result_content_type = $this->headers['content-type']; + } elseif (stristr($this->result_content_type, 'multipart/related')) { + $this->response = $this->incoming_payload; + if (PEAR::isError($this->_decodeMimeMessage($this->response, $this->headers, $this->attachments))) { + // _decodeMimeMessage already raised $this->fault + return false; + } + } elseif ($this->result_content_type != 'text/xml') { + $this->_raiseSoapFault($this->response); + return false; + } + + // if no content, return false + return strlen($this->response) > 0; + } + + /** + * Creates an HTTP request, including headers, for the outgoing request. + * + * @access private + * + * @param string $msg Outgoing SOAP package. + * @param array $options Options. + * + * @return string Outgoing payload. + */ + function _getRequest($msg, $options) + { + $this->headers = array(); + + $action = isset($options['soapaction']) ? $options['soapaction'] : ''; + $fullpath = $this->urlparts['path']; + if (isset($this->urlparts['query'])) { + $fullpath .= '?' . $this->urlparts['query']; + } + if (isset($this->urlparts['fragment'])) { + $fullpath .= '#' . $this->urlparts['fragment']; + } + + if (isset($options['proxy_host'])) { + $fullpath = 'http://' . $this->urlparts['host'] . ':' . + $this->urlparts['port'] . $fullpath; + } + + if (isset($options['proxy_user'])) { + $this->headers['Proxy-Authorization'] = 'Basic ' . + base64_encode($options['proxy_user'] . ':' . + $options['proxy_pass']); + } + + if (isset($options['user'])) { + $this->setCredentials($options['user'], $options['pass']); + } + + $this->headers['User-Agent'] = $this->_userAgent; + $this->headers['Host'] = $this->urlparts['host']; + $this->headers['Content-Type'] = "text/xml; charset=$this->encoding"; + $this->headers['Content-Length'] = strlen($msg); + $this->headers['SOAPAction'] = '"' . $action . '"'; + $this->headers['Connection'] = 'close'; + + if (isset($options['headers'])) { + $this->headers = array_merge($this->headers, $options['headers']); + } + + $cookies = $this->_generateCookieHeader($options); + if ($cookies) { + $this->headers['Cookie'] = $cookies; + } + + $headers = ''; + foreach ($this->headers as $k => $v) { + $headers .= "$k: $v\r\n"; + } + $this->outgoing_payload = "POST $fullpath HTTP/1.0\r\n" . $headers . + "\r\n" . $msg; + + return $this->outgoing_payload; + } + + /** + * Sends the outgoing HTTP request and reads and parses the response. + * + * @access private + * + * @param string $msg Outgoing SOAP package. + * @param array $options Options. + * + * @return string Response data without HTTP headers. + */ + function _sendHTTP($msg, $options) + { + $this->incoming_payload = ''; + $this->_getRequest($msg, $options); + $host = $this->urlparts['host']; + $port = $this->urlparts['port']; + if (isset($options['proxy_host'])) { + $host = $options['proxy_host']; + $port = isset($options['proxy_port']) ? $options['proxy_port'] : 8080; + } + // Send. + if ($this->timeout > 0) { + $fp = @fsockopen($host, $port, $this->errno, $this->errmsg, $this->timeout); + } else { + $fp = @fsockopen($host, $port, $this->errno, $this->errmsg); + } + if (!$fp) { + return $this->_raiseSoapFault("Connect Error to $host:$port"); + } + if ($this->timeout > 0) { + // some builds of PHP do not support this, silence the warning + @socket_set_timeout($fp, $this->timeout); + } + if (!fputs($fp, $this->outgoing_payload, strlen($this->outgoing_payload))) { + return $this->_raiseSoapFault("Error POSTing Data to $host"); + } + + // get reponse + // XXX time consumer + do { + $data = fread($fp, 4096); + $_tmp_status = socket_get_status($fp); + if ($_tmp_status['timed_out']) { + return $this->_raiseSoapFault("Timed out read from $host"); + } else { + $this->incoming_payload .= $data; + } + } while (!$_tmp_status['eof']); + + fclose($fp); + + if (!$this->_parseResponse()) { + return $this->fault; + } + return $this->response; + } + + /** + * Sends the outgoing HTTPS request and reads and parses the response. + * + * @access private + * + * @param string $msg Outgoing SOAP package. + * @param array $options Options. + * + * @return string Response data without HTTP headers. + */ + function _sendHTTPS($msg, $options) + { + /* Check if the required curl extension is installed. */ + if (!extension_loaded('curl')) { + return $this->_raiseSoapFault('CURL Extension is required for HTTPS'); + } + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); + if (isset($options['proxy_host'])) { + $port = isset($options['proxy_port']) ? $options['proxy_port'] : 8080; + curl_setopt($ch, CURLOPT_PROXY, + $options['proxy_host'] . ':' . $port); + } + if (isset($options['proxy_user'])) { + curl_setopt($ch, CURLOPT_PROXYUSERPWD, + $options['proxy_user'] . ':' . $options['proxy_pass']); + } + + if (isset($options['user'])) { + curl_setopt($ch, CURLOPT_USERPWD, + $options['user'] . ':' . $options['pass']); + } + + $headers = array(); + $action = isset($options['soapaction']) ? $options['soapaction'] : ''; + $headers['Content-Type'] = "text/xml; charset=$this->encoding"; + $headers['SOAPAction'] = '"' . $action . '"'; + if (isset($options['headers'])) { + $headers = array_merge($headers, $options['headers']); + } + foreach ($headers as $header => $value) { + $headers[$header] = $header . ': ' . $value; + } + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_USERAGENT, $this->_userAgent); + + if ($this->timeout) { + curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout); + } + + curl_setopt($ch, CURLOPT_POSTFIELDS, $msg); + curl_setopt($ch, CURLOPT_URL, $this->url); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_FAILONERROR, 0); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_HEADER, 1); + if (defined('CURLOPT_HTTP_VERSION')) { + curl_setopt($ch, CURLOPT_HTTP_VERSION, 1); + } + if (!ini_get('safe_mode') && !ini_get('open_basedir')) { + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); + } + $cookies = $this->_generateCookieHeader($options); + if ($cookies) { + curl_setopt($ch, CURLOPT_COOKIE, $cookies); + } + + if (isset($options['curl'])) { + foreach ($options['curl'] as $key => $val) { + curl_setopt($ch, $key, $val); + } + } + + // Save the outgoing XML. This doesn't quite match _sendHTTP as CURL + // generates the headers, but having the XML is usually the most + // important part for tracing/debugging. + $this->outgoing_payload = $msg; + + $this->incoming_payload = curl_exec($ch); + if (!$this->incoming_payload) { + $m = 'curl_exec error ' . curl_errno($ch) . ' ' . curl_error($ch); + curl_close($ch); + return $this->_raiseSoapFault($m); + } + curl_close($ch); + + if (!$this->_parseResponse()) { + return $this->fault; + } + + return $this->response; + } + +} diff --git a/lib/PEAR/SOAP/Value.php b/lib/PEAR/SOAP/Value.php new file mode 100644 index 0000000000..30c0ce6c55 --- /dev/null +++ b/lib/PEAR/SOAP/Value.php @@ -0,0 +1,288 @@ +<?php +/** + * This file contains the code for converting values between SOAP and PHP. + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 2.02 of the PHP license, + * that is bundled with this package in the file LICENSE, and is available at + * through the world-wide-web at http://www.php.net/license/2_02.txt. If you + * did not receive a copy of the PHP license and are unable to obtain it + * through the world-wide-web, please send a note to license@php.net so we can + * mail you a copy immediately. + * + * @category Web Services + * @package SOAP + * @author Dietrich Ayala <dietrich@ganx4.com> Original Author + * @author Shane Caraveo <Shane@Caraveo.com> Port to PEAR and more + * @author Chuck Hagenbuch <chuck@horde.org> Maintenance + * @author Jan Schneider <jan@horde.org> Maintenance + * @copyright 2003-2007 The PHP Group + * @license http://www.php.net/license/2_02.txt PHP License 2.02 + * @link http://pear.php.net/package/SOAP + */ + +require_once 'SOAP/Base.php'; + +/** + * SOAP::Value + * + * This class converts values between PHP and SOAP. + * + * Originally based on SOAPx4 by Dietrich Ayala + * http://dietrich.ganx4.com/soapx4 + * + * @access public + * @package SOAP + * @author Shane Caraveo <shane@php.net> Conversion to PEAR and updates + * @author Dietrich Ayala <dietrich@ganx4.com> Original Author + */ +class SOAP_Value +{ + /** + * The actual value. + * + * @var mixed + */ + var $value = null; + + /** + * QName instance representing the value name. + * + * @var QName + */ + var $nqn; + + /** + * The value name, without namespace information. + * + * @var string + */ + var $name = ''; + + /** + * The namespace of the value name. + * + * @var string + */ + var $namespace = ''; + + /** + * QName instance representing the value type. + * + * @var QName + */ + var $tqn; + + /** + * The value type, without namespace information. + * + * @var string + */ + var $type = ''; + + /** + * The namespace of the value type. + * + * @var string + */ + var $type_namespace = ''; + + /** + * The type of the array elements, if this value is an array. + * + * @var string + */ + var $arrayType = ''; + + /** + * A hash of additional attributes. + * + * @see SOAP_Value() + * @var array + */ + var $attributes = array(); + + /** + * List of encoding and serialization options. + * + * @see SOAP_Value() + * @var array + */ + var $options = array(); + + /** + * Constructor. + * + * @param string $name Name of the SOAP value {namespace}name. + * @param mixed $type SOAP value {namespace}type. Determined + * automatically if not set. + * @param mixed $value Value to set. + * @param array $attributes A has of additional XML attributes to be + * added to the serialized value. + * @param array $options A list of encoding and serialization options: + * - 'attachment': array with information about + * the attachment + * - 'soap_encoding': defines encoding for SOAP + * message part of a MIME encoded SOAP request + * (default: base64) + * - 'keep_arrays_flat': use the tag name + * multiple times for each element when + * passing in an array in literal mode + * - 'no_type_prefix': supress adding of the + * namespace prefix + */ + function SOAP_Value($name = '', $type = false, $value = null, + $attributes = array(), $options = array()) + { + $this->nqn = new QName($name); + $this->name = $this->nqn->name; + $this->namespace = $this->nqn->namespace; + if ($type) { + $this->tqn = new QName($type); + $this->type = $this->tqn->name; + $this->type_namespace = $this->tqn->namespace; + } + $this->value = $value; + $this->attributes = $attributes; + $this->options = $options; + } + + /** + * Serializes this value. + * + * @param SOAP_Base $serializer A SOAP_Base instance or subclass to + * serialize with. + * + * @return string XML representation of $this. + */ + function serialize(&$serializer) + { + return $serializer->_serializeValue($this->value, + $this->nqn, + $this->tqn, + $this->options, + $this->attributes, + $this->arrayType); + } + +} + +/** + * This class converts values between PHP and SOAP. It is a simple wrapper + * around SOAP_Value, adding support for SOAP actor and mustunderstand + * parameters. + * + * Originally based on SOAPx4 by Dietrich Ayala + * http://dietrich.ganx4.com/soapx4 + * + * @access public + * @package SOAP + * @author Shane Caraveo <shane@php.net> Conversion to PEAR and updates + * @author Dietrich Ayala <dietrich@ganx4.com> Original Author + */ +class SOAP_Header extends SOAP_Value +{ + /** + * Constructor + * + * @param string $name Name of the SOAP value {namespace}name. + * @param mixed $type SOAP value {namespace}type. Determined + * automatically if not set. + * @param mixed $value Value to set + * @param integer $mustunderstand Zero or one. + * @param mixed $attributes Attributes. + */ + function SOAP_Header($name = '', $type, $value, $mustunderstand = 0, + $attributes = array()) + { + if (!is_array($attributes)) { + $actor = $attributes; + $attributes = array(); + } + + parent::SOAP_Value($name, $type, $value, $attributes); + + if (isset($actor)) { + $this->attributes[SOAP_BASE::SOAPENVPrefix().':actor'] = $actor; + } elseif (!isset($this->attributes[SOAP_BASE::SOAPENVPrefix().':actor'])) { + $this->attributes[SOAP_BASE::SOAPENVPrefix().':actor'] = 'http://schemas.xmlsoap.org/soap/actor/next'; + } + $this->attributes[SOAP_BASE::SOAPENVPrefix().':mustUnderstand'] = (int)$mustunderstand; + } + +} + +/** + * This class handles MIME attachements per W3C's Note on Soap Attachements at + * http://www.w3.org/TR/SOAP-attachments + * + * @access public + * @package SOAP + * @author Shane Caraveo <shane@php.net> Conversion to PEAR and updates + */ +class SOAP_Attachment extends SOAP_Value +{ + /** + * Constructor. + * + * @param string $name Name of the SOAP value <value_name> + * @param string $type The attachment's MIME type. + * @param string $filename The attachment's file name. Ignored if $file + * is provide. + * @param string $file The attachment data. + * @param array $attributes Attributes. + */ + function SOAP_Attachment($name = '', $type = 'application/octet-stream', + $filename, $file = null, $attributes = null) + { + parent::SOAP_Value($name, null, null); + + $filedata = $file === null ? $this->_file2str($filename) : $file; + $filename = basename($filename); + if (PEAR::isError($filedata)) { + $this->options['attachment'] = $filedata; + return; + } + + $cid = md5(uniqid(time())); + + $this->attributes = $attributes; + $this->attributes['href'] = 'cid:' . $cid; + + $this->options['attachment'] = array('body' => $filedata, + 'disposition' => $filename, + 'content_type' => $type, + 'encoding' => 'base64', + 'cid' => $cid); + } + + /** + * Returns the contents of the given file name as string. + * + * @access private + * + * @param string $file_name The file location. + * + * @return string The file data or a PEAR_Error. + */ + function _file2str($file_name) + { + if (!is_readable($file_name)) { + return PEAR::raiseError('File is not readable: ' . $file_name); + } + + if (function_exists('file_get_contents')) { + return file_get_contents($file_name); + } + + if (!$fd = fopen($file_name, 'rb')) { + return PEAR::raiseError('Could not open ' . $file_name); + } + $cont = fread($fd, filesize($file_name)); + fclose($fd); + + return $cont; + } + +} diff --git a/lib/PEAR/SOAP/WSDL.php b/lib/PEAR/SOAP/WSDL.php new file mode 100644 index 0000000000..1fafd34279 --- /dev/null +++ b/lib/PEAR/SOAP/WSDL.php @@ -0,0 +1,2294 @@ +<?php +/** + * This file contains the code for dealing with WSDL access and services. + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 2.02 of the PHP license, + * that is bundled with this package in the file LICENSE, and is available at + * through the world-wide-web at http://www.php.net/license/2_02.txt. If you + * did not receive a copy of the PHP license and are unable to obtain it + * through the world-wide-web, please send a note to license@php.net so we can + * mail you a copy immediately. + * + * @category Web Services + * @package SOAP + * @author Dietrich Ayala <dietrich@ganx4.com> Original Author + * @author Shane Caraveo <Shane@Caraveo.com> Port to PEAR and more + * @author Chuck Hagenbuch <chuck@horde.org> Maintenance + * @author Jan Schneider <jan@horde.org> Maintenance + * @copyright 2003-2005 The PHP Group + * @license http://www.php.net/license/2_02.txt PHP License 2.02 + * @link http://pear.php.net/package/SOAP + */ + +require_once 'SOAP/Base.php'; +require_once 'SOAP/Fault.php'; +require_once 'HTTP/Request.php'; + +define('WSDL_CACHE_MAX_AGE', 43200); + +/** + * This class parses WSDL files, and can be used by SOAP::Client to properly + * register soap values for services. + * + * Originally based on SOAPx4 by Dietrich Ayala + * http://dietrich.ganx4.com/soapx4 + * + * @todo + * - refactor namespace handling ($namespace/$ns) + * - implement IDL type syntax declaration so we can generate WSDL + * + * @access public + * @package SOAP + * @author Shane Caraveo <shane@php.net> Conversion to PEAR and updates + * @author Dietrich Ayala <dietrich@ganx4.com> Original Author + */ +class SOAP_WSDL extends SOAP_Base +{ + var $tns = null; + var $definition = array(); + var $namespaces = array(); + var $ns = array(); + var $xsd = SOAP_XML_SCHEMA_VERSION; + var $complexTypes = array(); + var $elements = array(); + var $messages = array(); + var $portTypes = array(); + var $bindings = array(); + var $imports = array(); + var $services = array(); + var $service = ''; + + /** + * URL to WSDL file. + * + * @var string + */ + var $uri; + + /** + * Parse documentation in the WSDL? + * + * @var boolean + */ + var $docs; + + /** + * Proxy parameters. + * + * @var array + */ + var $proxy; + + /** + * Enable tracing in the generated proxy class? + * + * @var boolean + */ + var $trace = false; + + /** + * Use WSDL cache? + * + * @var boolean + */ + var $cacheUse; + + /** + * WSDL cache directory. + * + * @var string + */ + var $cacheDir; + + /** + * Cache maximum lifetime (in seconds). + * + * @var integer + */ + var $cacheMaxAge; + + /** + * Class to use for WSDL parsing. Can be overridden for special cases, + * subclasses, etc. + * + * @var string + */ + var $wsdlParserClass = 'SOAP_WSDL_Parser'; + + /** + * Reserved PHP keywords. + * + * @link http://www.php.net/manual/en/reserved.php + * + * @var array + */ + var $_reserved = array('abstract', 'and', 'array', 'as', 'break', 'case', + 'catch', 'cfunction', 'class', 'clone', 'const', + 'continue', 'declare', 'default', 'die', 'do', + 'echo', 'else', 'elseif', 'empty', 'enddeclare', + 'endfor', 'endforeach', 'endif', 'endswitch', + 'endwhile', 'eval', 'exception', 'exit', 'extends', + 'final', 'for', 'foreach', 'function', 'global', + 'if', 'implements', 'include', 'include_once', + 'interface', 'isset', 'list', 'new', 'old_function', + 'or', 'php_user_filter', 'print', 'private', + 'protected', 'public', 'require', 'require_once', + 'return', 'static', 'switch', 'this', 'throw', + 'try', 'unset', 'use', 'var', 'while', 'xor'); + + /** + * Regular expressions for invalid PHP labels. + * + * @link http://www.php.net/manual/en/language.variables.php. + * + * @var string + */ + var $_invalid = array('/^[^a-zA-Z_\x7f-\xff]/', '/[^a-zA-Z0-9_\x7f-\xff]/'); + + /** + * SOAP_WSDL constructor. + * + * @param string $wsdl_uri URL to WSDL file. + * @param array $proxy Options for HTTP_Request class + * @see HTTP_Request. + * @param boolean|string $cacheUse Use WSDL caching? The cache directory + * if a string. + * @param integer $cacheMaxAge Cache maximum lifetime (in seconds). + * @param boolean $docs Parse documentation in the WSDL? + * + * @access public + */ + function SOAP_WSDL($wsdl_uri = false, + $proxy = array(), + $cacheUse = false, + $cacheMaxAge = WSDL_CACHE_MAX_AGE, + $docs = false) + { + parent::SOAP_Base('WSDL'); + $this->uri = $wsdl_uri; + $this->proxy = $proxy; + $this->cacheUse = !empty($cacheUse); + $this->cacheMaxAge = $cacheMaxAge; + $this->docs = $docs; + if (is_string($cacheUse)) { + $this->cacheDir = $cacheUse; + } + + if ($wsdl_uri) { + if (!PEAR::isError($this->parseURL($wsdl_uri))) { + reset($this->services); + $this->service = key($this->services); + } + } + } + + /** + * @deprecated Use setService(). + */ + function set_service($service) + { + $this->setService($service); + } + + /** + * Sets the service currently to be used. + * + * @param string $service An (existing) service name. + */ + function setService($service) + { + if (array_key_exists($service, $this->services)) { + $this->service = $service; + } + } + + /** + * Fills the WSDL array tree with data from a WSDL file. + * + * @param string $wsdl_uri URL to WSDL file. + */ + function parseURL($wsdl_uri) + { + $parser = new $this->wsdlParserClass($wsdl_uri, $this, $this->docs); + + if ($parser->fault) { + $this->_raiseSoapFault($parser->fault); + } + } + + /** + * Fills the WSDL array tree with data from one or more PHP class objects. + * + * @param mixed $wsdl_obj An object or array of objects to add to + * the internal WSDL tree. + * @param string $targetNamespace The target namespace of schema types + * etc. + * @param string $service_name Name of the WSDL service. + * @param string $service_desc Optional description of the WSDL + * service. + */ + function parseObject($wsdl_obj, $targetNamespace, $service_name, + $service_desc = '') + { + $parser = new SOAP_WSDL_ObjectParser($wsdl_obj, $this, + $targetNamespace, $service_name, + $service_desc); + + if ($parser->fault) { + $this->_raiseSoapFault($parser->fault); + } + } + + function getEndpoint($portName) + { + if ($this->_isfault()) { + return $this->_getfault(); + } + + return (isset($this->services[$this->service]['ports'][$portName]['address']['location'])) + ? $this->services[$this->service]['ports'][$portName]['address']['location'] + : $this->_raiseSoapFault("No endpoint for port for $portName", $this->uri); + } + + function _getPortName($operation, $service) + { + if (isset($this->services[$service]['ports'])) { + $ports = $this->services[$service]['ports']; + foreach ($ports as $port => $portAttrs) { + $type = $ports[$port]['type']; + if ($type == 'soap' && + isset($this->bindings[$portAttrs['binding']]['operations'][$operation])) { + return $port; + } + } + } + return null; + } + + /** + * Finds the name of the first port that contains an operation of name + * $operation. Always returns a SOAP portName. + */ + function getPortName($operation, $service = null) + { + if ($this->_isfault()) { + return $this->_getfault(); + } + + if (!$service) { + $service = $this->service; + } + if (isset($this->services[$service]['ports'])) { + if ($portName = $this->_getPortName($operation, $service)) { + return $portName; + } + } + // Try any service in the WSDL. + foreach ($this->services as $serviceName => $service) { + if (isset($this->services[$serviceName]['ports'])) { + if ($portName = $this->_getPortName($operation, $serviceName)) { + $this->service = $serviceName; + return $portName; + } + } + } + return $this->_raiseSoapFault("No operation $operation in WSDL.", $this->uri); + } + + function getOperationData($portName, $operation) + { + if ($this->_isfault()) { + return $this->_getfault(); + } + + if (!isset($this->services[$this->service]['ports'][$portName]['binding']) || + !($binding = $this->services[$this->service]['ports'][$portName]['binding'])) { + return $this->_raiseSoapFault("No binding for port $portName in WSDL.", $this->uri); + } + + // Get operation data from binding. + if (is_array($this->bindings[$binding]['operations'][$operation])) { + $opData = $this->bindings[$binding]['operations'][$operation]; + } + // Get operation data from porttype. + $portType = $this->bindings[$binding]['type']; + if (!$portType) { + return $this->_raiseSoapFault("No port type for binding $binding in WSDL.", $this->uri); + } + if (is_array($type = $this->portTypes[$portType][$operation])) { + if (isset($type['parameterOrder'])) { + $opData['parameterOrder'] = $type['parameterOrder']; + } + $opData['input'] = array_merge($opData['input'], $type['input']); + $opData['output'] = array_merge($opData['output'], $type['output']); + } + if (!$opData) + return $this->_raiseSoapFault("No operation $operation for port $portName in WSDL.", $this->uri); + $opData['parameters'] = false; + if (isset($this->bindings[$binding]['operations'][$operation]['input']['namespace'])) + $opData['namespace'] = $this->bindings[$binding]['operations'][$operation]['input']['namespace']; + // Message data from messages. + $inputMsg = $opData['input']['message']; + if (is_array($this->messages[$inputMsg])) { + foreach ($this->messages[$inputMsg] as $pname => $pattrs) { + if ($opData['style'] == 'document' && + $opData['input']['use'] == 'literal' && + $pname == 'parameters') { + $opData['parameters'] = true; + $opData['namespace'] = $this->namespaces[$pattrs['namespace']]; + $el = $this->elements[$pattrs['namespace']][$pattrs['type']]; + if (isset($el['elements'])) { + foreach ($el['elements'] as $elname => $elattrs) { + $opData['input']['parts'][$elname] = $elattrs; + } + } + } else { + $opData['input']['parts'][$pname] = $pattrs; + } + } + } + $outputMsg = $opData['output']['message']; + if (is_array($this->messages[$outputMsg])) { + foreach ($this->messages[$outputMsg] as $pname => $pattrs) { + if ($opData['style'] == 'document' && + $opData['output']['use'] == 'literal' && + $pname == 'parameters') { + + $el = $this->elements[$pattrs['namespace']][$pattrs['type']]; + if (isset($el['elements'])) { + foreach ($el['elements'] as $elname => $elattrs) { + $opData['output']['parts'][$elname] = $elattrs; + } + } + } else { + $opData['output']['parts'][$pname] = $pattrs; + } + } + } + return $opData; + } + + function matchMethod(&$operation) + { + if ($this->_isfault()) { + return $this->_getfault(); + } + + // Overloading lowercases function names :( + foreach ($this->services[$this->service]['ports'] as $portAttrs) { + foreach (array_keys($this->bindings[$portAttrs['binding']]['operations']) as $op) { + if (strcasecmp($op, $operation) == 0) { + $operation = $op; + } + } + } + } + + /** + * Given a datatype, what function handles the processing? + * + * This is used for doc/literal requests where we receive a datatype, and + * we need to pass it to a method in out server class. + * + * @param string $datatype + * @param string $namespace + * @return string + * @access public + */ + function getDataHandler($datatype, $namespace) + { + // See if we have an element by this name. + if (isset($this->namespaces[$namespace])) { + $namespace = $this->namespaces[$namespace]; + } + + if (!isset($this->ns[$namespace])) { + return null; + } + + $nsp = $this->ns[$namespace]; + //if (!isset($this->elements[$nsp])) + // $nsp = $this->namespaces[$nsp]; + if (!isset($this->elements[$nsp][$datatype])) { + return null; + } + + $checkmessages = array(); + // Find what messages use this datatype. + foreach ($this->messages as $messagename => $message) { + foreach ($message as $part) { + if ($part['type'] == $datatype) { + $checkmessages[] = $messagename; + break; + } + } + } + // Find the operation that uses this message. + foreach($this->portTypes as $porttype) { + foreach ($porttype as $opname => $opinfo) { + foreach ($checkmessages as $messagename) { + if ($opinfo['input']['message'] == $messagename) { + return $opname; + } + } + } + } + + return null; + } + + function getSoapAction($portName, $operation) + { + if ($this->_isfault()) { + return $this->_getfault(); + } + + if (!empty($this->bindings[$this->services[$this->service]['ports'][$portName]['binding']]['operations'][$operation]['soapAction'])) { + return $this->bindings[$this->services[$this->service]['ports'][$portName]['binding']]['operations'][$operation]['soapAction']; + } + + return false; + } + + function getNamespace($portName, $operation) + { + if ($this->_isfault()) { + return $this->_getfault(); + } + + if (!empty($this->bindings[$this->services[$this->service]['ports'][$portName]['binding']]['operations'][$operation]['input']['namespace'])) { + return $this->bindings[$this->services[$this->service]['ports'][$portName]['binding']]['operations'][$operation]['input']['namespace']; + } + + return false; + } + + function getNamespaceAttributeName($namespace) + { + /* If it doesn't exist at first, flip the array and check again. */ + if (empty($this->ns[$namespace])) { + $this->ns = array_flip($this->namespaces); + } + + /* If it doesn't exist now, add it. */ + if (empty($this->ns[$namespace])) { + return $this->addNamespace($namespace); + } + + return $this->ns[$namespace]; + } + + function addNamespace($namespace) + { + if (!empty($this->ns[$namespace])) { + return $this->ns[$namespace]; + } + + $n = count($this->ns); + $attr = 'ns' . $n; + $this->namespaces['ns' . $n] = $namespace; + $this->ns[$namespace] = $attr; + + return $attr; + } + + function _validateString($string) + { + return preg_match('/^[\w_:#\/]+$/', $string); + } + + function _addArg(&$args, &$argarray, $argname) + { + if ($args) { + $args .= ', '; + } + $args .= '$' . $argname; + if (!$this->_validateString($argname)) { + return; + } + if ($argarray) { + $argarray .= ', '; + } + $argarray .= "'$argname' => $" . $argname; + } + + function _elementArg(&$args, &$argarray, &$_argtype, $_argname) + { + $comments = ''; + $el = $this->elements[$_argtype['namespace']][$_argtype['type']]; + $tns = isset($this->ns[$el['namespace']]) + ? $this->ns[$el['namespace']] + : $_argtype['namespace']; + + if (!empty($el['complex']) || + (isset($el['type']) && + isset($this->complexTypes[$tns][$el['type']]))) { + // The element is a complex type. + $comments .= " // {$_argtype['type']} is a ComplexType, refer to the WSDL for more info.\n"; + $attrname = "{$_argtype['type']}_attr"; + if (isset($el['type']) && + isset($this->complexTypes[$tns][$el['type']]['attribute'])) { + $comments .= " // {$_argtype['type']} may require attributes, refer to the WSDL for more info.\n"; + } + $comments .= " \${$attrname}['xmlns'] = '{$this->namespaces[$_argtype['namespace']]}';\n"; + $comments .= " \${$_argtype['type']} = new SOAP_Value('{$_argtype['type']}', false, \${$_argtype['type']}, \$$attrname);\n"; + $this->_addArg($args, $argarray, $_argtype['type']); + if (isset($el['type']) && + isset($this->complexTypes[$tns][$el['type']]['attribute'])) { + if ($args) { + $args .= ', '; + } + $args .= '$' . $attrname; + } + } elseif (isset($el['elements'])) { + foreach ($el['elements'] as $ename => $element) { + $comments .= " \$$ename = new SOAP_Value('{{$this->namespaces[$element['namespace']]}}$ename', '" . + (isset($element['type']) ? $element['type'] : false) . + "', \$$ename);\n"; + $this->_addArg($args, $argarray, $ename); + } + } else { + $comments .= " \$$_argname = new SOAP_Value('{{$this->namespaces[$tns]}}$_argname', '{$el['type']}', \$$_argname);\n"; + $this->_addArg($args, $argarray, $_argname); + } + + return $comments; + } + + function _complexTypeArg(&$args, &$argarray, &$_argtype, $_argname) + { + $comments = ''; + if (isset($this->complexTypes[$_argtype['namespace']][$_argtype['type']])) { + $comments = " // $_argname is a ComplexType {$_argtype['type']},\n" . + " // refer to wsdl for more info\n"; + if (isset($this->complexTypes[$_argtype['namespace']][$_argtype['type']]['attribute'])) { + $comments .= " // $_argname may require attributes, refer to wsdl for more info\n"; + } + $wrapname = '{' . $this->namespaces[$_argtype['namespace']].'}' . $_argtype['type']; + $comments .= " \$$_argname = new SOAP_Value('$_argname', '$wrapname', \$$_argname);\n"; + } + + $this->_addArg($args, $argarray, $_argname); + + return $comments; + } + + /** + * Generates stub code from the WSDL that can be saved to a file or eval'd + * into existence. + */ + function generateProxyCode($port = '', $classname = '') + { + if ($this->_isfault()) { + return $this->_getfault(); + } + + $multiport = count($this->services[$this->service]['ports']) > 1; + if (!$port) { + reset($this->services[$this->service]['ports']); + $port = current($this->services[$this->service]['ports']); + } + // XXX currently do not support HTTP ports + if ($port['type'] != 'soap') { + return null; + } + + // XXX currentPort is BAD + $clienturl = $port['address']['location']; + if (!$classname) { + if ($multiport || $port) { + $classname = 'WebService_' . $this->service . '_' . $port['name']; + } else { + $classname = 'WebService_' . $this->service; + } + $classname = $this->_sanitize($classname); + } + + if (!$this->_validateString($classname)) { + return null; + } + + if (is_array($this->proxy) && count($this->proxy)) { + $class = "class $classname extends SOAP_Client\n{\n" . + " function $classname(\$path = '$clienturl')\n {\n" . + " \$this->SOAP_Client(\$path, 0, 0,\n" . + ' array('; + + foreach ($this->proxy as $key => $val) { + if (is_array($val)) { + $class .= "'$key' => array("; + foreach ($val as $key2 => $val2) { + $class .= "'$key2' => '$val2', "; + } + $class .= ')'; + } else { + $class .= "'$key' => '$val', "; + } + } + $class .= "));\n }\n"; + $class = str_replace(', ))', '))', $class); + } else { + $class = "class $classname extends SOAP_Client\n{\n" . + " function $classname(\$path = '$clienturl')\n {\n" . + " \$this->SOAP_Client(\$path, 0);\n" . + " }\n"; + } + + // Get the binding, from that get the port type. + $primaryBinding = $port['binding']; + $primaryBinding = preg_replace("/^(.*:)/", '', $primaryBinding); + $portType = $this->bindings[$primaryBinding]['type']; + $portType = preg_replace("/^(.*:)/", '', $portType); + $style = $this->bindings[$primaryBinding]['style']; + + // XXX currentPortType is BAD + foreach ($this->portTypes[$portType] as $opname => $operation) { + $binding = $this->bindings[$primaryBinding]['operations'][$opname]; + if (isset($binding['soapAction'])) { + $soapaction = $binding['soapAction']; + } else { + $soapaction = null; + } + if (isset($binding['style'])) { + $opstyle = $binding['style']; + } else { + $opstyle = $style; + } + $use = $binding['input']['use']; + if ($use == 'encoded') { + $namespace = $binding['input']['namespace']; + } else { + $bindingType = $this->bindings[$primaryBinding]['type']; + $ns = $this->portTypes[$bindingType][$opname]['input']['namespace']; + $namespace = $this->namespaces[$ns]; + } + + $args = ''; + $argarray = ''; + $comments = ''; + $wrappers = ''; + foreach ($operation['input'] as $argname => $argtype) { + if ($argname == 'message') { + foreach ($this->messages[$argtype] as $_argname => $_argtype) { + $_argname = $this->_sanitize($_argname); + if ($opstyle == 'document' && $use == 'literal' && + $_argtype['name'] == 'parameters') { + // The type or element refered to is used for + // parameters. + $elattrs = null; + $el = $this->elements[$_argtype['namespace']][$_argtype['type']]; + + if ($el['complex']) { + $namespace = $this->namespaces[$_argtype['namespace']]; + // XXX need to wrap the parameters in a + // SOAP_Value. + } + if (isset($el['elements'])) { + foreach ($el['elements'] as $elname => $elattrs) { + $elname = $this->_sanitize($elname); + // Is the element a complex type? + if (isset($this->complexTypes[$elattrs['namespace']][$elname])) { + $comments .= $this->_complexTypeArg($args, $argarray, $_argtype, $_argname); + } else { + $this->_addArg($args, $argarray, $elname); + } + } + } + if ($el['complex'] && $argarray) { + $wrapname = '{' . $this->namespaces[$_argtype['namespace']].'}' . $el['name']; + $comments .= " \${$el['name']} = new SOAP_Value('$wrapname', false, \$v = array($argarray));\n"; + $argarray = "'{$el['name']}' => \${$el['name']}"; + } + } else { + if (isset($_argtype['element'])) { + // Element argument. + $comments .= $this->_elementArg($args, $argarray, $_argtype, $_argtype['type']); + } else { + // Complex type argument. + $comments .= $this->_complexTypeArg($args, $argarray, $_argtype, $_argname); + } + } + } + } + } + + // Validate entries. + + // Operation names are function names, so try to make sure it's + // legal. This could potentially cause collisions, but let's try + // to make everything callable and see how many problems that + // causes. + $opname_php = $this->_sanitize($opname); + if (!$this->_validateString($opname_php)) { + return null; + } + + if ($argarray) { + $argarray = "array($argarray)"; + } else { + $argarray = 'null'; + } + + $class .= " function &$opname_php($args)\n {\n$comments$wrappers" . + " \$result = \$this->call('$opname',\n" . + " \$v = $argarray,\n" . + " array('namespace' => '$namespace',\n" . + " 'soapaction' => '$soapaction',\n" . + " 'style' => '$opstyle',\n" . + " 'use' => '$use'" . + ($this->trace ? ",\n 'trace' => true" : '') . "));\n" . + " return \$result;\n" . + " }\n"; + } + + $class .= "}\n"; + + return $class; + } + + function generateAllProxies() + { + $proxycode = ''; + foreach (array_keys($this->services[$this->service]['ports']) as $key) { + $port =& $this->services[$this->service]['ports'][$key]; + $proxycode .= $this->generateProxyCode($port); + } + return $proxycode; + } + + function &getProxy($port = '', $name = '') + { + if ($this->_isfault()) { + $fault =& $this->_getfault(); + return $fault; + } + + $multiport = count($this->services[$this->service]['ports']) > 1; + + if (!$port) { + reset($this->services[$this->service]['ports']); + $port = current($this->services[$this->service]['ports']); + } + + if ($multiport || $port) { + $classname = 'WebService_' . $this->service . '_' . $port['name']; + } else { + $classname = 'WebService_' . $this->service; + } + + if ($name) { + $classname = $name . '_' . $classname; + } + + $classname = $this->_sanitize($classname); + if (!class_exists($classname)) { + $proxy = $this->generateProxyCode($port, $classname); + require_once 'SOAP/Client.php'; + eval($proxy); + } + $proxy = new $classname; + + return $proxy; + } + + /** + * Sanitizes a SOAP value, method or class name so that it can be used as + * a valid PHP identifier. Invalid characters are converted into + * underscores and reserved words are prefixed with an underscore. + * + * @param string $name The identifier to sanitize. + * + * @return string The sanitized identifier. + */ + function _sanitize($name) + { + $name = preg_replace($this->_invalid, '_', $name); + if (in_array($name, $this->_reserved)) { + $name = '_' . $name; + } + return $name; + } + + function &_getComplexTypeForElement($name, $namespace) + { + $t = null; + if (isset($this->ns[$namespace]) && + isset($this->elements[$this->ns[$namespace]][$name]['type'])) { + + $type = $this->elements[$this->ns[$namespace]][$name]['type']; + $ns = $this->elements[$this->ns[$namespace]][$name]['namespace']; + + if (isset($this->complexTypes[$ns][$type])) { + $t = $this->complexTypes[$ns][$type]; + } + } + return $t; + } + + function getComplexTypeNameForElement($name, $namespace) + { + $t = $this->_getComplexTypeForElement($name, $namespace); + if ($t) { + return $t['name']; + } + return null; + } + + function getComplexTypeChildType($ns, $name, $child_ns, $child_name) + { + // Is the type an element? + $t = $this->_getComplexTypeForElement($name, $ns); + if ($t) { + // No, get it from complex types directly. + if (isset($t['elements'][$child_name]['type'])) + return $t['elements'][$child_name]['type']; + } elseif (isset($this->ns[$ns]) && + isset($this->elements[$this->ns[$ns]][$name]['complex']) && + $this->elements[$this->ns[$ns]][$name]['complex']) { + // Type is not an element but complex. + return $this->elements[$this->ns[$ns]][$name]['elements'][$child_name]['type']; + } + return null; + } + + /** + * @param QName $name A parameter name. + * @param QName $type A parameter type. + * + * @return array A list of [type, array element type, array element + * namespace, array length]. + */ + function getSchemaType($type, $name) + { + // see if it's a complex type so we can deal properly with + // SOAPENC:arrayType. + if ($name && $type) { + // XXX TODO: + // look up the name in the wsdl and validate the type. + foreach ($this->complexTypes as $types) { + if (isset($types[$type->name])) { + if (isset($types[$type->name]['type'])) { + list($arraytype_ns, $arraytype, $array_depth) = isset($types[$type->name]['arrayType']) + ? $this->_getDeepestArrayType($types[$type->name]['namespace'], $types[$type->name]['arrayType']) + : array($this->namespaces[$types[$type->name]['namespace']], null, 0); + return array($types[$type->name]['type'], $arraytype, $arraytype_ns, $array_depth); + } + if (isset($types[$type->name]['arrayType'])) { + list($arraytype_ns, $arraytype, $array_depth) = + $this->_getDeepestArrayType($types[$type->name]['namespace'], $types[$type->name]['arrayType']); + return array('Array', $arraytype, $arraytype_ns, $array_depth); + } + if (!empty($types[$type->name]['elements'][$name->name])) { + $type->name = $types[$type->name]['elements']['type']; + return array($type->name, null, $this->namespaces[$types[$type->name]['namespace']], null); + } + break; + } + } + } + if ($type && $type->namespace) { + $arrayType = null; + // XXX TODO: + // this code currently handles only one way of encoding array + // types in wsdl need to do a generalized function to figure out + // complex types + $p = $this->ns[$type->namespace]; + if ($p && !empty($this->complexTypes[$p][$type->name])) { + if ($arrayType = $this->complexTypes[$p][$type->name]['arrayType']) { + $type->name = 'Array'; + } elseif ($this->complexTypes[$p][$type->name]['order'] == 'sequence' && + array_key_exists('elements', $this->complexTypes[$p][$type->name])) { + reset($this->complexTypes[$p][$type->name]['elements']); + // assume an array + if (count($this->complexTypes[$p][$type->name]['elements']) == 1) { + $arg = current($this->complexTypes[$p][$type->name]['elements']); + $arrayType = $arg['type']; + $type->name = 'Array'; + } else { + foreach ($this->complexTypes[$p][$type->name]['elements'] as $element) { + if ($element['name'] == $type->name) { + $arrayType = $element['type']; + $type->name = $element['type']; + } + } + } + } else { + $type->name = 'Struct'; + } + return array($type->name, $arrayType, $type->namespace, null); + } + } + return array(null, null, null, null); + } + + /** + * Recurse through the WSDL structure looking for the innermost array type + * of multi-dimensional arrays. + * + * Takes a namespace prefix and a type, which can be in the form 'type' or + * 'type[]', and returns the full namespace URI, the type of the most + * deeply nested array type found, and the number of levels of nesting. + * + * @access private + * @return mixed array or nothing + */ + function _getDeepestArrayType($nsPrefix, $arrayType) + { + static $trail = array(); + + $arrayType = ereg_replace('\[\]$', '', $arrayType); + + // Protect against circular references XXX We really need to remove + // trail from this altogether (it's very inefficient and in the wrong + // place!) and put circular reference checking in when the WSDL info + // is generated in the first place + if (array_search($nsPrefix . ':' . $arrayType, $trail)) { + return array(null, null, -count($trail)); + } + + if (array_key_exists($nsPrefix, $this->complexTypes) && + array_key_exists($arrayType, $this->complexTypes[$nsPrefix]) && + array_key_exists('arrayType', $this->complexTypes[$nsPrefix][$arrayType])) { + $trail[] = $nsPrefix . ':' . $arrayType; + $result = $this->_getDeepestArrayType($this->complexTypes[$nsPrefix][$arrayType]['namespace'], + $this->complexTypes[$nsPrefix][$arrayType]['arrayType']); + return array($result[0], $result[1], $result[2] + 1); + } + return array($this->namespaces[$nsPrefix], $arrayType, 0); + } + +} + +class SOAP_WSDL_Cache extends SOAP_Base +{ + /** + * Use WSDL cache? + * + * @var boolean + */ + var $_cacheUse; + + /** + * WSDL cache directory. + * + * @var string + */ + var $_cacheDir; + + /** + * Cache maximum lifetime (in seconds) + * + * @var integer + */ + var $_cacheMaxAge; + + /** + * Constructor. + * + * @param boolean $cashUse Use caching? + * @param integer $cacheMaxAge Cache maximum lifetime (in seconds) + */ + function SOAP_WSDL_Cache($cacheUse = false, + $cacheMaxAge = WSDL_CACHE_MAX_AGE, + $cacheDir = null) + { + parent::SOAP_Base('WSDLCACHE'); + $this->_cacheUse = $cacheUse; + $this->_cacheDir = $cacheDir; + $this->_cacheMaxAge = $cacheMaxAge; + } + + /** + * Returns the path to the cache and creates it, if it doesn't exist. + * + * @private + * + * @return string The directory to use for the cache. + */ + function _cacheDir() + { + if (!empty($this->_cacheDir)) { + $dir = $this->_cacheDir; + } else { + $dir = getenv('WSDLCACHE'); + if (empty($dir)) { + $dir = './wsdlcache'; + } + } + @mkdir($dir, 0700); + return $dir; + } + + /** + * Retrieves a file from cache if it exists, otherwise retreive from net, + * add to cache, and return from cache. + * + * @param string URL to WSDL + * @param array proxy parameters + * @param int expected MD5 of WSDL URL + * @access public + * @return string data + */ + function get($wsdl_fname, $proxy_params = array(), $cache = 0) + { + $cachename = $md5_wsdl = $file_data = ''; + if ($this->_cacheUse) { + // Try to retrieve WSDL from cache + $cachename = $this->_cacheDir() . '/' . md5($wsdl_fname). ' .wsdl'; + if (file_exists($cachename) && + $file_data = file_get_contents($cachename)) { + $md5_wsdl = md5($file_data); + if ($cache) { + if ($cache != $md5_wsdl) { + return $this->_raiseSoapFault('WSDL Checksum error!', $wsdl_fname); + } + } else { + $fi = stat($cachename); + $cache_mtime = $fi[8]; + if ($cache_mtime + $this->_cacheMaxAge < time()) { + // Expired, refetch. + $md5_wsdl = ''; + } + } + } + } + + // Not cached or not using cache. Retrieve WSDL from URL + if (!$md5_wsdl) { + // Is it a local file? + if (strpos($wsdl_fname, 'file://') === 0) { + $wsdl_fname = substr($wsdl_fname, 7); + if (!file_exists($wsdl_fname)) { + return $this->_raiseSoapFault('Unable to read local WSDL file', $wsdl_fname); + } + $file_data = file_get_contents($wsdl_fname); + } elseif (!preg_match('|^https?://|', $wsdl_fname)) { + return $this->_raiseSoapFault('Unknown schema of WSDL URL', $wsdl_fname); + } else { + $uri = explode('?', $wsdl_fname); + $rq = new HTTP_Request($uri[0], $proxy_params); + // the user agent HTTP_Request uses fouls things up + if (isset($uri[1])) { + $rq->addRawQueryString($uri[1]); + } + + if (isset($proxy_params['proxy_host']) && + isset($proxy_params['proxy_port']) && + isset($proxy_params['proxy_user']) && + isset($proxy_params['proxy_pass'])) { + $rq->setProxy($proxy_params['proxy_host'], + $proxy_params['proxy_port'], + $proxy_params['proxy_user'], + $proxy_params['proxy_pass']); + } elseif (isset($proxy_params['proxy_host']) && + isset($proxy_params['proxy_port'])) { + $rq->setProxy($proxy_params['proxy_host'], + $proxy_params['proxy_port']); + } + + $result = $rq->sendRequest(); + if (PEAR::isError($result)) { + return $this->_raiseSoapFault("Unable to retrieve WSDL $wsdl_fname," . $rq->getResponseCode(), $wsdl_fname); + } + $file_data = $rq->getResponseBody(); + if (!$file_data) { + return $this->_raiseSoapFault("Unable to retrieve WSDL $wsdl_fname, no http body", $wsdl_fname); + } + } + + $md5_wsdl = md5($file_data); + + if ($this->_cacheUse) { + $fp = fopen($cachename, "wb"); + fwrite($fp, $file_data); + fclose($fp); + } + } + + if ($this->_cacheUse && $cache && $cache != $md5_wsdl) { + return $this->_raiseSoapFault('WSDL Checksum error!', $wsdl_fname); + } + + return $file_data; + } + +} + +class SOAP_WSDL_Parser extends SOAP_Base +{ + + /** + * Define internal arrays of bindings, ports, operations, + * messages, etc. + */ + var $currentMessage; + var $currentOperation; + var $currentPortType; + var $currentBinding; + var $currentPort; + + /** + * Parser vars. + */ + var $cache; + + var $tns = null; + var $soapns = array('soap'); + var $uri = ''; + var $wsdl = null; + + var $status = ''; + var $element_stack = array(); + var $parentElement = ''; + + var $schema = ''; + var $schemaStatus = ''; + var $schema_stack = array(); + var $currentComplexType; + var $schema_element_stack = array(); + var $currentElement; + + /** + * Constructor. + */ + function SOAP_WSDL_Parser($uri, &$wsdl, $docs = false) + { + parent::SOAP_Base('WSDLPARSER'); + $this->cache = new SOAP_WSDL_Cache($wsdl->cacheUse, + $wsdl->cacheMaxAge, + $wsdl->cacheDir); + $this->uri = $uri; + $this->wsdl = &$wsdl; + $this->docs = $docs; + $this->parse($uri); + } + + function parse($uri) + { + // Check whether content has been read. + $fd = $this->cache->get($uri, $this->wsdl->proxy); + if (PEAR::isError($fd)) { + return $this->_raiseSoapFault($fd); + } + + // Create an XML parser. + $parser = xml_parser_create(); + xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0); + xml_set_object($parser, $this); + xml_set_element_handler($parser, 'startElement', 'endElement'); + if ($this->docs) { + xml_set_character_data_handler($parser, 'characterData'); + } + + if (!xml_parse($parser, $fd, true)) { + $detail = sprintf('XML error on line %d: %s', + xml_get_current_line_number($parser), + xml_error_string(xml_get_error_code($parser))); + return $this->_raiseSoapFault("Unable to parse WSDL file $uri\n$detail"); + } + xml_parser_free($parser); + return true; + } + + /** + * start-element handler + */ + function startElement($parser, $name, $attrs) + { + // Get element prefix. + $qname = new QName($name); + if ($qname->ns) { + $ns = $qname->ns; + if ($ns && ((!$this->tns && strcasecmp($qname->name, 'definitions') == 0) || $ns == $this->tns)) { + $name = $qname->name; + } + } + $this->currentTag = $qname->name; + $this->parentElement = ''; + $stack_size = count($this->element_stack); + if ($stack_size) { + $this->parentElement = $this->element_stack[$stack_size - 1]; + } + $this->element_stack[] = $this->currentTag; + + // Find status, register data. + switch ($this->status) { + case 'types': + // sect 2.2 wsdl:types + // children: xsd:schema + $parent_tag = ''; + $stack_size = count($this->schema_stack); + if ($stack_size) { + $parent_tag = $this->schema_stack[$stack_size - 1]; + } + + switch ($qname->name) { + case 'schema': + // No parent should be in the stack. + if (!$parent_tag || $parent_tag == 'types') { + if (array_key_exists('targetNamespace', $attrs)) { + $this->schema = $this->wsdl->getNamespaceAttributeName($attrs['targetNamespace']); + } else { + $this->schema = $this->wsdl->getNamespaceAttributeName($this->wsdl->tns); + } + $this->wsdl->complexTypes[$this->schema] = array(); + $this->wsdl->elements[$this->schema] = array(); + } + break; + + case 'complexType': + if ($parent_tag == 'schema') { + $this->currentComplexType = $attrs['name']; + if (!isset($attrs['namespace'])) { + $attrs['namespace'] = $this->schema; + } + $this->wsdl->complexTypes[$this->schema][$this->currentComplexType] = $attrs; + if (array_key_exists('base', $attrs)) { + $qn = new QName($attrs['base']); + $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = $qn->name; + $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['namespace'] = $qn->ns; + } else { + $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = 'Struct'; + } + $this->schemaStatus = 'complexType'; + } else { + $this->wsdl->elements[$this->schema][$this->currentElement]['complex'] = true; + } + break; + + case 'element': + if (isset($attrs['type'])) { + $qn = new QName($attrs['type']); + $attrs['type'] = $qn->name; + if ($qn->ns && array_key_exists($qn->ns, $this->wsdl->namespaces)) { + $attrs['namespace'] = $qn->ns; + } + } + + $parentElement = ''; + $stack_size = count($this->schema_element_stack); + if ($stack_size > 0) { + $parentElement = $this->schema_element_stack[$stack_size - 1]; + } + + if (isset($attrs['ref'])) { + $qn = new QName($attrs['ref']); + $this->currentElement = $qn->name; + } else { + $this->currentElement = $attrs['name']; + } + $this->schema_element_stack[] = $this->currentElement; + if (!isset($attrs['namespace'])) { + $attrs['namespace'] = $this->schema; + } + + if ($parent_tag == 'schema') { + $this->wsdl->elements[$this->schema][$this->currentElement] = $attrs; + $this->wsdl->elements[$this->schema][$this->currentElement]['complex'] = false; + $this->schemaStatus = 'element'; + } elseif ($this->currentComplexType) { + // we're inside a complexType + if ((isset($this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['order']) && + $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['order'] == 'sequence') + && $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] == 'Array') { + $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['arrayType'] = isset($attrs['type']) ? $attrs['type'] : null; + } + $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['elements'][$this->currentElement] = $attrs; + } else { + $this->wsdl->elements[$this->schema][$parentElement]['elements'][$this->currentElement] = $attrs; + } + break; + + case 'complexContent': + case 'simpleContent': + break; + + case 'extension': + case 'restriction': + if ($this->schemaStatus == 'complexType') { + if (!empty($attrs['base'])) { + $qn = new QName($attrs['base']); + $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = $qn->name; + + // Types that extend from other types aren't + // *of* those types. Reflect this by denoting + // which type they extend. I'm leaving the + // 'type' setting here since I'm not sure what + // removing it might break at the moment. + if ($qname->name == 'extension') { + $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['extends'] = $qn->name; + } + } else { + $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = 'Struct'; + } + } + break; + + case 'sequence': + if ($this->schemaStatus == 'complexType') { + $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['order'] = $qname->name; + if (!isset($this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'])) { + $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = 'Array'; + } + } + break; + + case 'all': + $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['order'] = $qname->name; + if (!isset($this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'])) { + $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = 'Struct'; + } + break; + + case 'choice': + $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['order'] = $qname->name; + if (!isset($this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'])) { + $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = 'Array'; + } + + case 'attribute': + if ($this->schemaStatus == 'complexType') { + if (isset($attrs['name'])) { + $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['attribute'][$attrs['name']] = $attrs; + } else { + if (isset($attrs['ref'])) { + $q = new QName($attrs['ref']); + foreach ($attrs as $k => $v) { + if ($k != 'ref' && strstr($k, $q->name)) { + $vq = new QName($v); + if ($q->name == 'arrayType') { + $this->wsdl->complexTypes[$this->schema][$this->currentComplexType][$q->name] = $vq->name. $vq->arrayInfo; + $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = 'Array'; + $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['namespace'] = $vq->ns; + } else { + $this->wsdl->complexTypes[$this->schema][$this->currentComplexType][$q->name] = $vq->name; + } + } + } + } + } + } + break; + } + + $this->schema_stack[] = $qname->name; + break; + + case 'message': + // sect 2.3 wsdl:message child wsdl:part + switch ($qname->name) { + case 'part': + $qn = null; + if (isset($attrs['type'])) { + $qn = new QName($attrs['type']); + } elseif (isset($attrs['element'])) { + $qn = new QName($attrs['element']); + } + if ($qn) { + $attrs['type'] = $qn->name; + $attrs['namespace'] = $qn->ns; + } + $this->wsdl->messages[$this->currentMessage][$attrs['name']] = $attrs; + // error in wsdl + + case 'documentation': + break; + + default: + break; + } + break; + + case 'portType': + // sect 2.4 + switch ($qname->name) { + case 'operation': + // attributes: name + // children: wsdl:input wsdl:output wsdl:fault + $this->currentOperation = $attrs['name']; + $this->wsdl->portTypes[$this->currentPortType][$this->currentOperation] = $attrs; + break; + + case 'input': + case 'output': + case 'fault': + // wsdl:input wsdl:output wsdl:fault + // attributes: name message parameterOrder(optional) + if ($this->currentOperation) { + if (isset($this->wsdl->portTypes[$this->currentPortType][$this->currentOperation][$name])) { + $this->wsdl->portTypes[$this->currentPortType][$this->currentOperation][$name] = array_merge($this->wsdl->portTypes[$this->currentPortType][$this->currentOperation][$name], $attrs); + } else { + $this->wsdl->portTypes[$this->currentPortType][$this->currentOperation][$name] = $attrs; + } + if (array_key_exists('message', $attrs)) { + $qn = new QName($attrs['message']); + $this->wsdl->portTypes[$this->currentPortType][$this->currentOperation][$name]['message'] = $qn->name; + $this->wsdl->portTypes[$this->currentPortType][$this->currentOperation][$name]['namespace'] = $qn->ns; + } + } + break; + + case 'documentation': + break; + + default: + break; + } + break; + + case 'binding': + $ns = $qname->ns ? $this->wsdl->namespaces[$qname->ns] : SCHEMA_WSDL; + switch ($ns) { + case SCHEMA_SOAP: + case SCHEMA_SOAP12: + // this deals with wsdl section 3 soap binding + switch ($qname->name) { + case 'binding': + // sect 3.3 + // soap:binding, attributes: transport(required), style(optional, default = document) + // if style is missing, it is assumed to be 'document' + if (!isset($attrs['style'])) { + $attrs['style'] = 'document'; + } + $this->wsdl->bindings[$this->currentBinding] = array_merge($this->wsdl->bindings[$this->currentBinding], $attrs); + break; + + case 'operation': + // sect 3.4 + // soap:operation, attributes: soapAction(required), style(optional, default = soap:binding:style) + if (!isset($attrs['style'])) { + $attrs['style'] = $this->wsdl->bindings[$this->currentBinding]['style']; + } + if (isset($this->wsdl->bindings[$this->currentBinding]['operations'][$this->currentOperation])) { + $this->wsdl->bindings[$this->currentBinding]['operations'][$this->currentOperation] = array_merge($this->wsdl->bindings[$this->currentBinding]['operations'][$this->currentOperation], $attrs); + } else { + $this->wsdl->bindings[$this->currentBinding]['operations'][$this->currentOperation] = $attrs; + } + break; + + case 'body': + // sect 3.5 + // soap:body attributes: + // part - optional. listed parts must appear in body, missing means all parts appear in body + // use - required. encoded|literal + // encodingStyle - optional. space seperated list of encodings (uri's) + $this->wsdl->bindings[$this->currentBinding] + ['operations'][$this->currentOperation][$this->opStatus] = $attrs; + break; + + case 'fault': + // sect 3.6 + // soap:fault attributes: name use encodingStyle namespace + $this->wsdl->bindings[$this->currentBinding] + ['operations'][$this->currentOperation][$this->opStatus] = $attrs; + break; + + case 'header': + // sect 3.7 + // soap:header attributes: message part use encodingStyle namespace + $this->wsdl->bindings[$this->currentBinding] + ['operations'][$this->currentOperation][$this->opStatus]['headers'][] = $attrs; + break; + + case 'headerfault': + // sect 3.7 + // soap:header attributes: message part use encodingStyle namespace + $header = count($this->wsdl->bindings[$this->currentBinding] + ['operations'][$this->currentOperation][$this->opStatus]['headers'])-1; + $this->wsdl->bindings[$this->currentBinding] + ['operations'][$this->currentOperation][$this->opStatus]['headers'][$header]['fault'] = $attrs; + break; + + case 'documentation': + break; + + default: + // error! not a valid element inside binding + break; + } + break; + + case SCHEMA_WSDL: + // XXX verify correct namespace + // for now, default is the 'wsdl' namespace + // other possible namespaces include smtp, http, etc. for alternate bindings + switch ($qname->name) { + case 'operation': + // sect 2.5 + // wsdl:operation attributes: name + $this->currentOperation = $attrs['name']; + break; + + case 'output': + case 'input': + case 'fault': + // sect 2.5 + // wsdl:input attributes: name + $this->opStatus = $qname->name; + break; + + case 'documentation': + break; + + default: + break; + } + break; + + case SCHEMA_WSDL_HTTP: + switch ($qname->name) { + case 'binding': + // sect 4.4 + // http:binding attributes: verb + // parent: wsdl:binding + $this->wsdl->bindings[$this->currentBinding] = array_merge($this->wsdl->bindings[$this->currentBinding], $attrs); + break; + + case 'operation': + // sect 4.5 + // http:operation attributes: location + // parent: wsdl:operation + $this->wsdl->bindings[$this->currentBinding]['operations'] + [$this->currentOperation] = $attrs; + break; + + case 'urlEncoded': + // sect 4.6 + // http:urlEncoded attributes: location + // parent: wsdl:input wsdl:output etc. + $this->wsdl->bindings[$this->currentBinding]['operations'][$this->opStatus] + [$this->currentOperation]['uri'] = 'urlEncoded'; + break; + + case 'urlReplacement': + // sect 4.7 + // http:urlReplacement attributes: location + // parent: wsdl:input wsdl:output etc. + $this->wsdl->bindings[$this->currentBinding]['operations'][$this->opStatus] + [$this->currentOperation]['uri'] = 'urlReplacement'; + break; + + case 'documentation': + break; + + default: + // error + break; + } + + case SCHEMA_MIME: + // sect 5 + // all mime parts are children of wsdl:input, wsdl:output, etc. + // unsuported as of yet + switch ($qname->name) { + case 'content': + // sect 5.3 mime:content + // <mime:content part="nmtoken"? type="string"?/> + // part attribute only required if content is child of multipart related, + // it contains the name of the part + // type attribute contains the mime type + case 'multipartRelated': + // sect 5.4 mime:multipartRelated + case 'part': + case 'mimeXml': + // sect 5.6 mime:mimeXml + // <mime:mimeXml part="nmtoken"?/> + // + case 'documentation': + break; + + default: + // error + break; + } + + case SCHEMA_DIME: + // DIME is defined in: + // http://gotdotnet.com/team/xml_wsspecs/dime/WSDL-Extension-for-DIME.htm + // all DIME parts are children of wsdl:input, wsdl:output, etc. + // unsuported as of yet + switch ($qname->name) { + case 'message': + // sect 4.1 dime:message + // appears in binding section + $this->wsdl->bindings[$this->currentBinding]['dime'] = $attrs; + break; + + default: + break; + } + + default: + break; + } + break; + + case 'service': + $ns = $qname->ns ? $this->wsdl->namespaces[$qname->ns] : SCHEMA_WSDL; + + switch ($qname->name) { + case 'port': + // sect 2.6 wsdl:port attributes: name binding + $this->currentPort = $attrs['name']; + $this->wsdl->services[$this->currentService]['ports'][$this->currentPort] = $attrs; + // XXX hack to deal with binding namespaces + $qn = new QName($attrs['binding']); + $this->wsdl->services[$this->currentService]['ports'][$this->currentPort]['binding'] = $qn->name; + $this->wsdl->services[$this->currentService]['ports'][$this->currentPort]['namespace'] = $qn->ns; + break; + + case 'address': + $this->wsdl->services[$this->currentService]['ports'][$this->currentPort]['address'] = $attrs; + // what TYPE of port is it? SOAP or HTTP? + $ns = $qname->ns ? $this->wsdl->namespaces[$qname->ns] : SCHEMA_WSDL; + switch ($ns) { + case SCHEMA_WSDL_HTTP: + $this->wsdl->services[$this->currentService]['ports'][$this->currentPort]['type']='http'; + break; + + case SCHEMA_SOAP: + $this->wsdl->services[$this->currentService]['ports'][$this->currentPort]['type']='soap'; + break; + + default: + // Shouldn't happen, we'll assume SOAP. + $this->wsdl->services[$this->currentService]['ports'][$this->currentPort]['type']='soap'; + } + + break; + + case 'documentation': + break; + + default: + break; + } + } + + // Top level elements found under wsdl:definitions. + switch ($qname->name) { + case 'import': + // sect 2.1.1 wsdl:import attributes: namespace location + if ((isset($attrs['location']) || isset($attrs['schemaLocation'])) && + !isset($this->wsdl->imports[$attrs['namespace']])) { + $uri = isset($attrs['location']) ? $attrs['location'] : $attrs['schemaLocation']; + $location = @parse_url($uri); + if (!isset($location['scheme'])) { + $base = @parse_url($this->uri); + $uri = $this->mergeUrl($base, $uri); + } + + $this->wsdl->imports[$attrs['namespace']] = $attrs; + $import_parser_class = get_class($this); + $import_parser = new $import_parser_class($uri, $this->wsdl, $this->docs); + if ($import_parser->fault) { + unset($this->wsdl->imports[$attrs['namespace']]); + return false; + } + $this->currentImport = $attrs['namespace']; + } + // Continue on to the 'types' case - lack of break; is + // intentional. + + case 'types': + // sect 2.2 wsdl:types + $this->status = 'types'; + break; + + case 'schema': + // We can hit this at the top level if we've been asked to + // import an XSD file. + if (!empty($attrs['targetNamespace'])) { + $this->schema = $this->wsdl->getNamespaceAttributeName($attrs['targetNamespace']); + } else { + $this->schema = $this->wsdl->getNamespaceAttributeName($this->wsdl->tns); + } + $this->wsdl->complexTypes[$this->schema] = array(); + $this->wsdl->elements[$this->schema] = array(); + $this->schema_stack[] = $qname->name; + $this->status = 'types'; + break; + + case 'message': + // sect 2.3 wsdl:message attributes: name children:wsdl:part + $this->status = 'message'; + if (isset($attrs['name'])) { + $this->currentMessage = $attrs['name']; + $this->wsdl->messages[$this->currentMessage] = array(); + } + break; + + case 'portType': + // sect 2.4 wsdl:portType + // attributes: name + // children: wsdl:operation + $this->status = 'portType'; + $this->currentPortType = $attrs['name']; + $this->wsdl->portTypes[$this->currentPortType] = array(); + break; + + case 'binding': + // sect 2.5 wsdl:binding attributes: name type + // children: wsdl:operation soap:binding http:binding + if ($qname->ns && $qname->ns != $this->tns) { + break; + } + $this->status = 'binding'; + $this->currentBinding = $attrs['name']; + $qn = new QName($attrs['type']); + $this->wsdl->bindings[$this->currentBinding]['type'] = $qn->name; + $this->wsdl->bindings[$this->currentBinding]['namespace'] = $qn->ns; + break; + + case 'service': + // sect 2.7 wsdl:service attributes: name children: ports + $this->currentService = $attrs['name']; + $this->wsdl->services[$this->currentService]['ports'] = array(); + $this->status = 'service'; + break; + + case 'definitions': + // sec 2.1 wsdl:definitions + // attributes: name targetNamespace xmlns:* + // children: wsdl:import wsdl:types wsdl:message wsdl:portType wsdl:binding wsdl:service + $this->wsdl->definition = $attrs; + foreach ($attrs as $key => $value) { + if (strstr($key, 'xmlns:') !== false) { + $qn = new QName($key); + // XXX need to refactor ns handling. + $this->wsdl->namespaces[$qn->name] = $value; + $this->wsdl->ns[$value] = $qn->name; + if ($key == 'targetNamespace' || + strcasecmp($value,SOAP_SCHEMA) == 0) { + $this->soapns[] = $qn->name; + } else { + if (in_array($value, $this->_XMLSchema)) { + $this->wsdl->xsd = $value; + } + } + } + } + if (isset($ns) && $ns) { + $namespace = 'xmlns:' . $ns; + if (!$this->wsdl->definition[$namespace]) { + return $this->_raiseSoapFault("parse error, no namespace for $namespace", $this->uri); + } + $this->tns = $ns; + } + break; + } + } + + /** + * end-element handler. + */ + function endElement($parser, $name) + { + $stacksize = count($this->element_stack); + if ($stacksize) { + if ($this->element_stack[$stacksize - 1] == 'definitions') { + $this->status = ''; + } + array_pop($this->element_stack); + } + + if (stristr($name, 'schema')) { + array_pop($this->schema_stack); + $this->schema = ''; + } + + if ($this->schema) { + array_pop($this->schema_stack); + if (count($this->schema_stack) <= 1) { + /* Correct the type for sequences with multiple + * elements. */ + if (isset($this->currentComplexType) && isset($this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type']) + && $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] == 'Array' + && array_key_exists('elements', $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]) + && count($this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['elements']) > 1) { + $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = 'Struct'; + } + } + if (stristr($name, 'complexType')) { + $this->currentComplexType = ''; + if (count($this->schema_element_stack)) { + $this->currentElement = array_pop($this->schema_element_stack); + } else { + $this->currentElement = ''; + } + } elseif (stristr($name, 'element')) { + if (count($this->schema_element_stack)) { + $this->currentElement = array_pop($this->schema_element_stack); + } else { + $this->currentElement = ''; + } + } + } + } + + /** + * Element content handler. + */ + function characterData($parser, $data) + { + // Store the documentation in the WSDL file. + if ($this->currentTag == 'documentation') { + $data = trim(preg_replace('/\s+/', ' ', $data)); + if (!strlen($data)) { + return; + } + + switch ($this->status) { + case 'service': + $ptr =& $this->wsdl->services[$this->currentService]; + break; + + case 'portType': + $ptr =& $this->wsdl->portTypes[$this->currentPortType][$this->currentOperation]; + break; + + case 'binding': + $ptr =& $this->wsdl->bindings[$this->currentBinding]; + break; + + case 'message': + $ptr =& $this->wsdl->messages[$this->currentMessage]; + break; + + case 'operation': + break; + + case 'types': + if (isset($this->currentComplexType) && + isset($this->wsdl->complexTypes[$this->schema][$this->currentComplexType])) { + if ($this->currentElement) { + $ptr =& $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['elements'][$this->currentElement]; + } else { + $ptr =& $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]; + } + } + break; + } + + if (isset($ptr)) { + if (!isset($ptr['documentation'])) { + $ptr['documentation'] = ''; + } else { + $ptr['documentation'] .= ' '; + } + $ptr['documentation'] .= $data; + } + } + } + + /** + * $parsed is an array returned by parse_url(). + * + * @access private + */ + function mergeUrl($parsed, $path) + { + if (!is_array($parsed)) { + return false; + } + + $uri = ''; + if (!empty($parsed['scheme'])) { + $sep = (strtolower($parsed['scheme']) == 'mailto' ? ':' : '://'); + $uri = $parsed['scheme'] . $sep; + } + + if (isset($parsed['pass'])) { + $uri .= "$parsed[user]:$parsed[pass]@"; + } elseif (isset($parsed['user'])) { + $uri .= "$parsed[user]@"; + } + + if (isset($parsed['host'])) { + $uri .= $parsed['host']; + } + if (isset($parsed['port'])) { + $uri .= ":$parsed[port]"; + } + if ($path[0] != '/' && isset($parsed['path'])) { + if ($parsed['path'][strlen($parsed['path']) - 1] != '/') { + $path = dirname($parsed['path']) . '/' . $path; + } else { + $path = $parsed['path'] . $path; + } + $path = $this->_normalize($path); + } + $sep = $path[0] == '/' ? '' : '/'; + $uri .= $sep . $path; + + return $uri; + } + + function _normalize($path_str) + { + $pwd = ''; + $strArr = preg_split('/(\/)/', $path_str, -1, PREG_SPLIT_NO_EMPTY); + $pwdArr = ''; + $j = 0; + for ($i = 0; $i < count($strArr); $i++) { + if ($strArr[$i] != ' ..') { + if ($strArr[$i] != ' .') { + $pwdArr[$j] = $strArr[$i]; + $j++; + } + } else { + array_pop($pwdArr); + $j--; + } + } + $pStr = implode('/', $pwdArr); + $pwd = (strlen($pStr) > 0) ? ('/' . $pStr) : '/'; + return $pwd; + } + +} + +/** + * Parses the types and methods used in web service objects into the internal + * data structures used by SOAP_WSDL. + * + * Assumes the SOAP_WSDL class is unpopulated to start with. + * + * @author Chris Coe <info@intelligentstreaming.com> + */ +class SOAP_WSDL_ObjectParser extends SOAP_Base +{ + /** + * Target namespace for the WSDL document will have the following + * prefix. + */ + var $tnsPrefix = 'tns'; + + /** + * Reference to the SOAP_WSDL object to populate. + */ + var $wsdl = null; + + /** + * Constructor. + * + * @param object|array $objects Reference to the object or array of + * objects to parse. + * @param SOAP_WSDL $wsdl Reference to the SOAP_WSDL object to + * populate. + * @param string $targetNamespace The target namespace of schema types + * etc. + * @param string $service_name Name of the WSDL <service>. + * @param string $service_desc Optional description of the WSDL + * <service>. + */ + function SOAP_WSDL_ObjectParser($objects, &$wsdl, $targetNamespace, + $service_name, $service_desc = '') + { + parent::SOAP_Base('WSDLOBJECTPARSER'); + + $this->wsdl = &$wsdl; + + // Set up the SOAP_WSDL object + $this->_initialise($service_name); + + // Parse each web service object + $wsdl_ref = is_array($objects) ? $objects : array($objects); + + foreach ($wsdl_ref as $ref_item) { + if (!is_object($ref_item)) { + $this->_raiseSoapFault('Invalid web service object passed to object parser'); + continue; + } + + if (!$this->_parse($ref_item, $targetNamespace, $service_name)) { + break; + } + } + + // Build bindings from abstract data. + if ($this->fault == null) { + $this->_generateBindingsAndServices($targetNamespace, $service_name, $service_desc); + } + } + + /** + * Initialise the SOAP_WSDL tree (destructive). + * + * If the object has already been initialised, the only effect + * will be to change the tns namespace to the new service name. + * + * @param $service_name Name of the WSDL <service> + * @access private + */ + function _initialise($service_name) + { + // Set up the basic namespaces that all WSDL definitions use. + $this->wsdl->namespaces['wsdl'] = SCHEMA_WSDL; // WSDL language + $this->wsdl->namespaces['soap'] = SCHEMA_SOAP; // WSDL SOAP bindings + $this->wsdl->namespaces[$this->tnsPrefix] = 'urn:' . $service_name; // Target namespace + $this->wsdl->namespaces['xsd'] = array_search('xsd', $this->_namespaces); // XML Schema + $this->wsdl->namespaces[SOAP_BASE::SOAPENCPrefix()] = array_search(SOAP_BASE::SOAPENCPrefix(), $this->_namespaces); // SOAP types + + // XXX Refactor $namespace/$ns for Shane :-) + unset($this->wsdl->ns['urn:' . $service_name]); + $this->wsdl->ns += array_flip($this->wsdl->namespaces); + + // Imports are not implemented in WSDL generation from classes. + // *** <wsdl:import> *** + } + + /** + * Parser - takes a single object to add to tree (non-destructive). + * + * @access private + * + * @param object $object Reference to the object to parse. + * @param string $schemaNamespace + * @param string $service_name Name of the WSDL <service>. + */ + function _parse($object, $schemaNamespace, $service_name) + { + // Create namespace prefix for the schema + list($schPrefix,) = $this->_getTypeNs('{' . $schemaNamespace . '}'); + + // Parse all the types defined by the object in whatever + // schema language we are using (currently __typedef arrays) + // *** <wsdl:types> *** + foreach ($object->__typedef as $typeName => $typeValue) { + // Get/create namespace definition + list($nsPrefix, $typeName) = $this->_getTypeNs($typeName); + + // Create type definition + $this->wsdl->complexTypes[$schPrefix][$typeName] = array('name' => $typeName); + $thisType =& $this->wsdl->complexTypes[$schPrefix][$typeName]; + + // According to Dmitri's documentation, __typedef comes in two + // flavors: + // Array = array(array("item" => "value")) + // Struct = array("item1" => "value1", "item2" => "value2", ...) + if (is_array($typeValue)) { + if (is_array(current($typeValue)) && count($typeValue) == 1 + && count(current($typeValue)) == 1) { + // It's an array + $thisType['type'] = 'Array'; + $nsType = current(current($typeValue)); + list($nsPrefix, $typeName) = $this->_getTypeNs($nsType); + $thisType['namespace'] = $nsPrefix; + $thisType['arrayType'] = $typeName . '[]'; + } elseif (!is_array(current($typeValue))) { + // It's a struct + $thisType['type'] = 'Struct'; + $thisType['order'] = 'all'; + $thisType['namespace'] = $nsPrefix; + $thisType['elements'] = array(); + + foreach ($typeValue as $elementName => $elementType) { + list($nsPrefix, $typeName) = $this->_getTypeNs($elementType); + $thisType['elements'][$elementName]['name'] = $elementName; + $thisType['elements'][$elementName]['type'] = $typeName; + $thisType['elements'][$elementName]['namespace'] = $nsPrefix; + } + } else { + // It's erroneous + return $this->_raiseSoapFault("The type definition for $nsPrefix:$typeName is invalid.", 'urn:' . get_class($object)); + } + } else { + // It's erroneous + return $this->_raiseSoapFault("The type definition for $nsPrefix:$typeName is invalid.", 'urn:' . get_class($object)); + } + } + + // Create an empty element array with the target namespace + // prefix, to match the results of WSDL parsing. + $this->wsdl->elements[$schPrefix] = array(); + + // Populate tree with message information + // *** <wsdl:message> *** + foreach ($object->__dispatch_map as $operationName => $messages) { + // We need at least 'in' and 'out' parameters. + if (!isset($messages['in']) || !isset($messages['out'])) { + return $this->_raiseSoapFault('The dispatch map for the method "' . $operationName . '" is missing an "in" or "out" definition.', 'urn:' . get_class($object)); + } + foreach ($messages as $messageType => $messageParts) { + unset($thisMessage); + + switch ($messageType) { + case 'in': + $this->wsdl->messages[$operationName . 'Request'] = array(); + $thisMessage =& $this->wsdl->messages[$operationName . 'Request']; + break; + + case 'out': + $this->wsdl->messages[$operationName . 'Response'] = array(); + $thisMessage =& $this->wsdl->messages[$operationName . 'Response']; + break; + + case 'alias': + // Do nothing + break; + + default: + // Error condition + break; + } + + if (isset($thisMessage)) { + foreach ($messageParts as $partName => $partType) { + list ($nsPrefix, $typeName) = $this->_getTypeNs($partType); + + $thisMessage[$partName] = array( + 'name' => $partName, + 'type' => $typeName, + 'namespace' => $nsPrefix + ); + } + } + } + } + + // Populate tree with portType information + // XXX Current implementation only supports one portType that + // encompasses all of the operations available. + // *** <wsdl:portType> *** + if (!isset($this->wsdl->portTypes[$service_name . 'Port'])) { + $this->wsdl->portTypes[$service_name . 'Port'] = array(); + } + $thisPortType =& $this->wsdl->portTypes[$service_name . 'Port']; + + foreach ($object->__dispatch_map as $operationName => $messages) { + $thisPortType[$operationName] = array('name' => $operationName); + + foreach ($messages as $messageType => $messageParts) { + switch ($messageType) { + case 'in': + $thisPortType[$operationName]['input'] = array( + 'message' => $operationName . 'Request', + 'namespace' => $this->tnsPrefix); + break; + + case 'out': + $thisPortType[$operationName]['output'] = array( + 'message' => $operationName . 'Response', + 'namespace' => $this->tnsPrefix); + break; + } + } + } + + return true; + } + + /** + * Takes all the abstract WSDL data and builds concrete bindings and + * services (destructive). + * + * @access private + * @todo Current implementation discards $service_desc. + * + * @param string $schemaNamespace Namespace for types etc. + * @param string $service_name Name of the WSDL <service>. + * @param string $service_desc Optional description of the WSDL + * <service>. + */ + function _generateBindingsAndServices($schemaNamespace, $service_name, + $service_desc = '') + { + // Populate tree with bindings information + // XXX Current implementation only supports one binding that + // matches the single portType and all of its operations. + // XXX Is this the correct use of $schemaNamespace here? + // *** <wsdl:binding> *** + $this->wsdl->bindings[$service_name . 'Binding'] = array( + 'type' => $service_name . 'Port', + 'namespace' => $this->tnsPrefix, + 'style' => 'rpc', + 'transport' => SCHEMA_SOAP_HTTP, + 'operations' => array()); + $thisBinding =& $this->wsdl->bindings[$service_name . 'Binding']; + + foreach ($this->wsdl->portTypes[$service_name . 'Port'] as $operationName => $operationData) { + $thisBinding['operations'][$operationName] = array( + 'soapAction' => $schemaNamespace . '#' . $operationName, + 'style' => $thisBinding['style']); + + foreach (array('input', 'output') as $messageType) + if (isset($operationData[$messageType])) { + $thisBinding['operations'][$operationName][$messageType] = array( + 'use' => 'encoded', + 'namespace' => $schemaNamespace, + 'encodingStyle' => SOAP_SCHEMA_ENCODING); + } + } + + // Populate tree with service information + // XXX Current implementation supports one service which groups + // all of the ports together, one port per binding + // *** <wsdl:service> *** + + $this->wsdl->services[$service_name . 'Service'] = array('ports' => array()); + $thisService =& $this->wsdl->services[$service_name . 'Service']['ports']; + $https = (isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] == 'on')) || + getenv('SSL_PROTOCOL_VERSION'); + + foreach ($this->wsdl->bindings as $bindingName => $bindingData) { + $thisService[$bindingData['type']] = array( + 'name' => $bindingData['type'], + 'binding' => $bindingName, + 'namespace' => $this->tnsPrefix, + 'address' => array('location' => + ($https ? 'https://' : 'http://') . + $_SERVER['SERVER_NAME'] . $_SERVER['PHP_SELF'] . + (isset($_SERVER['QUERY_STRING']) ? '?' . $_SERVER['QUERY_STRING'] : '')), + 'type' => 'soap'); + } + + // Set service + $this->wsdl->set_service($service_name . 'Service'); + $this->wsdl->uri = $this->wsdl->namespaces[$this->tnsPrefix]; + + // Create WSDL definition + // *** <wsdl:definitions> *** + + $this->wsdl->definition = array( + 'name' => $service_name, + 'targetNamespace' => $this->wsdl->namespaces[$this->tnsPrefix], + 'xmlns' => SCHEMA_WSDL); + + foreach ($this->wsdl->namespaces as $nsPrefix => $namespace) { + $this->wsdl->definition['xmlns:' . $nsPrefix] = $namespace; + } + } + + /** + * This function is adapted from Dmitri V's implementation of + * DISCO/WSDL generation. It separates namespace from type name in + * a __typedef key and creates a new namespace entry in the WSDL + * structure if the namespace has not been used before. The + * namespace prefix and type name are returned. If no namespace is + * specified, xsd is assumed. + * + * We will not need this function anymore once __typedef is + * eliminated. + */ + function _getTypeNs($type) + { + preg_match_all('/\{(.*)\}/sm', $type, $m); + if (!empty($m[1][0])) { + if (!isset($this->wsdl->ns[$m[1][0]])) { + $ns_pref = 'ns' . count($this->wsdl->namespaces); + $this->wsdl->ns[$m[1][0]] = $ns_pref; + $this->wsdl->namespaces[$ns_pref] = $m[1][0]; + } + $typens = $this->wsdl->ns[$m[1][0]]; + $type = str_replace($m[0][0], '', $type); + } else { + $typens = 'xsd'; + } + + return array($typens, $type); + } + +} diff --git a/lib/PEAR/XML/Parser.php b/lib/PEAR/XML/Parser.php new file mode 100644 index 0000000000..ec145ba5a6 --- /dev/null +++ b/lib/PEAR/XML/Parser.php @@ -0,0 +1,768 @@ +<?php + +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * XML_Parser + * + * XML Parser package + * + * PHP versions 4 and 5 + * + * LICENSE: + * + * Copyright (c) 2002-2008 The PHP Group + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @category XML + * @package XML_Parser + * @author Stig Bakken <ssb@fast.no> + * @author Tomas V.V.Cox <cox@idecnet.com> + * @author Stephan Schmidt <schst@php.net> + * @copyright 2002-2008 The PHP Group + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version CVS: $Id: Parser.php,v 1.30 2008/09/16 16:06:22 ashnazg Exp $ + * @link http://pear.php.net/package/XML_Parser + */ + +/** + * uses PEAR's error handling + */ +require_once 'PEAR.php'; + +/** + * resource could not be created + */ +define('XML_PARSER_ERROR_NO_RESOURCE', 200); + +/** + * unsupported mode + */ +define('XML_PARSER_ERROR_UNSUPPORTED_MODE', 201); + +/** + * invalid encoding was given + */ +define('XML_PARSER_ERROR_INVALID_ENCODING', 202); + +/** + * specified file could not be read + */ +define('XML_PARSER_ERROR_FILE_NOT_READABLE', 203); + +/** + * invalid input + */ +define('XML_PARSER_ERROR_INVALID_INPUT', 204); + +/** + * remote file cannot be retrieved in safe mode + */ +define('XML_PARSER_ERROR_REMOTE', 205); + +/** + * XML Parser class. + * + * This is an XML parser based on PHP's "xml" extension, + * based on the bundled expat library. + * + * Notes: + * - It requires PHP 4.0.4pl1 or greater + * - From revision 1.17, the function names used by the 'func' mode + * are in the format "xmltag_$elem", for example: use "xmltag_name" + * to handle the <name></name> tags of your xml file. + * - different parsing modes + * + * @category XML + * @package XML_Parser + * @author Stig Bakken <ssb@fast.no> + * @author Tomas V.V.Cox <cox@idecnet.com> + * @author Stephan Schmidt <schst@php.net> + * @copyright 2002-2008 The PHP Group + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/XML_Parser + * @todo create XML_Parser_Namespace to parse documents with namespaces + * @todo create XML_Parser_Pull + * @todo Tests that need to be made: + * - mixing character encodings + * - a test using all expat handlers + * - options (folding, output charset) + */ +class XML_Parser extends PEAR +{ + // {{{ properties + + /** + * XML parser handle + * + * @var resource + * @see xml_parser_create() + */ + var $parser; + + /** + * File handle if parsing from a file + * + * @var resource + */ + var $fp; + + /** + * Whether to do case folding + * + * If set to true, all tag and attribute names will + * be converted to UPPER CASE. + * + * @var boolean + */ + var $folding = true; + + /** + * Mode of operation, one of "event" or "func" + * + * @var string + */ + var $mode; + + /** + * Mapping from expat handler function to class method. + * + * @var array + */ + var $handler = array( + 'character_data_handler' => 'cdataHandler', + 'default_handler' => 'defaultHandler', + 'processing_instruction_handler' => 'piHandler', + 'unparsed_entity_decl_handler' => 'unparsedHandler', + 'notation_decl_handler' => 'notationHandler', + 'external_entity_ref_handler' => 'entityrefHandler' + ); + + /** + * source encoding + * + * @var string + */ + var $srcenc; + + /** + * target encoding + * + * @var string + */ + var $tgtenc; + + /** + * handler object + * + * @var object + */ + var $_handlerObj; + + /** + * valid encodings + * + * @var array + */ + var $_validEncodings = array('ISO-8859-1', 'UTF-8', 'US-ASCII'); + + // }}} + // {{{ php4 constructor + + /** + * Creates an XML parser. + * + * This is needed for PHP4 compatibility, it will + * call the constructor, when a new instance is created. + * + * @param string $srcenc source charset encoding, use NULL (default) to use + * whatever the document specifies + * @param string $mode how this parser object should work, "event" for + * startelement/endelement-type events, "func" + * to have it call functions named after elements + * @param string $tgtenc a valid target encoding + */ + function XML_Parser($srcenc = null, $mode = 'event', $tgtenc = null) + { + XML_Parser::__construct($srcenc, $mode, $tgtenc); + } + // }}} + // {{{ php5 constructor + + /** + * PHP5 constructor + * + * @param string $srcenc source charset encoding, use NULL (default) to use + * whatever the document specifies + * @param string $mode how this parser object should work, "event" for + * startelement/endelement-type events, "func" + * to have it call functions named after elements + * @param string $tgtenc a valid target encoding + */ + function __construct($srcenc = null, $mode = 'event', $tgtenc = null) + { + $this->PEAR('XML_Parser_Error'); + + $this->mode = $mode; + $this->srcenc = $srcenc; + $this->tgtenc = $tgtenc; + } + // }}} + + /** + * Sets the mode of the parser. + * + * Possible modes are: + * - func + * - event + * + * You can set the mode using the second parameter + * in the constructor. + * + * This method is only needed, when switching to a new + * mode at a later point. + * + * @param string $mode mode, either 'func' or 'event' + * + * @return boolean|object true on success, PEAR_Error otherwise + * @access public + */ + function setMode($mode) + { + if ($mode != 'func' && $mode != 'event') { + $this->raiseError('Unsupported mode given', + XML_PARSER_ERROR_UNSUPPORTED_MODE); + } + + $this->mode = $mode; + return true; + } + + /** + * Sets the object, that will handle the XML events + * + * This allows you to create a handler object independent of the + * parser object that you are using and easily switch the underlying + * parser. + * + * If no object will be set, XML_Parser assumes that you + * extend this class and handle the events in $this. + * + * @param object &$obj object to handle the events + * + * @return boolean will always return true + * @access public + * @since v1.2.0beta3 + */ + function setHandlerObj(&$obj) + { + $this->_handlerObj = &$obj; + return true; + } + + /** + * Init the element handlers + * + * @return mixed + * @access private + */ + function _initHandlers() + { + if (!is_resource($this->parser)) { + return false; + } + + if (!is_object($this->_handlerObj)) { + $this->_handlerObj = &$this; + } + switch ($this->mode) { + + case 'func': + xml_set_object($this->parser, $this->_handlerObj); + xml_set_element_handler($this->parser, + array(&$this, 'funcStartHandler'), array(&$this, 'funcEndHandler')); + break; + + case 'event': + xml_set_object($this->parser, $this->_handlerObj); + xml_set_element_handler($this->parser, 'startHandler', 'endHandler'); + break; + default: + return $this->raiseError('Unsupported mode given', + XML_PARSER_ERROR_UNSUPPORTED_MODE); + break; + } + + /** + * set additional handlers for character data, entities, etc. + */ + foreach ($this->handler as $xml_func => $method) { + if (method_exists($this->_handlerObj, $method)) { + $xml_func = 'xml_set_' . $xml_func; + $xml_func($this->parser, $method); + } + } + } + + // {{{ _create() + + /** + * create the XML parser resource + * + * Has been moved from the constructor to avoid + * problems with object references. + * + * Furthermore it allows us returning an error + * if something fails. + * + * NOTE: uses '@' error suppresion in this method + * + * @return bool|PEAR_Error true on success, PEAR_Error otherwise + * @access private + * @see xml_parser_create + */ + function _create() + { + if ($this->srcenc === null) { + $xp = @xml_parser_create(); + } else { + $xp = @xml_parser_create($this->srcenc); + } + if (is_resource($xp)) { + if ($this->tgtenc !== null) { + if (!@xml_parser_set_option($xp, XML_OPTION_TARGET_ENCODING, + $this->tgtenc) + ) { + return $this->raiseError('invalid target encoding', + XML_PARSER_ERROR_INVALID_ENCODING); + } + } + $this->parser = $xp; + $result = $this->_initHandlers($this->mode); + if ($this->isError($result)) { + return $result; + } + xml_parser_set_option($xp, XML_OPTION_CASE_FOLDING, $this->folding); + return true; + } + if (!in_array(strtoupper($this->srcenc), $this->_validEncodings)) { + return $this->raiseError('invalid source encoding', + XML_PARSER_ERROR_INVALID_ENCODING); + } + return $this->raiseError('Unable to create XML parser resource.', + XML_PARSER_ERROR_NO_RESOURCE); + } + + // }}} + // {{{ reset() + + /** + * Reset the parser. + * + * This allows you to use one parser instance + * to parse multiple XML documents. + * + * @access public + * @return boolean|object true on success, PEAR_Error otherwise + */ + function reset() + { + $result = $this->_create(); + if ($this->isError($result)) { + return $result; + } + return true; + } + + // }}} + // {{{ setInputFile() + + /** + * Sets the input xml file to be parsed + * + * @param string $file Filename (full path) + * + * @return resource fopen handle of the given file + * @access public + * @throws XML_Parser_Error + * @see setInput(), setInputString(), parse() + */ + function setInputFile($file) + { + /** + * check, if file is a remote file + */ + if (eregi('^(http|ftp)://', substr($file, 0, 10))) { + if (!ini_get('allow_url_fopen')) { + return $this-> + raiseError('Remote files cannot be parsed, as safe mode is enabled.', + XML_PARSER_ERROR_REMOTE); + } + } + + $fp = @fopen($file, 'rb'); + if (is_resource($fp)) { + $this->fp = $fp; + return $fp; + } + return $this->raiseError('File could not be opened.', + XML_PARSER_ERROR_FILE_NOT_READABLE); + } + + // }}} + // {{{ setInputString() + + /** + * XML_Parser::setInputString() + * + * Sets the xml input from a string + * + * @param string $data a string containing the XML document + * + * @return null + */ + function setInputString($data) + { + $this->fp = $data; + return null; + } + + // }}} + // {{{ setInput() + + /** + * Sets the file handle to use with parse(). + * + * You should use setInputFile() or setInputString() if you + * pass a string + * + * @param mixed $fp Can be either a resource returned from fopen(), + * a URL, a local filename or a string. + * + * @return mixed + * @access public + * @see parse() + * @uses setInputString(), setInputFile() + */ + function setInput($fp) + { + if (is_resource($fp)) { + $this->fp = $fp; + return true; + } elseif (eregi('^[a-z]+://', substr($fp, 0, 10))) { + // see if it's an absolute URL (has a scheme at the beginning) + return $this->setInputFile($fp); + } elseif (file_exists($fp)) { + // see if it's a local file + return $this->setInputFile($fp); + } else { + // it must be a string + $this->fp = $fp; + return true; + } + + return $this->raiseError('Illegal input format', + XML_PARSER_ERROR_INVALID_INPUT); + } + + // }}} + // {{{ parse() + + /** + * Central parsing function. + * + * @return bool|PEAR_Error returns true on success, or a PEAR_Error otherwise + * @access public + */ + function parse() + { + /** + * reset the parser + */ + $result = $this->reset(); + if ($this->isError($result)) { + return $result; + } + // if $this->fp was fopened previously + if (is_resource($this->fp)) { + + while ($data = fread($this->fp, 4096)) { + if (!$this->_parseString($data, feof($this->fp))) { + $error = &$this->raiseError(); + $this->free(); + return $error; + } + } + } else { + // otherwise, $this->fp must be a string + if (!$this->_parseString($this->fp, true)) { + $error = &$this->raiseError(); + $this->free(); + return $error; + } + } + $this->free(); + + return true; + } + + /** + * XML_Parser::_parseString() + * + * @param string $data data + * @param bool $eof end-of-file flag + * + * @return bool + * @access private + * @see parseString() + **/ + function _parseString($data, $eof = false) + { + return xml_parse($this->parser, $data, $eof); + } + + // }}} + // {{{ parseString() + + /** + * XML_Parser::parseString() + * + * Parses a string. + * + * @param string $data XML data + * @param boolean $eof If set and TRUE, data is the last piece + * of data sent in this parser + * + * @return bool|PEAR_Error true on success or a PEAR Error + * @throws XML_Parser_Error + * @see _parseString() + */ + function parseString($data, $eof = false) + { + if (!isset($this->parser) || !is_resource($this->parser)) { + $this->reset(); + } + + if (!$this->_parseString($data, $eof)) { + $error = &$this->raiseError(); + $this->free(); + return $error; + } + + if ($eof === true) { + $this->free(); + } + return true; + } + + /** + * XML_Parser::free() + * + * Free the internal resources associated with the parser + * + * @return null + **/ + function free() + { + if (isset($this->parser) && is_resource($this->parser)) { + xml_parser_free($this->parser); + unset( $this->parser ); + } + if (isset($this->fp) && is_resource($this->fp)) { + fclose($this->fp); + } + unset($this->fp); + return null; + } + + /** + * XML_Parser::raiseError() + * + * Throws a XML_Parser_Error + * + * @param string $msg the error message + * @param integer $ecode the error message code + * + * @return XML_Parser_Error reference to the error object + **/ + function &raiseError($msg = null, $ecode = 0) + { + $msg = !is_null($msg) ? $msg : $this->parser; + $err = &new XML_Parser_Error($msg, $ecode); + return parent::raiseError($err); + } + + // }}} + // {{{ funcStartHandler() + + /** + * derives and calls the Start Handler function + * + * @param mixed $xp ?? + * @param mixed $elem ?? + * @param mixed $attribs ?? + * + * @return void + */ + function funcStartHandler($xp, $elem, $attribs) + { + $func = 'xmltag_' . $elem; + $func = str_replace(array('.', '-', ':'), '_', $func); + if (method_exists($this->_handlerObj, $func)) { + call_user_func(array(&$this->_handlerObj, $func), $xp, $elem, $attribs); + } elseif (method_exists($this->_handlerObj, 'xmltag')) { + call_user_func(array(&$this->_handlerObj, 'xmltag'), + $xp, $elem, $attribs); + } + } + + // }}} + // {{{ funcEndHandler() + + /** + * derives and calls the End Handler function + * + * @param mixed $xp ?? + * @param mixed $elem ?? + * + * @return void + */ + function funcEndHandler($xp, $elem) + { + $func = 'xmltag_' . $elem . '_'; + $func = str_replace(array('.', '-', ':'), '_', $func); + if (method_exists($this->_handlerObj, $func)) { + call_user_func(array(&$this->_handlerObj, $func), $xp, $elem); + } elseif (method_exists($this->_handlerObj, 'xmltag_')) { + call_user_func(array(&$this->_handlerObj, 'xmltag_'), $xp, $elem); + } + } + + // }}} + // {{{ startHandler() + + /** + * abstract method signature for Start Handler + * + * @param mixed $xp ?? + * @param mixed $elem ?? + * @param mixed &$attribs ?? + * + * @return null + * @abstract + */ + function startHandler($xp, $elem, &$attribs) + { + return null; + } + + // }}} + // {{{ endHandler() + + /** + * abstract method signature for End Handler + * + * @param mixed $xp ?? + * @param mixed $elem ?? + * + * @return null + * @abstract + */ + function endHandler($xp, $elem) + { + return null; + } + + + // }}}me +} + +/** + * error class, replaces PEAR_Error + * + * An instance of this class will be returned + * if an error occurs inside XML_Parser. + * + * There are three advantages over using the standard PEAR_Error: + * - All messages will be prefixed + * - check for XML_Parser error, using is_a( $error, 'XML_Parser_Error' ) + * - messages can be generated from the xml_parser resource + * + * @category XML + * @package XML_Parser + * @author Stig Bakken <ssb@fast.no> + * @author Tomas V.V.Cox <cox@idecnet.com> + * @author Stephan Schmidt <schst@php.net> + * @copyright 2002-2008 The PHP Group + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/XML_Parser + * @see PEAR_Error + */ +class XML_Parser_Error extends PEAR_Error +{ + // {{{ properties + + /** + * prefix for all messages + * + * @var string + */ + var $error_message_prefix = 'XML_Parser: '; + + // }}} + // {{{ constructor() + /** + * construct a new error instance + * + * You may either pass a message or an xml_parser resource as first + * parameter. If a resource has been passed, the last error that + * happened will be retrieved and returned. + * + * @param string|resource $msgorparser message or parser resource + * @param integer $code error code + * @param integer $mode error handling + * @param integer $level error level + * + * @access public + * @todo PEAR CS - can't meet 85char line limit without arg refactoring + */ + function XML_Parser_Error($msgorparser = 'unknown error', $code = 0, $mode = PEAR_ERROR_RETURN, $level = E_USER_NOTICE) + { + if (is_resource($msgorparser)) { + $code = xml_get_error_code($msgorparser); + $msgorparser = sprintf('%s at XML input line %d:%d', + xml_error_string($code), + xml_get_current_line_number($msgorparser), + xml_get_current_column_number($msgorparser)); + } + $this->PEAR_Error($msgorparser, $code, $mode, $level); + } + // }}} +} +?> diff --git a/lib/PEAR/XML/Parser/Simple.php b/lib/PEAR/XML/Parser/Simple.php new file mode 100644 index 0000000000..2ba9fb513a --- /dev/null +++ b/lib/PEAR/XML/Parser/Simple.php @@ -0,0 +1,326 @@ +<?php + +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * XML_Parser + * + * XML Parser's Simple parser class + * + * PHP versions 4 and 5 + * + * LICENSE: + * + * Copyright (c) 2002-2008 The PHP Group + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @category XML + * @package XML_Parser + * @author Stephan Schmidt <schst@php.net> + * @copyright 2004-2008 Stephan Schmidt <schst@php.net> + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version CVS: $Id: Simple.php,v 1.7 2008/08/24 21:48:21 ashnazg Exp $ + * @link http://pear.php.net/package/XML_Parser + */ + +/** + * built on XML_Parser + */ +require_once 'XML/Parser.php'; + +/** + * Simple XML parser class. + * + * This class is a simplified version of XML_Parser. + * In most XML applications the real action is executed, + * when a closing tag is found. + * + * XML_Parser_Simple allows you to just implement one callback + * for each tag that will receive the tag with its attributes + * and CData. + * + * <code> + * require_once '../Parser/Simple.php'; + * + * class myParser extends XML_Parser_Simple + * { + * function myParser() + * { + * $this->XML_Parser_Simple(); + * } + * + * function handleElement($name, $attribs, $data) + * { + * printf('handle %s<br>', $name); + * } + * } + * + * $p = &new myParser(); + * + * $result = $p->setInputFile('myDoc.xml'); + * $result = $p->parse(); + * </code> + * + * @category XML + * @package XML_Parser + * @author Stephan Schmidt <schst@php.net> + * @copyright 2004-2008 The PHP Group + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/XML_Parser + */ +class XML_Parser_Simple extends XML_Parser +{ + /** + * element stack + * + * @access private + * @var array + */ + var $_elStack = array(); + + /** + * all character data + * + * @access private + * @var array + */ + var $_data = array(); + + /** + * element depth + * + * @access private + * @var integer + */ + var $_depth = 0; + + /** + * Mapping from expat handler function to class method. + * + * @var array + */ + var $handler = array( + 'default_handler' => 'defaultHandler', + 'processing_instruction_handler' => 'piHandler', + 'unparsed_entity_decl_handler' => 'unparsedHandler', + 'notation_decl_handler' => 'notationHandler', + 'external_entity_ref_handler' => 'entityrefHandler' + ); + + /** + * Creates an XML parser. + * + * This is needed for PHP4 compatibility, it will + * call the constructor, when a new instance is created. + * + * @param string $srcenc source charset encoding, use NULL (default) to use + * whatever the document specifies + * @param string $mode how this parser object should work, "event" for + * handleElement(), "func" to have it call functions + * named after elements (handleElement_$name()) + * @param string $tgtenc a valid target encoding + */ + function XML_Parser_Simple($srcenc = null, $mode = 'event', $tgtenc = null) + { + $this->XML_Parser($srcenc, $mode, $tgtenc); + } + + /** + * inits the handlers + * + * @return mixed + * @access private + */ + function _initHandlers() + { + if (!is_object($this->_handlerObj)) { + $this->_handlerObj = &$this; + } + + if ($this->mode != 'func' && $this->mode != 'event') { + return $this->raiseError('Unsupported mode given', + XML_PARSER_ERROR_UNSUPPORTED_MODE); + } + xml_set_object($this->parser, $this->_handlerObj); + + xml_set_element_handler($this->parser, array(&$this, 'startHandler'), + array(&$this, 'endHandler')); + xml_set_character_data_handler($this->parser, array(&$this, 'cdataHandler')); + + /** + * set additional handlers for character data, entities, etc. + */ + foreach ($this->handler as $xml_func => $method) { + if (method_exists($this->_handlerObj, $method)) { + $xml_func = 'xml_set_' . $xml_func; + $xml_func($this->parser, $method); + } + } + } + + /** + * Reset the parser. + * + * This allows you to use one parser instance + * to parse multiple XML documents. + * + * @access public + * @return boolean|object true on success, PEAR_Error otherwise + */ + function reset() + { + $this->_elStack = array(); + $this->_data = array(); + $this->_depth = 0; + + $result = $this->_create(); + if ($this->isError($result)) { + return $result; + } + return true; + } + + /** + * start handler + * + * Pushes attributes and tagname onto a stack + * + * @param resource $xp xml parser resource + * @param string $elem element name + * @param array &$attribs attributes + * + * @return mixed + * @access private + * @final + */ + function startHandler($xp, $elem, &$attribs) + { + array_push($this->_elStack, array( + 'name' => $elem, + 'attribs' => $attribs + )); + $this->_depth++; + $this->_data[$this->_depth] = ''; + } + + /** + * end handler + * + * Pulls attributes and tagname from a stack + * + * @param resource $xp xml parser resource + * @param string $elem element name + * + * @return mixed + * @access private + * @final + */ + function endHandler($xp, $elem) + { + $el = array_pop($this->_elStack); + $data = $this->_data[$this->_depth]; + $this->_depth--; + + switch ($this->mode) { + case 'event': + $this->_handlerObj->handleElement($el['name'], $el['attribs'], $data); + break; + case 'func': + $func = 'handleElement_' . $elem; + if (strchr($func, '.')) { + $func = str_replace('.', '_', $func); + } + if (method_exists($this->_handlerObj, $func)) { + call_user_func(array(&$this->_handlerObj, $func), + $el['name'], $el['attribs'], $data); + } + break; + } + } + + /** + * handle character data + * + * @param resource $xp xml parser resource + * @param string $data data + * + * @return void + * @access private + * @final + */ + function cdataHandler($xp, $data) + { + $this->_data[$this->_depth] .= $data; + } + + /** + * handle a tag + * + * Implement this in your parser + * + * @param string $name element name + * @param array $attribs attributes + * @param string $data character data + * + * @return void + * @access public + * @abstract + */ + function handleElement($name, $attribs, $data) + { + } + + /** + * get the current tag depth + * + * The root tag is in depth 0. + * + * @access public + * @return integer + */ + function getCurrentDepth() + { + return $this->_depth; + } + + /** + * add some string to the current ddata. + * + * This is commonly needed, when a document is parsed recursively. + * + * @param string $data data to add + * + * @return void + * @access public + */ + function addToData($data) + { + $this->_data[$this->_depth] .= $data; + } +} +?> diff --git a/lib/PEAR/XML/Serializer.php b/lib/PEAR/XML/Serializer.php new file mode 100644 index 0000000000..b9f2f0880c --- /dev/null +++ b/lib/PEAR/XML/Serializer.php @@ -0,0 +1,1222 @@ +<?PHP +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * XML_Serializer + * + * Creates XML documents from PHP data structures like arrays, objects or scalars. + * + * PHP versions 4 and 5 + * + * LICENSE: + * + * Copyright (c) 2003-2008 Stephan Schmidt <schst@php.net> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @category XML + * @package XML_Serializer + * @author Stephan Schmidt <schst@php.net> + * @copyright 2003-2008 Stephan Schmidt <schst@php.net> + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version CVS: $Id: Serializer.php,v 1.57 2009/01/25 03:51:11 ashnazg Exp $ + * @link http://pear.php.net/package/XML_Serializer + * @see XML_Unserializer + */ + +/** + * uses PEAR error management + */ +require_once 'PEAR.php'; + +/** + * uses XML_Util to create XML tags + */ +require_once 'XML/Util.php'; + +/** + * option: string used for indentation + * + * Possible values: + * - any string (default is any string) + */ +define('XML_SERIALIZER_OPTION_INDENT', 'indent'); + +/** + * option: string used for linebreaks + * + * Possible values: + * - any string (default is \n) + */ +define('XML_SERIALIZER_OPTION_LINEBREAKS', 'linebreak'); + +/** + * option: enable type hints + * + * Possible values: + * - true + * - false + */ +define('XML_SERIALIZER_OPTION_TYPEHINTS', 'typeHints'); + +/** + * option: add an XML declaration + * + * Possible values: + * - true + * - false + */ +define('XML_SERIALIZER_OPTION_XML_DECL_ENABLED', 'addDecl'); + +/** + * option: encoding of the document + * + * Possible values: + * - any valid encoding + * - null (default) + */ +define('XML_SERIALIZER_OPTION_XML_ENCODING', 'encoding'); + +/** + * option: default name for tags + * + * Possible values: + * - any string (XML_Serializer_Tag is default) + */ +define('XML_SERIALIZER_OPTION_DEFAULT_TAG', 'defaultTagName'); + +/** + * option: use classname for objects in indexed arrays + * + * Possible values: + * - true + * - false (default) + */ +define('XML_SERIALIZER_OPTION_CLASSNAME_AS_TAGNAME', 'classAsTagName'); + +/** + * option: attribute where original key is stored + * + * Possible values: + * - any string (default is _originalKey) + */ +define('XML_SERIALIZER_OPTION_ATTRIBUTE_KEY', 'keyAttribute'); + +/** + * option: attribute for type (only if typeHints => true) + * + * Possible values: + * - any string (default is _type) + */ +define('XML_SERIALIZER_OPTION_ATTRIBUTE_TYPE', 'typeAttribute'); + +/** + * option: attribute for class (only if typeHints => true) + * + * Possible values: + * - any string (default is _class) + */ +define('XML_SERIALIZER_OPTION_ATTRIBUTE_CLASS', 'classAttribute'); + +/** + * option: scalar values (strings, ints,..) will be serialized as attribute + * + * Possible values: + * - true + * - false (default) + * - array which sets this option on a per-tag basis + */ +define('XML_SERIALIZER_OPTION_SCALAR_AS_ATTRIBUTES', 'scalarAsAttributes'); + +/** + * option: prepend string for attributes + * + * Possible values: + * - any string (default is any string) + */ +define('XML_SERIALIZER_OPTION_PREPEND_ATTRIBUTES', 'prependAttributes'); + +/** + * option: indent the attributes, if set to '_auto', + * it will indent attributes so they all start at the same column + * + * Possible values: + * - true + * - false (default) + * - '_auto' + */ +define('XML_SERIALIZER_OPTION_INDENT_ATTRIBUTES', 'indentAttributes'); + +/** + * option: use 'simplexml' to use parent name as tagname + * if transforming an indexed array + * + * Possible values: + * - XML_SERIALIZER_MODE_DEFAULT (default) + * - XML_SERIALIZER_MODE_SIMPLEXML + */ +define('XML_SERIALIZER_OPTION_MODE', 'mode'); + +/** + * option: add a doctype declaration + * + * Possible values: + * - true + * - false (default) + */ +define('XML_SERIALIZER_OPTION_DOCTYPE_ENABLED', 'addDoctype'); + +/** + * option: supply a string or an array with id and uri + * ({@see XML_Util::getDoctypeDeclaration()} + * + * Possible values: + * - string + * - array + */ +define('XML_SERIALIZER_OPTION_DOCTYPE', 'doctype'); + +/** + * option: name of the root tag + * + * Possible values: + * - string + * - null (default) + */ +define('XML_SERIALIZER_OPTION_ROOT_NAME', 'rootName'); + +/** + * option: attributes of the root tag + * + * Possible values: + * - array + */ +define('XML_SERIALIZER_OPTION_ROOT_ATTRIBS', 'rootAttributes'); + +/** + * option: all values in this key will be treated as attributes + * + * Possible values: + * - string + */ +define('XML_SERIALIZER_OPTION_ATTRIBUTES_KEY', 'attributesArray'); + +/** + * option: this value will be used directly as content, + * instead of creating a new tag, may only be used + * in conjuction with attributesArray + * + * Possible values: + * - string + * - null (default) + */ +define('XML_SERIALIZER_OPTION_CONTENT_KEY', 'contentName'); + +/** + * option: this value will be used in a comment, instead of creating a new tag + * + * Possible values: + * - string + * - null (default) + */ +define('XML_SERIALIZER_OPTION_COMMENT_KEY', 'commentName'); + +/** + * option: tag names that will be changed + * + * Possible values: + * - array + */ +define('XML_SERIALIZER_OPTION_TAGMAP', 'tagMap'); + +/** + * option: function that will be applied before serializing + * + * Possible values: + * - any valid PHP callback + */ +define('XML_SERIALIZER_OPTION_ENCODE_FUNC', 'encodeFunction'); + +/** + * option: namespace to use for the document + * + * Possible values: + * - string + * - null (default) + */ +define('XML_SERIALIZER_OPTION_NAMESPACE', 'namespace'); + +/** + * option: type of entities to replace + * + * Possible values: + * - XML_SERIALIZER_ENTITIES_NONE + * - XML_SERIALIZER_ENTITIES_XML (default) + * - XML_SERIALIZER_ENTITIES_XML_REQUIRED + * - XML_SERIALIZER_ENTITIES_HTML + */ +define('XML_SERIALIZER_OPTION_ENTITIES', 'replaceEntities'); + +/** + * option: whether to return the result of the serialization from serialize() + * + * Possible values: + * - true + * - false (default) + */ +define('XML_SERIALIZER_OPTION_RETURN_RESULT', 'returnResult'); + +/** + * option: whether to ignore properties that are set to null + * + * Possible values: + * - true + * - false (default) + */ +define('XML_SERIALIZER_OPTION_IGNORE_NULL', 'ignoreNull'); + +/** + * option: whether to use cdata sections for character data + * + * Possible values: + * - true + * - false (default) + */ +define('XML_SERIALIZER_OPTION_CDATA_SECTIONS', 'cdata'); + + +/** + * default mode + */ +define('XML_SERIALIZER_MODE_DEFAULT', 'default'); + +/** + * SimpleXML mode + * + * When serializing indexed arrays, the key of the parent value is used as a tagname. + */ +define('XML_SERIALIZER_MODE_SIMPLEXML', 'simplexml'); + +/** + * error code for no serialization done + */ +define('XML_SERIALIZER_ERROR_NO_SERIALIZATION', 51); + +/** + * do not replace entitites + */ +define('XML_SERIALIZER_ENTITIES_NONE', XML_UTIL_ENTITIES_NONE); + +/** + * replace all XML entitites + * This setting will replace <, >, ", ' and & + */ +define('XML_SERIALIZER_ENTITIES_XML', XML_UTIL_ENTITIES_XML); + +/** + * replace only required XML entitites + * This setting will replace <, " and & + */ +define('XML_SERIALIZER_ENTITIES_XML_REQUIRED', XML_UTIL_ENTITIES_XML_REQUIRED); + +/** + * replace HTML entitites + * @link http://www.php.net/htmlentities + */ +define('XML_SERIALIZER_ENTITIES_HTML', XML_UTIL_ENTITIES_HTML); + +/** + * Creates XML documents from PHP data structures like arrays, objects or scalars. + * + * this class can be used in two modes: + * + * 1. create an XML document from an array or object that is processed by other + * applications. That means, you can create a RDF document from an array in the + * following format: + * + * $data = array( + * 'channel' => array( + * 'title' => 'Example RDF channel', + * 'link' => 'http://www.php-tools.de', + * 'image' => array( + * 'title' => 'Example image', + * 'url' => 'http://www.php-tools.de/image.gif', + * 'link' => 'http://www.php-tools.de' + * ), + * array( + * 'title' => 'Example item', + * 'link' => 'http://example.com' + * ), + * array( + * 'title' => 'Another Example item', + * 'link' => 'http://example.org' + * ) + * ) + * ); + * + * to create a RDF document from this array do the following: + * + * require_once 'XML/Serializer.php'; + * + * $options = array( + * XML_SERIALIZER_OPTION_INDENT => "\t", // indent with tabs + * XML_SERIALIZER_OPTION_LINEBREAKS => "\n", // use UNIX line breaks + * XML_SERIALIZER_OPTION_ROOT_NAME => 'rdf:RDF',// root tag + * XML_SERIALIZER_OPTION_DEFAULT_TAG => 'item' // tag for values + * // with numeric keys + * ); + * + * $serializer = new XML_Serializer($options); + * $rdf = $serializer->serialize($data); + * + * You will get a complete XML document that can be processed like any RDF document. + * + * 2. this classes can be used to serialize any data structure in a way that it can + * later be unserialized again. + * XML_Serializer will store the type of the value and additional meta information + * in attributes of the surrounding tag. This meat information can later be used + * to restore the original data structure in PHP. If you want XML_Serializer + * to add meta information to the tags, add + * + * XML_SERIALIZER_OPTION_TYPEHINTS => true + * + * to the options array in the constructor. + * + * @category XML + * @package XML_Serializer + * @author Stephan Schmidt <schst@php.net> + * @copyright 2003-2008 Stephan Schmidt <schst@php.net> + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version Release: 0.19.2 + * @link http://pear.php.net/package/XML_Serializer + * @see XML_Unserializer + */ +class XML_Serializer extends PEAR +{ + /** + * list of all available options + * + * @access private + * @var array + */ + var $_knownOptions = array( + XML_SERIALIZER_OPTION_INDENT, + XML_SERIALIZER_OPTION_LINEBREAKS, + XML_SERIALIZER_OPTION_TYPEHINTS, + XML_SERIALIZER_OPTION_XML_DECL_ENABLED, + XML_SERIALIZER_OPTION_XML_ENCODING, + XML_SERIALIZER_OPTION_DEFAULT_TAG, + XML_SERIALIZER_OPTION_CLASSNAME_AS_TAGNAME, + XML_SERIALIZER_OPTION_ATTRIBUTE_KEY, + XML_SERIALIZER_OPTION_ATTRIBUTE_TYPE, + XML_SERIALIZER_OPTION_ATTRIBUTE_CLASS, + XML_SERIALIZER_OPTION_SCALAR_AS_ATTRIBUTES, + XML_SERIALIZER_OPTION_PREPEND_ATTRIBUTES, + XML_SERIALIZER_OPTION_INDENT_ATTRIBUTES, + XML_SERIALIZER_OPTION_MODE, + XML_SERIALIZER_OPTION_DOCTYPE_ENABLED, + XML_SERIALIZER_OPTION_DOCTYPE, + XML_SERIALIZER_OPTION_ROOT_NAME, + XML_SERIALIZER_OPTION_ROOT_ATTRIBS, + XML_SERIALIZER_OPTION_ATTRIBUTES_KEY, + XML_SERIALIZER_OPTION_CONTENT_KEY, + XML_SERIALIZER_OPTION_COMMENT_KEY, + XML_SERIALIZER_OPTION_TAGMAP, + XML_SERIALIZER_OPTION_ENCODE_FUNC, + XML_SERIALIZER_OPTION_NAMESPACE, + XML_SERIALIZER_OPTION_ENTITIES, + XML_SERIALIZER_OPTION_RETURN_RESULT, + XML_SERIALIZER_OPTION_IGNORE_NULL, + XML_SERIALIZER_OPTION_CDATA_SECTIONS + ); + + /** + * default options for the serialization + * + * @access private + * @var array + */ + var $_defaultOptions = array( + + // string used for indentation + XML_SERIALIZER_OPTION_INDENT => '', + + // string used for newlines + XML_SERIALIZER_OPTION_LINEBREAKS => "\n", + + // automatically add type hin attributes + XML_SERIALIZER_OPTION_TYPEHINTS => false, + + // add an XML declaration + XML_SERIALIZER_OPTION_XML_DECL_ENABLED => false, + + // encoding specified in the XML declaration + XML_SERIALIZER_OPTION_XML_ENCODING => null, + + // tag used for indexed arrays or invalid names + XML_SERIALIZER_OPTION_DEFAULT_TAG => 'XML_Serializer_Tag', + + // use classname for objects in indexed arrays + XML_SERIALIZER_OPTION_CLASSNAME_AS_TAGNAME => false, + + // attribute where original key is stored + XML_SERIALIZER_OPTION_ATTRIBUTE_KEY => '_originalKey', + + // attribute for type (only if typeHints => true) + XML_SERIALIZER_OPTION_ATTRIBUTE_TYPE => '_type', + + // attribute for class of objects (only if typeHints => true) + XML_SERIALIZER_OPTION_ATTRIBUTE_CLASS => '_class', + + // scalar values (strings, ints,..) will be serialized as attribute + XML_SERIALIZER_OPTION_SCALAR_AS_ATTRIBUTES => false, + + // prepend string for attributes + XML_SERIALIZER_OPTION_PREPEND_ATTRIBUTES => '', + + // indent the attributes, if set to '_auto', + // it will indent attributes so they all start at the same column + XML_SERIALIZER_OPTION_INDENT_ATTRIBUTES => false, + + // use XML_SERIALIZER_MODE_SIMPLEXML to use parent name as tagname + // if transforming an indexed array + XML_SERIALIZER_OPTION_MODE => XML_SERIALIZER_MODE_DEFAULT, + + // add a doctype declaration + XML_SERIALIZER_OPTION_DOCTYPE_ENABLED => false, + + // supply a string or an array with id and uri + // ({@see XML_Util::getDoctypeDeclaration()} + XML_SERIALIZER_OPTION_DOCTYPE => null, + + // name of the root tag + XML_SERIALIZER_OPTION_ROOT_NAME => null, + + // attributes of the root tag + XML_SERIALIZER_OPTION_ROOT_ATTRIBS => array(), + + // all values in this key will be treated as attributes + XML_SERIALIZER_OPTION_ATTRIBUTES_KEY => null, + + // this value will be used directly as content, + // instead of creating a new tag, may only be used + // in conjuction with attributesArray + XML_SERIALIZER_OPTION_CONTENT_KEY => null, + + // this value will be used directly as comment, + // instead of creating a new tag, may only be used + // in conjuction with attributesArray + XML_SERIALIZER_OPTION_COMMENT_KEY => null, + + // tag names that will be changed + XML_SERIALIZER_OPTION_TAGMAP => array(), + + // function that will be applied before serializing + XML_SERIALIZER_OPTION_ENCODE_FUNC => null, + + // namespace to use + XML_SERIALIZER_OPTION_NAMESPACE => null, + + // type of entities to replace, + XML_SERIALIZER_OPTION_ENTITIES => XML_SERIALIZER_ENTITIES_XML, + + // serialize() returns the result of the serialization instead of true + XML_SERIALIZER_OPTION_RETURN_RESULT => false, + + // ignore properties that are set to null + XML_SERIALIZER_OPTION_IGNORE_NULL => false, + + // Whether to use cdata sections for plain character data + XML_SERIALIZER_OPTION_CDATA_SECTIONS => false + ); + + /** + * options for the serialization + * + * @access public + * @var array + */ + var $options = array(); + + /** + * current tag depth + * + * @access private + * @var integer + */ + var $_tagDepth = 0; + + /** + * serilialized representation of the data + * + * @access private + * @var string + */ + var $_serializedData = null; + + /** + * constructor + * + * @param mixed $options array containing options for the serialization + * + * @return void + * @access public + */ + function XML_Serializer( $options = null ) + { + $this->PEAR(); + if (is_array($options)) { + $this->options = array_merge($this->_defaultOptions, $options); + } else { + $this->options = $this->_defaultOptions; + } + } + + /** + * return API version + * + * @access public + * @static + * @return string $version API version + */ + function apiVersion() + { + return '0.19.2'; + } + + /** + * reset all options to default options + * + * @return void + * @access public + * @see setOption(), XML_Serializer() + */ + function resetOptions() + { + $this->options = $this->_defaultOptions; + } + + /** + * set an option + * + * You can use this method if you do not want + * to set all options in the constructor + * + * @param string $name option name + * @param mixed $value option value + * + * @return void + * @access public + * @see resetOption(), XML_Serializer() + */ + function setOption($name, $value) + { + $this->options[$name] = $value; + } + + /** + * sets several options at once + * + * You can use this method if you do not want + * to set all options in the constructor + * + * @param array $options options array + * + * @return void + * @access public + * @see resetOption(), XML_Unserializer(), setOption() + */ + function setOptions($options) + { + $this->options = array_merge($this->options, $options); + } + + /** + * serialize data + * + * @param mixed $data data to serialize + * @param array $options options array + * + * @return boolean true on success, pear error on failure + * @access public + */ + function serialize($data, $options = null) + { + // if options have been specified, use them instead + // of the previously defined ones + if (is_array($options)) { + $optionsBak = $this->options; + if (isset($options['overrideOptions']) + && $options['overrideOptions'] == true + ) { + $this->options = array_merge($this->_defaultOptions, $options); + } else { + $this->options = array_merge($this->options, $options); + } + } else { + $optionsBak = null; + } + + // start depth is zero + $this->_tagDepth = 0; + + $rootAttributes = $this->options[XML_SERIALIZER_OPTION_ROOT_ATTRIBS]; + if (isset($this->options[XML_SERIALIZER_OPTION_NAMESPACE]) + && is_array($this->options[XML_SERIALIZER_OPTION_NAMESPACE]) + ) { + $rootAttributes['xmlns:' + . $this->options[XML_SERIALIZER_OPTION_NAMESPACE][0]] = + $this->options[XML_SERIALIZER_OPTION_NAMESPACE][1]; + } + + $this->_serializedData = ''; + // serialize an array + if (is_array($data)) { + if (isset($this->options[XML_SERIALIZER_OPTION_ROOT_NAME])) { + $tagName = $this->options[XML_SERIALIZER_OPTION_ROOT_NAME]; + } else { + $tagName = 'array'; + } + + $this->_serializedData .= + $this->_serializeArray($data, $tagName, $rootAttributes); + } elseif (is_object($data)) { + // serialize an object + if (isset($this->options[XML_SERIALIZER_OPTION_ROOT_NAME])) { + $tagName = $this->options[XML_SERIALIZER_OPTION_ROOT_NAME]; + } else { + $tagName = get_class($data); + } + $this->_serializedData .= + $this->_serializeObject($data, $tagName, $rootAttributes); + } else { + $tag = array(); + if (isset($this->options[XML_SERIALIZER_OPTION_ROOT_NAME])) { + $tag['qname'] = $this->options[XML_SERIALIZER_OPTION_ROOT_NAME]; + } else { + $tag['qname'] = gettype($data); + } + $tagName = $tag['qname']; + if ($this->options[XML_SERIALIZER_OPTION_TYPEHINTS] === true) { + $rootAttributes[$this-> + options[XML_SERIALIZER_OPTION_ATTRIBUTE_TYPE]] = gettype($data); + } + @settype($data, 'string'); + $tag['content'] = $data; + $tag['attributes'] = $rootAttributes; + $this->_serializedData = $this->_createXMLTag($tag); + } + + // add doctype declaration + if ($this->options[XML_SERIALIZER_OPTION_DOCTYPE_ENABLED] === true) { + $this->_serializedData = + XML_Util::getDoctypeDeclaration($tagName, + $this->options[XML_SERIALIZER_OPTION_DOCTYPE]) + . $this->options[XML_SERIALIZER_OPTION_LINEBREAKS] + . $this->_serializedData; + } + + // build xml declaration + if ($this->options[XML_SERIALIZER_OPTION_XML_DECL_ENABLED]) { + $atts = array(); + $this->_serializedData = XML_Util::getXMLDeclaration('1.0', + $this->options[XML_SERIALIZER_OPTION_XML_ENCODING]) + . $this->options[XML_SERIALIZER_OPTION_LINEBREAKS] + . $this->_serializedData; + } + + if ($this->options[XML_SERIALIZER_OPTION_RETURN_RESULT] === true) { + $result = $this->_serializedData; + } else { + $result = true; + } + + if ($optionsBak !== null) { + $this->options = $optionsBak; + } + + return $result; + } + + /** + * get the result of the serialization + * + * @access public + * @return string serialized XML + */ + function getSerializedData() + { + if ($this->_serializedData == null) { + return $this->raiseError('No serialized data available. ' + . 'Use XML_Serializer::serialize() first.', + XML_SERIALIZER_ERROR_NO_SERIALIZATION); + } + return $this->_serializedData; + } + + /** + * serialize any value + * + * This method checks for the type of the value and calls the appropriate method + * + * @param mixed $value tag value + * @param string $tagName tag name + * @param array $attributes attributes + * + * @return string + * @access private + */ + function _serializeValue($value, $tagName = null, $attributes = array()) + { + if (is_array($value)) { + $xml = $this->_serializeArray($value, $tagName, $attributes); + } elseif (is_object($value)) { + $xml = $this->_serializeObject($value, $tagName); + } else { + $tag = array( + 'qname' => $tagName, + 'attributes' => $attributes, + 'content' => $value + ); + $xml = $this->_createXMLTag($tag); + } + return $xml; + } + + /** + * serialize an array + * + * @param array &$array array to serialize + * @param string $tagName name of the root tag + * @param array $attributes attributes for the root tag + * + * @return string $string serialized data + * @access private + * @uses XML_Util::isValidName() to check, whether key has to be substituted + */ + function _serializeArray(&$array, $tagName = null, $attributes = array()) + { + $_content = null; + $_comment = null; + + // check for comment + if ($this->options[XML_SERIALIZER_OPTION_COMMENT_KEY] !== null) { + if (isset($array[$this->options[XML_SERIALIZER_OPTION_COMMENT_KEY]]) + ) { + $_comment = + $array[$this->options[XML_SERIALIZER_OPTION_COMMENT_KEY]]; + unset($array[$this->options[XML_SERIALIZER_OPTION_COMMENT_KEY]]); + } + } + + /** + * check for special attributes + */ + if ($this->options[XML_SERIALIZER_OPTION_ATTRIBUTES_KEY] !== null) { + if (isset($array[$this->options[XML_SERIALIZER_OPTION_ATTRIBUTES_KEY]]) + ) { + $attributes = + $array[$this->options[XML_SERIALIZER_OPTION_ATTRIBUTES_KEY]]; + unset($array[$this->options[XML_SERIALIZER_OPTION_ATTRIBUTES_KEY]]); + } + /** + * check for special content + */ + if ($this->options[XML_SERIALIZER_OPTION_CONTENT_KEY] !== null) { + if (isset($array[$this->options[XML_SERIALIZER_OPTION_CONTENT_KEY]]) + ) { + $_content = + XML_Util::replaceEntities($array + [$this->options[XML_SERIALIZER_OPTION_CONTENT_KEY]]); + unset($array[$this->options[XML_SERIALIZER_OPTION_CONTENT_KEY]]); + } + } + } + + if ($this->options[XML_SERIALIZER_OPTION_IGNORE_NULL] === true) { + foreach (array_keys($array) as $key) { + if (is_null($array[$key])) { + unset($array[$key]); + } + } + } + + /* + * if mode is set to simpleXML, check whether + * the array is associative or indexed + */ + if (is_array($array) && !empty($array) + && $this->options[XML_SERIALIZER_OPTION_MODE] + == XML_SERIALIZER_MODE_SIMPLEXML + ) { + $indexed = true; + foreach ($array as $key => $val) { + if (!is_int($key)) { + $indexed = false; + break; + } + } + + if ($indexed + && $this->options[XML_SERIALIZER_OPTION_MODE] + == XML_SERIALIZER_MODE_SIMPLEXML + ) { + $string = ''; + foreach ($array as $key => $val) { + $string .= $this->_serializeValue($val, $tagName, $attributes); + + $string .= $this->options[XML_SERIALIZER_OPTION_LINEBREAKS]; + // do indentation + if ($this->options[XML_SERIALIZER_OPTION_INDENT]!==null + && $this->_tagDepth>0 + ) { + $string .= + str_repeat($this->options[XML_SERIALIZER_OPTION_INDENT], + $this->_tagDepth); + } + } + return rtrim($string); + } + } + + $scalarAsAttributes = false; + if (is_array($this->options[XML_SERIALIZER_OPTION_SCALAR_AS_ATTRIBUTES]) + && isset($this->options[XML_SERIALIZER_OPTION_SCALAR_AS_ATTRIBUTES] + [$tagName]) + ) { + $scalarAsAttributes = + $this->options[XML_SERIALIZER_OPTION_SCALAR_AS_ATTRIBUTES][$tagName]; + } elseif ($this->options[XML_SERIALIZER_OPTION_SCALAR_AS_ATTRIBUTES] === true + ) { + $scalarAsAttributes = true; + } + + if ($scalarAsAttributes === true) { + $this->expectError('*'); + foreach ($array as $key => $value) { + if (is_scalar($value) && (XML_Util::isValidName($key) === true)) { + unset($array[$key]); + $attributes[$this->options + [XML_SERIALIZER_OPTION_PREPEND_ATTRIBUTES].$key] = $value; + } + } + $this->popExpect(); + } elseif (is_array($scalarAsAttributes)) { + $this->expectError('*'); + foreach ($scalarAsAttributes as $key) { + if (!isset($array[$key])) { + continue; + } + $value = $array[$key]; + if (is_scalar($value) && (XML_Util::isValidName($key) === true)) { + unset($array[$key]); + $attributes[$this->options + [XML_SERIALIZER_OPTION_PREPEND_ATTRIBUTES].$key] = $value; + } + } + $this->popExpect(); + } + + // check for empty array => create empty tag + if (empty($array)) { + $tag = array( + 'qname' => $tagName, + 'content' => $_content, + 'attributes' => $attributes + ); + } else { + $this->_tagDepth++; + $tmp = $_content . $this->options[XML_SERIALIZER_OPTION_LINEBREAKS]; + foreach ($array as $key => $value) { + // do indentation + if ($this->options[XML_SERIALIZER_OPTION_INDENT]!==null + && $this->_tagDepth>0 + ) { + $tmp .= str_repeat($this->options[XML_SERIALIZER_OPTION_INDENT], + $this->_tagDepth); + } + + // copy key + $origKey = $key; + $this->expectError('*'); + // key cannot be used as tagname => use default tag + $valid = XML_Util::isValidName($key); + $this->popExpect(); + if (PEAR::isError($valid)) { + if ($this->options[XML_SERIALIZER_OPTION_CLASSNAME_AS_TAGNAME] + && is_object($value) + ) { + $key = get_class($value); + } else { + $key = $this->_getDefaultTagname($tagName); + } + } + + // once we've established the true $key, is there a tagmap for it? + if (isset($this->options[XML_SERIALIZER_OPTION_TAGMAP][$key])) { + $key = $this->options[XML_SERIALIZER_OPTION_TAGMAP][$key]; + } + + $atts = array(); + if ($this->options[XML_SERIALIZER_OPTION_TYPEHINTS] === true) { + $atts[$this->options[XML_SERIALIZER_OPTION_ATTRIBUTE_TYPE]] = + gettype($value); + if ($key !== $origKey) { + $atts[$this->options[XML_SERIALIZER_OPTION_ATTRIBUTE_KEY]] = + (string)$origKey; + } + } + + $tmp .= $this->_createXMLTag(array( + 'qname' => $key, + 'attributes' => $atts, + 'content' => $value + )); + $tmp .= $this->options[XML_SERIALIZER_OPTION_LINEBREAKS]; + } + + $this->_tagDepth--; + if ($this->options[XML_SERIALIZER_OPTION_INDENT]!==null + && $this->_tagDepth>0 + ) { + $tmp .= str_repeat($this->options[XML_SERIALIZER_OPTION_INDENT], + $this->_tagDepth); + } + + if (trim($tmp) === '') { + $tmp = null; + } + + $tag = array( + 'qname' => $tagName, + 'content' => $tmp, + 'attributes' => $attributes + ); + } + if ($this->options[XML_SERIALIZER_OPTION_TYPEHINTS] === true) { + if (!isset($tag['attributes'] + [$this->options[XML_SERIALIZER_OPTION_ATTRIBUTE_TYPE]]) + ) { + $tag['attributes'] + [$this->options[XML_SERIALIZER_OPTION_ATTRIBUTE_TYPE]] = 'array'; + } + } + + $string = ''; + if (!is_null($_comment)) { + $string .= XML_Util::createComment($_comment); + $string .= $this->options[XML_SERIALIZER_OPTION_LINEBREAKS]; + if ($this->options[XML_SERIALIZER_OPTION_INDENT]!==null + && $this->_tagDepth>0 + ) { + $string .= str_repeat($this->options[XML_SERIALIZER_OPTION_INDENT], + $this->_tagDepth); + } + } + $string .= $this->_createXMLTag($tag, false); + return $string; + } + + /** + * get the name of the default tag. + * + * The name of the parent tag needs to be passed as the + * default name can depend on the context. + * + * @param string $parent name of the parent tag + * + * @return string default tag name + */ + function _getDefaultTagname($parent) + { + if (is_string($this->options[XML_SERIALIZER_OPTION_DEFAULT_TAG])) { + return $this->options[XML_SERIALIZER_OPTION_DEFAULT_TAG]; + } + if (isset($this->options[XML_SERIALIZER_OPTION_DEFAULT_TAG][$parent])) { + return $this->options[XML_SERIALIZER_OPTION_DEFAULT_TAG][$parent]; + } elseif (isset($this->options[XML_SERIALIZER_OPTION_DEFAULT_TAG] + ['#default']) + ) { + return $this->options[XML_SERIALIZER_OPTION_DEFAULT_TAG]['#default']; + } elseif (isset($this->options[XML_SERIALIZER_OPTION_DEFAULT_TAG] + ['__default']) + ) { + // keep this for BC + return $this->options[XML_SERIALIZER_OPTION_DEFAULT_TAG]['__default']; + } + return 'XML_Serializer_Tag'; + } + + /** + * serialize an object + * + * @param object &$object object to serialize + * @param string $tagName tag name + * @param array $attributes attributes + * + * @return string $string serialized data + * @access private + */ + function _serializeObject(&$object, $tagName = null, $attributes = array()) + { + // check for magic function + if (method_exists($object, '__sleep')) { + $propNames = $object->__sleep(); + if (is_array($propNames)) { + $properties = array(); + foreach ($propNames as $propName) { + $properties[$propName] = $object->$propName; + } + } else { + $properties = get_object_vars($object); + } + } else { + $properties = get_object_vars($object); + } + + if (empty($tagName)) { + $tagName = get_class($object); + } + + // typehints activated? + if ($this->options[XML_SERIALIZER_OPTION_TYPEHINTS] === true) { + $attributes[$this->options[XML_SERIALIZER_OPTION_ATTRIBUTE_TYPE]] = + 'object'; + $attributes[$this->options[XML_SERIALIZER_OPTION_ATTRIBUTE_CLASS]] = + get_class($object); + } + $string = $this->_serializeArray($properties, $tagName, $attributes); + return $string; + } + + /** + * create a tag from an array + * this method awaits an array in the following format + * array( + * 'qname' => $tagName, + * 'attributes' => array(), + * 'content' => $content, // optional + * 'namespace' => $namespace // optional + * 'namespaceUri' => $namespaceUri // optional + * ) + * + * @param array $tag tag definition + * @param boolean $firstCall whether or not this is the first call + * + * @return string $string XML tag + * @access private + */ + function _createXMLTag($tag, $firstCall = true) + { + // build fully qualified tag name + if ($this->options[XML_SERIALIZER_OPTION_NAMESPACE] !== null) { + if (is_array($this->options[XML_SERIALIZER_OPTION_NAMESPACE])) { + $tag['qname'] = $this->options[XML_SERIALIZER_OPTION_NAMESPACE][0] + . ':' . $tag['qname']; + } else { + $tag['qname'] = $this->options[XML_SERIALIZER_OPTION_NAMESPACE] + . ':' . $tag['qname']; + } + } + + // attribute indentation + if ($this->options[XML_SERIALIZER_OPTION_INDENT_ATTRIBUTES] !== false) { + $multiline = true; + $indent = str_repeat($this->options[XML_SERIALIZER_OPTION_INDENT], + $this->_tagDepth); + + if ($this->options[XML_SERIALIZER_OPTION_INDENT_ATTRIBUTES] == '_auto') { + $indent .= str_repeat(' ', (strlen($tag['qname'])+2)); + + } else { + $indent .= $this->options[XML_SERIALIZER_OPTION_INDENT_ATTRIBUTES]; + } + } else { + $multiline = false; + $indent = false; + } + + if (is_array($tag['content'])) { + if (empty($tag['content'])) { + $tag['content'] = ''; + } + } elseif (is_scalar($tag['content']) && (string)$tag['content'] == '') { + $tag['content'] = ''; + } + + // replace XML entities + if ($firstCall === true) { + if ($this->options[XML_SERIALIZER_OPTION_CDATA_SECTIONS] === true) { + $replaceEntities = XML_UTIL_CDATA_SECTION; + } else { + $replaceEntities = $this->options[XML_SERIALIZER_OPTION_ENTITIES]; + } + } else { + // this is a nested call, so value is already encoded + // and must not be encoded again + $replaceEntities = XML_SERIALIZER_ENTITIES_NONE; + // but attributes need to be encoded anyways + // (done here because the rest of the code assumes the same encoding + // can be used both for attributes and content) + foreach ($tag['attributes'] as $k => &$v) { + $v = XML_Util::replaceEntities($v, + $this->options[XML_SERIALIZER_OPTION_ENTITIES]); + } + } + if (is_scalar($tag['content']) || is_null($tag['content'])) { + if ($this->options[XML_SERIALIZER_OPTION_ENCODE_FUNC]) { + if ($firstCall === true) { + $tag['content'] = call_user_func($this-> + options[XML_SERIALIZER_OPTION_ENCODE_FUNC], $tag['content']); + } + $tag['attributes'] = array_map($this-> + options[XML_SERIALIZER_OPTION_ENCODE_FUNC], $tag['attributes']); + } + $tag = XML_Util::createTagFromArray($tag, $replaceEntities, $multiline, + $indent, $this->options[XML_SERIALIZER_OPTION_LINEBREAKS]); + } elseif (is_array($tag['content'])) { + $tag = $this->_serializeArray($tag['content'], $tag['qname'], + $tag['attributes']); + } elseif (is_object($tag['content'])) { + $tag = $this->_serializeObject($tag['content'], $tag['qname'], + $tag['attributes']); + } elseif (is_resource($tag['content'])) { + settype($tag['content'], 'string'); + if ($this->options[XML_SERIALIZER_OPTION_ENCODE_FUNC]) { + if ($replaceEntities === true) { + $tag['content'] = call_user_func($this-> + options[XML_SERIALIZER_OPTION_ENCODE_FUNC], $tag['content']); + } + $tag['attributes'] = array_map($this-> + options[XML_SERIALIZER_OPTION_ENCODE_FUNC], + $tag['attributes']); + } + $tag = XML_Util::createTagFromArray($tag, $replaceEntities); + } + return $tag; + } +} +?> diff --git a/lib/PEAR/XML/Unserializer.php b/lib/PEAR/XML/Unserializer.php new file mode 100644 index 0000000000..4db7e9f6ed --- /dev/null +++ b/lib/PEAR/XML/Unserializer.php @@ -0,0 +1,983 @@ +<?PHP +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * XML_Unserializer + * + * Parses any XML document into PHP data structures. + * + * PHP versions 4 and 5 + * + * LICENSE: + * + * Copyright (c) 2003-2008 Stephan Schmidt <schst@php.net> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @category XML + * @package XML_Serializer + * @author Stephan Schmidt <schst@php.net> + * @copyright 2003-2008 Stephan Schmidt <schst@php.net> + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version CVS: $Id: Unserializer.php,v 1.41 2008/08/25 00:07:16 ashnazg Exp $ + * @link http://pear.php.net/package/XML_Serializer + * @see XML_Unserializer + */ + +/** + * uses PEAR error managemt + */ +require_once 'PEAR.php'; + +/** + * uses XML_Parser to unserialize document + */ +require_once 'XML/Parser.php'; + +/** + * option: Convert nested tags to array or object + * + * Possible values: + * - array + * - object + * - associative array to define this option per tag name + */ +define('XML_UNSERIALIZER_OPTION_COMPLEXTYPE', 'complexType'); + +/** + * option: Name of the attribute that stores the original key + * + * Possible values: + * - any string + */ +define('XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY', 'keyAttribute'); + +/** + * option: Name of the attribute that stores the type + * + * Possible values: + * - any string + */ +define('XML_UNSERIALIZER_OPTION_ATTRIBUTE_TYPE', 'typeAttribute'); + +/** + * option: Name of the attribute that stores the class name + * + * Possible values: + * - any string + */ +define('XML_UNSERIALIZER_OPTION_ATTRIBUTE_CLASS', 'classAttribute'); + +/** + * option: Whether to use the tag name as a class name + * + * Possible values: + * - true or false + */ +define('XML_UNSERIALIZER_OPTION_TAG_AS_CLASSNAME', 'tagAsClass'); + +/** + * option: Name of the default class + * + * Possible values: + * - any string + */ +define('XML_UNSERIALIZER_OPTION_DEFAULT_CLASS', 'defaultClass'); + +/** + * option: Whether to parse attributes + * + * Possible values: + * - true or false + */ +define('XML_UNSERIALIZER_OPTION_ATTRIBUTES_PARSE', 'parseAttributes'); + +/** + * option: Key of the array to store attributes (if any) + * + * Possible values: + * - any string + * - false (disabled) + */ +define('XML_UNSERIALIZER_OPTION_ATTRIBUTES_ARRAYKEY', 'attributesArray'); + +/** + * option: string to prepend attribute name (if any) + * + * Possible values: + * - any string + * - false (disabled) + */ +define('XML_UNSERIALIZER_OPTION_ATTRIBUTES_PREPEND', 'prependAttributes'); + +/** + * option: key to store the content, + * if XML_UNSERIALIZER_OPTION_ATTRIBUTES_PARSE is used + * + * Possible values: + * - any string + */ +define('XML_UNSERIALIZER_OPTION_CONTENT_KEY', 'contentName'); + +/** + * option: map tag names + * + * Possible values: + * - associative array + */ +define('XML_UNSERIALIZER_OPTION_TAG_MAP', 'tagMap'); + +/** + * option: list of tags that will always be enumerated + * + * Possible values: + * - indexed array + */ +define('XML_UNSERIALIZER_OPTION_FORCE_ENUM', 'forceEnum'); + +/** + * option: Encoding of the XML document + * + * Possible values: + * - UTF-8 + * - ISO-8859-1 + */ +define('XML_UNSERIALIZER_OPTION_ENCODING_SOURCE', 'encoding'); + +/** + * option: Desired target encoding of the data + * + * Possible values: + * - UTF-8 + * - ISO-8859-1 + */ +define('XML_UNSERIALIZER_OPTION_ENCODING_TARGET', 'targetEncoding'); + +/** + * option: Callback that will be applied to textual data + * + * Possible values: + * - any valid PHP callback + */ +define('XML_UNSERIALIZER_OPTION_DECODE_FUNC', 'decodeFunction'); + +/** + * option: whether to return the result of the unserialization from unserialize() + * + * Possible values: + * - true + * - false (default) + */ +define('XML_UNSERIALIZER_OPTION_RETURN_RESULT', 'returnResult'); + +/** + * option: set the whitespace behaviour + * + * Possible values: + * - XML_UNSERIALIZER_WHITESPACE_KEEP + * - XML_UNSERIALIZER_WHITESPACE_TRIM + * - XML_UNSERIALIZER_WHITESPACE_NORMALIZE + */ +define('XML_UNSERIALIZER_OPTION_WHITESPACE', 'whitespace'); + +/** + * Keep all whitespace + */ +define('XML_UNSERIALIZER_WHITESPACE_KEEP', 'keep'); + +/** + * remove whitespace from start and end of the data + */ +define('XML_UNSERIALIZER_WHITESPACE_TRIM', 'trim'); + +/** + * normalize whitespace + */ +define('XML_UNSERIALIZER_WHITESPACE_NORMALIZE', 'normalize'); + +/** + * option: whether to ovverride all options that have been set before + * + * Possible values: + * - true + * - false (default) + */ +define('XML_UNSERIALIZER_OPTION_OVERRIDE_OPTIONS', 'overrideOptions'); + +/** + * option: list of tags, that will not be used as keys + */ +define('XML_UNSERIALIZER_OPTION_IGNORE_KEYS', 'ignoreKeys'); + +/** + * option: whether to use type guessing for scalar values + */ +define('XML_UNSERIALIZER_OPTION_GUESS_TYPES', 'guessTypes'); + +/** + * error code for no serialization done + */ +define('XML_UNSERIALIZER_ERROR_NO_UNSERIALIZATION', 151); + +/** + * XML_Unserializer + * + * class to unserialize XML documents that have been created with + * XML_Serializer. To unserialize an XML document you have to add + * type hints to the XML_Serializer options. + * + * If no type hints are available, XML_Unserializer will guess how + * the tags should be treated, that means complex structures will be + * arrays and tags with only CData in them will be strings. + * + * <code> + * require_once 'XML/Unserializer.php'; + * + * // be careful to always use the ampersand in front of the new operator + * $unserializer = &new XML_Unserializer(); + * + * $unserializer->unserialize($xml); + * + * $data = $unserializer->getUnserializedData(); + * <code> + * + * @category XML + * @package XML_Serializer + * @author Stephan Schmidt <schst@php.net> + * @copyright 2003-2008 Stephan Schmidt <schst@php.net> + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version Release: 0.19.2 + * @link http://pear.php.net/package/XML_Serializer + * @see XML_Serializer + */ +class XML_Unserializer extends PEAR +{ + /** + * list of all available options + * + * @access private + * @var array + */ + var $_knownOptions = array( + XML_UNSERIALIZER_OPTION_COMPLEXTYPE, + XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY, + XML_UNSERIALIZER_OPTION_ATTRIBUTE_TYPE, + XML_UNSERIALIZER_OPTION_ATTRIBUTE_CLASS, + XML_UNSERIALIZER_OPTION_TAG_AS_CLASSNAME, + XML_UNSERIALIZER_OPTION_DEFAULT_CLASS, + XML_UNSERIALIZER_OPTION_ATTRIBUTES_PARSE, + XML_UNSERIALIZER_OPTION_ATTRIBUTES_ARRAYKEY, + XML_UNSERIALIZER_OPTION_ATTRIBUTES_PREPEND, + XML_UNSERIALIZER_OPTION_CONTENT_KEY, + XML_UNSERIALIZER_OPTION_TAG_MAP, + XML_UNSERIALIZER_OPTION_FORCE_ENUM, + XML_UNSERIALIZER_OPTION_ENCODING_SOURCE, + XML_UNSERIALIZER_OPTION_ENCODING_TARGET, + XML_UNSERIALIZER_OPTION_DECODE_FUNC, + XML_UNSERIALIZER_OPTION_RETURN_RESULT, + XML_UNSERIALIZER_OPTION_WHITESPACE, + XML_UNSERIALIZER_OPTION_IGNORE_KEYS, + XML_UNSERIALIZER_OPTION_GUESS_TYPES + ); + /** + * default options for the serialization + * + * @access private + * @var array + */ + var $_defaultOptions = array( + // complex types will be converted to arrays, if no type hint is given + XML_UNSERIALIZER_OPTION_COMPLEXTYPE => 'array', + + // get array key/property name from this attribute + XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY => '_originalKey', + + // get type from this attribute + XML_UNSERIALIZER_OPTION_ATTRIBUTE_TYPE => '_type', + + // get class from this attribute (if not given, use tag name) + XML_UNSERIALIZER_OPTION_ATTRIBUTE_CLASS => '_class', + + // use the tagname as the classname + XML_UNSERIALIZER_OPTION_TAG_AS_CLASSNAME => true, + + // name of the class that is used to create objects + XML_UNSERIALIZER_OPTION_DEFAULT_CLASS => 'stdClass', + + // parse the attributes of the tag into an array + XML_UNSERIALIZER_OPTION_ATTRIBUTES_PARSE => false, + + // parse them into sperate array (specify name of array here) + XML_UNSERIALIZER_OPTION_ATTRIBUTES_ARRAYKEY => false, + + // prepend attribute names with this string + XML_UNSERIALIZER_OPTION_ATTRIBUTES_PREPEND => '', + + // put cdata found in a tag that has been converted + // to a complex type in this key + XML_UNSERIALIZER_OPTION_CONTENT_KEY => '_content', + + // use this to map tagnames + XML_UNSERIALIZER_OPTION_TAG_MAP => array(), + + // these tags will always be an indexed array + XML_UNSERIALIZER_OPTION_FORCE_ENUM => array(), + + // specify the encoding character of the document to parse + XML_UNSERIALIZER_OPTION_ENCODING_SOURCE => null, + + // specify the target encoding + XML_UNSERIALIZER_OPTION_ENCODING_TARGET => null, + + // function used to decode data + XML_UNSERIALIZER_OPTION_DECODE_FUNC => null, + + // unserialize() returns the result of the unserialization instead of true + XML_UNSERIALIZER_OPTION_RETURN_RESULT => false, + + // remove whitespace around data + XML_UNSERIALIZER_OPTION_WHITESPACE => XML_UNSERIALIZER_WHITESPACE_TRIM, + + // List of tags that will automatically be added to the parent, + // instead of adding a new key + XML_UNSERIALIZER_OPTION_IGNORE_KEYS => array(), + + // Whether to use type guessing + XML_UNSERIALIZER_OPTION_GUESS_TYPES => false + ); + + /** + * current options for the serialization + * + * @access public + * @var array + */ + var $options = array(); + + /** + * unserialized data + * + * @access private + * @var string + */ + var $_unserializedData = null; + + /** + * name of the root tag + * + * @access private + * @var string + */ + var $_root = null; + + /** + * stack for all data that is found + * + * @access private + * @var array + */ + var $_dataStack = array(); + + /** + * stack for all values that are generated + * + * @access private + * @var array + */ + var $_valStack = array(); + + /** + * current tag depth + * + * @access private + * @var int + */ + var $_depth = 0; + + /** + * XML_Parser instance + * + * @access private + * @var object XML_Parser + */ + var $_parser = null; + + /** + * constructor + * + * @param mixed $options array containing options for the unserialization + * + * @access public + */ + function XML_Unserializer($options = null) + { + if (is_array($options)) { + $this->options = array_merge($this->_defaultOptions, $options); + } else { + $this->options = $this->_defaultOptions; + } + } + + /** + * return API version + * + * @access public + * @return string $version API version + * @static + */ + function apiVersion() + { + return '0.19.2'; + } + + /** + * reset all options to default options + * + * @return void + * @access public + * @see setOption(), XML_Unserializer(), setOptions() + */ + function resetOptions() + { + $this->options = $this->_defaultOptions; + } + + /** + * set an option + * + * You can use this method if you do not want + * to set all options in the constructor + * + * @param string $name name of option + * @param mixed $value value of option + * + * @return void + * @access public + * @see resetOption(), XML_Unserializer(), setOptions() + */ + function setOption($name, $value) + { + $this->options[$name] = $value; + } + + /** + * sets several options at once + * + * You can use this method if you do not want + * to set all options in the constructor + * + * @param array $options options array + * + * @return void + * @access public + * @see resetOption(), XML_Unserializer(), setOption() + */ + function setOptions($options) + { + $this->options = array_merge($this->options, $options); + } + + /** + * unserialize data + * + * @param mixed $data data to unserialize (string, filename or resource) + * @param boolean $isFile data should be treated as a file + * @param array $options options that will override + * the global options for this call + * + * @return boolean $success + * @access public + */ + function unserialize($data, $isFile = false, $options = null) + { + $this->_unserializedData = null; + $this->_root = null; + + // if options have been specified, use them instead + // of the previously defined ones + if (is_array($options)) { + $optionsBak = $this->options; + if (isset($options[XML_UNSERIALIZER_OPTION_OVERRIDE_OPTIONS]) + && $options[XML_UNSERIALIZER_OPTION_OVERRIDE_OPTIONS] == true + ) { + $this->options = array_merge($this->_defaultOptions, $options); + } else { + $this->options = array_merge($this->options, $options); + } + } else { + $optionsBak = null; + } + + $this->_valStack = array(); + $this->_dataStack = array(); + $this->_depth = 0; + + $this->_createParser(); + + if (is_string($data)) { + if ($isFile) { + $result = $this->_parser->setInputFile($data); + if (PEAR::isError($result)) { + return $result; + } + $result = $this->_parser->parse(); + } else { + $result = $this->_parser->parseString($data, true); + } + } else { + $this->_parser->setInput($data); + $result = $this->_parser->parse(); + } + + if ($this->options[XML_UNSERIALIZER_OPTION_RETURN_RESULT] === true) { + $return = $this->_unserializedData; + } else { + $return = true; + } + + if ($optionsBak !== null) { + $this->options = $optionsBak; + } + + if (PEAR::isError($result)) { + return $result; + } + + return $return; + } + + /** + * get the result of the serialization + * + * @access public + * @return string $serializedData + */ + function getUnserializedData() + { + if ($this->_root === null) { + return $this->raiseError('No unserialized data available. ' + . 'Use XML_Unserializer::unserialize() first.', + XML_UNSERIALIZER_ERROR_NO_UNSERIALIZATION); + } + return $this->_unserializedData; + } + + /** + * get the name of the root tag + * + * @access public + * @return string $rootName + */ + function getRootName() + { + if ($this->_root === null) { + return $this->raiseError('No unserialized data available. ' + . 'Use XML_Unserializer::unserialize() first.', + XML_UNSERIALIZER_ERROR_NO_UNSERIALIZATION); + } + return $this->_root; + } + + /** + * Start element handler for XML parser + * + * @param object $parser XML parser object + * @param string $element XML element + * @param array $attribs attributes of XML tag + * + * @return void + * @access private + */ + function startHandler($parser, $element, $attribs) + { + if (isset($attribs[$this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_TYPE]]) + ) { + $type = $attribs[$this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_TYPE]]; + + $guessType = false; + } else { + $type = 'string'; + if ($this->options[XML_UNSERIALIZER_OPTION_GUESS_TYPES] === true) { + $guessType = true; + } else { + $guessType = false; + } + } + + if ($this->options[XML_UNSERIALIZER_OPTION_DECODE_FUNC] !== null) { + $attribs = array_map($this->options[XML_UNSERIALIZER_OPTION_DECODE_FUNC], + $attribs); + } + + $this->_depth++; + $this->_dataStack[$this->_depth] = null; + + if (is_array($this->options[XML_UNSERIALIZER_OPTION_TAG_MAP]) + && isset($this->options[XML_UNSERIALIZER_OPTION_TAG_MAP][$element]) + ) { + $element = $this->options[XML_UNSERIALIZER_OPTION_TAG_MAP][$element]; + } + + $val = array( + 'name' => $element, + 'value' => null, + 'type' => $type, + 'guessType' => $guessType, + 'childrenKeys' => array(), + 'aggregKeys' => array() + ); + + if ($this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTES_PARSE] == true + && (count($attribs) > 0) + ) { + $val['children'] = array(); + $val['type'] = $this->_getComplexType($element); + $val['class'] = $element; + + if ($this->options[XML_UNSERIALIZER_OPTION_GUESS_TYPES] === true) { + $attribs = $this->_guessAndSetTypes($attribs); + } + if ($this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTES_ARRAYKEY] != false + ) { + $val['children'][$this-> + options[XML_UNSERIALIZER_OPTION_ATTRIBUTES_ARRAYKEY]] = $attribs; + } else { + foreach ($attribs as $attrib => $value) { + $val['children'][$this-> + options[XML_UNSERIALIZER_OPTION_ATTRIBUTES_PREPEND] + . $attrib] = $value; + } + } + } + + $keyAttr = false; + + if (is_string($this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY])) { + $keyAttr = $this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY]; + } elseif (is_array($this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY])) { + if (isset($this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY] + [$element]) + ) { + $keyAttr = + $this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY][$element]; + } elseif (isset($this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY] + ['#default']) + ) { + $keyAttr = $this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY] + ['#default']; + } elseif (isset($this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY] + ['__default']) + ) { + // keep this for BC + $keyAttr = + $this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY] + ['__default']; + } + } + + if ($keyAttr !== false && isset($attribs[$keyAttr])) { + $val['name'] = $attribs[$keyAttr]; + } + + if (isset($attribs[$this-> + options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_CLASS]]) + ) { + $val['class'] = + $attribs[$this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_CLASS]]; + } + + array_push($this->_valStack, $val); + } + + /** + * Try to guess the type of several values and + * set them accordingly + * + * @param array $array array containing the values + * + * @return array array, containing the values with their correct types + * @access private + */ + function _guessAndSetTypes($array) + { + foreach ($array as $key => $value) { + $array[$key] = $this->_guessAndSetType($value); + } + return $array; + } + + /** + * Try to guess the type of a value and + * set it accordingly + * + * @param string $value character data + * + * @return mixed value with the best matching type + * @access private + */ + function _guessAndSetType($value) + { + if ($value === 'true') { + return true; + } + if ($value === 'false') { + return false; + } + if ($value === 'NULL') { + return null; + } + if (preg_match('/^[-+]?[0-9]{1,}$/', $value)) { + return intval($value); + } + if (preg_match('/^[-+]?[0-9]{1,}\.[0-9]{1,}$/', $value)) { + return doubleval($value); + } + return (string)$value; + } + + /** + * End element handler for XML parser + * + * @param object $parser XML parser object + * @param string $element element + * + * @return void + * @access private + */ + function endHandler($parser, $element) + { + $value = array_pop($this->_valStack); + switch ($this->options[XML_UNSERIALIZER_OPTION_WHITESPACE]) { + case XML_UNSERIALIZER_WHITESPACE_KEEP: + $data = $this->_dataStack[$this->_depth]; + break; + case XML_UNSERIALIZER_WHITESPACE_NORMALIZE: + $data = trim(preg_replace('/\s\s+/m', ' ', + $this->_dataStack[$this->_depth])); + break; + case XML_UNSERIALIZER_WHITESPACE_TRIM: + default: + $data = trim($this->_dataStack[$this->_depth]); + break; + } + + // adjust type of the value + switch(strtolower($value['type'])) { + + // unserialize an object + case 'object': + if (isset($value['class'])) { + $classname = $value['class']; + } else { + $classname = ''; + } + // instantiate the class + if ($this->options[XML_UNSERIALIZER_OPTION_TAG_AS_CLASSNAME] === true + && class_exists($classname) + ) { + $value['value'] = &new $classname; + } else { + $value['value'] = + &new $this->options[XML_UNSERIALIZER_OPTION_DEFAULT_CLASS]; + } + if (trim($data) !== '') { + if ($value['guessType'] === true) { + $data = $this->_guessAndSetType($data); + } + $value['children'][$this-> + options[XML_UNSERIALIZER_OPTION_CONTENT_KEY]] = $data; + } + + // set properties + foreach ($value['children'] as $prop => $propVal) { + // check whether there is a special method to set this property + $setMethod = 'set'.$prop; + if (method_exists($value['value'], $setMethod)) { + call_user_func(array(&$value['value'], $setMethod), $propVal); + } else { + $value['value']->$prop = $propVal; + } + } + // check for magic function + if (method_exists($value['value'], '__wakeup')) { + $value['value']->__wakeup(); + } + break; + + // unserialize an array + case 'array': + if (trim($data) !== '') { + if ($value['guessType'] === true) { + $data = $this->_guessAndSetType($data); + } + $value['children'][$this-> + options[XML_UNSERIALIZER_OPTION_CONTENT_KEY]] = $data; + } + if (isset($value['children'])) { + $value['value'] = $value['children']; + } else { + $value['value'] = array(); + } + break; + + // unserialize a null value + case 'null': + $data = null; + break; + + // unserialize a resource => this is not possible :-( + case 'resource': + $value['value'] = $data; + break; + + // unserialize any scalar value + default: + if ($value['guessType'] === true) { + $data = $this->_guessAndSetType($data); + } else { + settype($data, $value['type']); + } + + $value['value'] = $data; + break; + } + $parent = array_pop($this->_valStack); + if ($parent === null) { + $this->_unserializedData = &$value['value']; + $this->_root = &$value['name']; + return true; + } else { + // parent has to be an array + if (!isset($parent['children']) || !is_array($parent['children'])) { + $parent['children'] = array(); + if (!in_array($parent['type'], array('array', 'object'))) { + $parent['type'] = $this->_getComplexType($parent['name']); + if ($parent['type'] == 'object') { + $parent['class'] = $parent['name']; + } + } + } + + if (in_array($element, + $this->options[XML_UNSERIALIZER_OPTION_IGNORE_KEYS]) + ) { + $ignoreKey = true; + } else { + $ignoreKey = false; + } + + if (!empty($value['name']) && $ignoreKey === false) { + // there already has been a tag with this name + if (in_array($value['name'], $parent['childrenKeys']) + || in_array($value['name'], + $this->options[XML_UNSERIALIZER_OPTION_FORCE_ENUM]) + ) { + // no aggregate has been created for this tag + if (!in_array($value['name'], $parent['aggregKeys'])) { + if (isset($parent['children'][$value['name']])) { + $parent['children'][$value['name']] = + array($parent['children'][$value['name']]); + } else { + $parent['children'][$value['name']] = array(); + } + array_push($parent['aggregKeys'], $value['name']); + } + array_push($parent['children'][$value['name']], $value['value']); + } else { + $parent['children'][$value['name']] = &$value['value']; + array_push($parent['childrenKeys'], $value['name']); + } + } else { + array_push($parent['children'], $value['value']); + } + array_push($this->_valStack, $parent); + } + + $this->_depth--; + } + + /** + * Handler for character data + * + * @param object $parser XML parser object + * @param string $cdata CDATA + * + * @return void + * @access private + */ + function cdataHandler($parser, $cdata) + { + if ($this->options[XML_UNSERIALIZER_OPTION_DECODE_FUNC] !== null) { + $cdata = call_user_func($this-> + options[XML_UNSERIALIZER_OPTION_DECODE_FUNC], $cdata); + } + $this->_dataStack[$this->_depth] .= $cdata; + } + + /** + * get the complex type, that should be used for a specified tag + * + * @param string $tagname name of the tag + * + * @return string complex type ('array' or 'object') + * @access private + */ + function _getComplexType($tagname) + { + if (is_string($this->options[XML_UNSERIALIZER_OPTION_COMPLEXTYPE])) { + return $this->options[XML_UNSERIALIZER_OPTION_COMPLEXTYPE]; + } + if (isset($this->options[XML_UNSERIALIZER_OPTION_COMPLEXTYPE][$tagname])) { + return $this->options[XML_UNSERIALIZER_OPTION_COMPLEXTYPE][$tagname]; + } + if (isset($this->options[XML_UNSERIALIZER_OPTION_COMPLEXTYPE]['#default'])) { + return $this->options[XML_UNSERIALIZER_OPTION_COMPLEXTYPE]['#default']; + } + return 'array'; + } + + /** + * create the XML_Parser instance + * + * @return boolean + * @access private + */ + function _createParser() + { + if (is_object($this->_parser)) { + $this->_parser->free(); + unset($this->_parser); + } + $this->_parser = &new XML_Parser($this-> + options[XML_UNSERIALIZER_OPTION_ENCODING_SOURCE], + 'event', $this->options[XML_UNSERIALIZER_OPTION_ENCODING_TARGET]); + + $this->_parser->folding = false; + $this->_parser->setHandlerObj($this); + return true; + } +} +?> diff --git a/lib/Varien/Db/Adapter/Pdo/Mysql.php b/lib/Varien/Db/Adapter/Pdo/Mysql.php index 84767ed171..076e1a56a6 100644 --- a/lib/Varien/Db/Adapter/Pdo/Mysql.php +++ b/lib/Varien/Db/Adapter/Pdo/Mysql.php @@ -893,4 +893,18 @@ public function getLimitation($code) return $value; } + + /** + * Truncate table + * + * @param string $tableName + * @return Varien_Db_Adapter_Pdo_Mysql + */ + public function truncate($tableName) + { + $sql = 'TRUNCATE ' . $this->quoteIdentifier($tableName); + $this->raw_query($sql); + + return $this; + } } diff --git a/lib/Varien/Db/Select.php b/lib/Varien/Db/Select.php index 6088eab578..d34ed56fed 100644 --- a/lib/Varien/Db/Select.php +++ b/lib/Varien/Db/Select.php @@ -232,6 +232,12 @@ public function crossUpdateFromSelect($table) { $columns = array(); foreach ($this->_parts[self::COLUMNS] as $columnEntry) { list($correlationName, $column, $alias) = $columnEntry; + if (empty($alias)) { + $alias = $column; + } + if (!$column instanceof Zend_Db_Expr && !empty($correlationName)) { + $column = $this->_adapter->quoteIdentifier(array($correlationName, $column)); + } $columns[] = $this->_adapter->quoteIdentifier(array($tableAlias, $alias)) . " = {$column}"; } diff --git a/lib/Varien/Simplexml/Config.php b/lib/Varien/Simplexml/Config.php index 47269ed86d..7ca8c0449f 100644 --- a/lib/Varien/Simplexml/Config.php +++ b/lib/Varien/Simplexml/Config.php @@ -413,7 +413,7 @@ public function saveCache($tags=null) $this->_saveCache($this->getCacheChecksum(), $this->getCacheChecksumId(), $tags, $this->getCacheLifetime()); } - $xmlString = $this->getNode()->asNiceXml('', false); + $xmlString = $this->getXmlString(); $this->_saveCache($xmlString, $this->getCacheId(), $tags, $this->getCacheLifetime()); $this->setCacheSaved(true); @@ -421,6 +421,16 @@ public function saveCache($tags=null) return $this; } + /** + * Return Xml of node as string + * + * @return string + */ + public function getXmlString() + { + return $this->getNode()->asNiceXml('', false); + } + /** * Enter description here... * diff --git a/skin/frontend/default/default/css/boxes.css b/skin/frontend/default/default/css/boxes.css index c03f80e0bb..164d0e50f4 100644 --- a/skin/frontend/default/default/css/boxes.css +++ b/skin/frontend/default/default/css/boxes.css @@ -751,12 +751,15 @@ a.minimal-price-link .price { font-weight:normal; color:#1e7ec8; } .best-selling .product-description { margin-left:107px; line-height:1.3em; } .best-selling a.product-name, .home-spot .best-selling a.product-name:hover { color:#203548; } -.recently h3 { margin:12px 0 6px 0; color:#e25203; font-size:1.2em; } -.recently .product-image { border:2px solid #dcdcdc; } -.recently a.product-name, .recently a.product-name:hover { color:#1d7ecf; font-size:11px; } +.recently { margin:0 0 12px; } +.recently h3 { margin:0 0 6px; color:#e25203; font-size:1.2em; } +.recently .product-image { border:1px solid #dcdcdc; } +.recently a.product-name { display:block; width:130px; overflow:hidden; } +.recently a.product-name, +.recently a.product-name:hover { font-size:11px; color:#1d7ecf; } .recently .add-to {margin-top:5px;font-size:11px; } -table.recently-list {width:100%; } -table.recently-list td {width:20%; } +table.recently-list { width:100%; } +table.recently-list td { width:20%; } /********************** Search */ .advanced-search {