Version Notes
Adds support for checkout via Magento.
Download this release
Release Info
| Developer | PriceWaiter |
| Extension | nypwidget |
| Version | 3.0.0 |
| Comparing to | |
| See all releases | |
Code changes from version 2.5.5 to 3.0.0
- app/code/community/PriceWaiter/NYPWidget/Block/Widget.php +19 -7
- app/code/community/PriceWaiter/NYPWidget/Controller/Endpoint.php +206 -0
- app/code/community/PriceWaiter/NYPWidget/Controller/Endpoint/Request.php +116 -0
- app/code/community/PriceWaiter/NYPWidget/Controller/Endpoint/Response.php +83 -0
- app/code/community/PriceWaiter/NYPWidget/Exception/Abstract.php +60 -0
- app/code/community/PriceWaiter/NYPWidget/Exception/DealAlreadyCreated.php +10 -0
- app/code/community/PriceWaiter/NYPWidget/Exception/DealAlreadyRevoked.php +10 -0
- app/code/community/PriceWaiter/NYPWidget/Exception/DealNotFound.php +10 -0
- app/code/community/PriceWaiter/NYPWidget/Exception/InvalidRegion.php +1 -1
- app/code/community/PriceWaiter/NYPWidget/Exception/NoTestDeals.php +10 -0
- app/code/community/PriceWaiter/NYPWidget/Exception/OrderNotFound.php +11 -0
- app/code/community/PriceWaiter/NYPWidget/Exception/OutOfStock.php +0 -10
- app/code/community/PriceWaiter/NYPWidget/Exception/Product/Abstract.php +1 -0
- app/code/community/PriceWaiter/NYPWidget/Exception/Product/Invalid.php +15 -0
- app/code/community/PriceWaiter/NYPWidget/Exception/Product/NotFound.php +1 -2
- app/code/community/PriceWaiter/NYPWidget/Exception/Product/OutOfStock.php +10 -0
- app/code/community/PriceWaiter/NYPWidget/Exception/Signature.php +1 -1
- app/code/community/PriceWaiter/NYPWidget/Exception/SingleItemOnly.php +10 -0
- app/code/community/PriceWaiter/NYPWidget/Exception/Version.php +34 -0
- app/code/community/PriceWaiter/NYPWidget/Helper/About.php +65 -0
- app/code/community/PriceWaiter/NYPWidget/Helper/Data.php +172 -360
- app/code/community/PriceWaiter/NYPWidget/Helper/Product.php +0 -108
- app/code/community/PriceWaiter/NYPWidget/Model/Callback.php +151 -1
- app/code/community/PriceWaiter/NYPWidget/Model/Callback/Inventory.php +2 -7
- app/code/community/PriceWaiter/NYPWidget/Model/Deal.php +282 -0
- app/code/community/PriceWaiter/NYPWidget/Model/Discounter.php +195 -0
- app/code/community/PriceWaiter/NYPWidget/Model/Embed.php +520 -0
- app/code/community/PriceWaiter/NYPWidget/Model/Mysql4/Deal.php +16 -0
- app/code/community/PriceWaiter/NYPWidget/Model/Mysql4/Deal/Collection.php +10 -0
- app/code/community/PriceWaiter/NYPWidget/Model/Mysql4/Deal/Usage.php +228 -0
- app/code/community/PriceWaiter/NYPWidget/Model/Observer.php +48 -0
- app/code/community/PriceWaiter/NYPWidget/Model/Offer/Item.php +501 -0
- app/code/community/PriceWaiter/NYPWidget/Model/Offer/Item/Handler.php +263 -0
- app/code/community/PriceWaiter/NYPWidget/Model/Offer/Item/Inventory.php +127 -0
- app/code/community/PriceWaiter/NYPWidget/Model/Offer/Item/Pricing.php +269 -0
- app/code/community/PriceWaiter/NYPWidget/Model/Session.php +112 -0
- app/code/community/PriceWaiter/NYPWidget/Model/Total/Quote.php +452 -0
- app/code/community/PriceWaiter/NYPWidget/controllers/Adminhtml/PricewaiterController.php +0 -14
- app/code/community/PriceWaiter/NYPWidget/controllers/CallbackController.php +1 -1
- app/code/community/PriceWaiter/NYPWidget/controllers/CheckoutController.php +204 -0
- app/code/community/PriceWaiter/NYPWidget/controllers/CreatedealController.php +46 -0
- app/code/community/PriceWaiter/NYPWidget/controllers/DebugController.php +77 -0
- app/code/community/PriceWaiter/NYPWidget/controllers/ListordersController.php +103 -0
- app/code/community/PriceWaiter/NYPWidget/controllers/PingController.php +22 -0
- app/code/community/PriceWaiter/NYPWidget/controllers/ProductinfoController.php +138 -38
- app/code/community/PriceWaiter/NYPWidget/controllers/RevokedealController.php +50 -0
- app/code/community/PriceWaiter/NYPWidget/etc/config.xml +45 -1
- app/code/community/PriceWaiter/NYPWidget/sql/nypwidget_setup/mysql4-install-1.0.0.php +1 -0
- app/code/community/PriceWaiter/NYPWidget/sql/nypwidget_setup/mysql4-upgrade-2.5.4-3.0.0.php +193 -0
- app/design/frontend/base/default/template/pricewaiter/widget.phtml +10 -79
- package.xml +6 -6
app/code/community/PriceWaiter/NYPWidget/Block/Widget.php
CHANGED
|
@@ -2,14 +2,26 @@
|
|
| 2 |
|
| 3 |
class PriceWaiter_NYPWidget_Block_Widget extends Mage_Core_Block_Template
|
| 4 |
{
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
}
|
| 10 |
-
|
| 11 |
-
public function getPriceWaiterOptions()
|
| 12 |
{
|
|
|
|
| 13 |
$product = Mage::registry('current_product');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
}
|
| 15 |
}
|
| 2 |
|
| 3 |
class PriceWaiter_NYPWidget_Block_Widget extends Mage_Core_Block_Template
|
| 4 |
{
|
| 5 |
+
/**
|
| 6 |
+
* @return PriceWaiter_NYPWidget_Model_Embed
|
| 7 |
+
*/
|
| 8 |
+
public function getEmbed()
|
|
|
|
|
|
|
|
|
|
| 9 |
{
|
| 10 |
+
// Figure out where + who we are...
|
| 11 |
$product = Mage::registry('current_product');
|
| 12 |
+
$store = Mage::app()->getStore();
|
| 13 |
+
$category = Mage::registry('current_category');
|
| 14 |
+
|
| 15 |
+
$session = Mage::getSingleton('customer/session');
|
| 16 |
+
$customer = $session->getCustomer();
|
| 17 |
+
$customerGroupId = $session->getCustomerGroupId();
|
| 18 |
+
|
| 19 |
+
// ..and wire up an appropriate embed.
|
| 20 |
+
return Mage::getModel('nypwidget/embed')
|
| 21 |
+
->setProduct($product)
|
| 22 |
+
->setStore($store)
|
| 23 |
+
->setCategory($category)
|
| 24 |
+
->setCustomer($customer->getId() ? $customer : false)
|
| 25 |
+
->setCustomerGroupId($customerGroupId);
|
| 26 |
}
|
| 27 |
}
|
app/code/community/PriceWaiter/NYPWidget/Controller/Endpoint.php
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
/*
|
| 3 |
+
* Copyright 2013-2016 Price Waiter, LLC
|
| 4 |
+
*
|
| 5 |
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
| 6 |
+
* you may not use this file except in compliance with the License.
|
| 7 |
+
* You may obtain a copy of the License at
|
| 8 |
+
*
|
| 9 |
+
* http://www.apache.org/licenses/LICENSE-2.0
|
| 10 |
+
*
|
| 11 |
+
* Unless required by applicable law or agreed to in writing, software
|
| 12 |
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
| 13 |
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 14 |
+
* See the License for the specific language governing permissions and
|
| 15 |
+
* limitations under the License.
|
| 16 |
+
*
|
| 17 |
+
*/
|
| 18 |
+
|
| 19 |
+
/**
|
| 20 |
+
* Base class for impelmenting PriceWaiter-specific endpoints.
|
| 21 |
+
* Our endpoints all use HTTP POST, with signed JSON request/response payloads.
|
| 22 |
+
* This base class handles the signing and request versioning.
|
| 23 |
+
*/
|
| 24 |
+
abstract class PriceWaiter_NYPWidget_Controller_Endpoint extends Mage_Core_Controller_Front_Action
|
| 25 |
+
{
|
| 26 |
+
/**
|
| 27 |
+
* Set to the set of version numbers that this endpoint can process.
|
| 28 |
+
* @var array
|
| 29 |
+
*/
|
| 30 |
+
protected $supportedVersions = [];
|
| 31 |
+
|
| 32 |
+
/**
|
| 33 |
+
* Content type in which responses are sent.
|
| 34 |
+
*/
|
| 35 |
+
const RESPONSE_CONTENT_TYPE = 'application/json; charset=UTF-8';
|
| 36 |
+
|
| 37 |
+
/**
|
| 38 |
+
* HTTP header containing the API key of the PriceWaiter store.
|
| 39 |
+
*/
|
| 40 |
+
const API_KEY_HEADER = 'X-PriceWaiter-Api-Key';
|
| 41 |
+
|
| 42 |
+
/**
|
| 43 |
+
* HTTP header containing the unique PW request id.
|
| 44 |
+
*/
|
| 45 |
+
const REQUEST_ID_HEADER = 'X-PriceWaiter-Request-Id';
|
| 46 |
+
|
| 47 |
+
/**
|
| 48 |
+
* HTTP header in which request/response signature is stored.
|
| 49 |
+
*/
|
| 50 |
+
const SIGNATURE_HEADER = 'X-PriceWaiter-Signature';
|
| 51 |
+
|
| 52 |
+
/**
|
| 53 |
+
* Header in which version is specified on requests.
|
| 54 |
+
*/
|
| 55 |
+
const VERSION_HEADER = 'X-PriceWaiter-Version';
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
public function indexAction()
|
| 59 |
+
{
|
| 60 |
+
$httpRequest = Mage::app()->getRequest();
|
| 61 |
+
$httpResponse = Mage::app()->getResponse();
|
| 62 |
+
|
| 63 |
+
// We support POST only. For non-POST requests, we serve the traditional
|
| 64 |
+
// Magento "not found" page.
|
| 65 |
+
if (!$httpRequest->isPost()) {
|
| 66 |
+
return $this->silentNotFound($httpResponse);
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
$response = null;
|
| 70 |
+
|
| 71 |
+
try
|
| 72 |
+
{
|
| 73 |
+
$rawBody = $httpRequest->getRawBody();
|
| 74 |
+
$id = $httpRequest->getHeader(self::REQUEST_ID_HEADER);
|
| 75 |
+
$apiKey = $httpRequest->getHeader(self::API_KEY_HEADER);
|
| 76 |
+
$version = $httpRequest->getHeader(self::VERSION_HEADER);
|
| 77 |
+
|
| 78 |
+
$request = new PriceWaiter_NYPWidget_Controller_Endpoint_Request(
|
| 79 |
+
$id,
|
| 80 |
+
$apiKey,
|
| 81 |
+
$version,
|
| 82 |
+
$rawBody
|
| 83 |
+
);
|
| 84 |
+
|
| 85 |
+
$signature = $httpRequest->getHeader(self::SIGNATURE_HEADER);
|
| 86 |
+
$this->checkSignature($request, $signature);
|
| 87 |
+
|
| 88 |
+
$this->checkVersion($request);
|
| 89 |
+
|
| 90 |
+
$response = $this->processRequest($request);
|
| 91 |
+
}
|
| 92 |
+
catch (PriceWaiter_NYPWidget_Exception_Abstract $ex)
|
| 93 |
+
{
|
| 94 |
+
// This is an exception generated within our code, meant to be passed back to the client.
|
| 95 |
+
// It's reporting something like "signature verification failed" or "product could
|
| 96 |
+
// not be found" or "wtf is that currency".
|
| 97 |
+
|
| 98 |
+
$response = new PriceWaiter_NYPWidget_Controller_Endpoint_Response(
|
| 99 |
+
$ex->httpStatusCode,
|
| 100 |
+
array(
|
| 101 |
+
'error' => $ex->jsonSerialize(),
|
| 102 |
+
)
|
| 103 |
+
);
|
| 104 |
+
}
|
| 105 |
+
catch (Exception $ex)
|
| 106 |
+
{
|
| 107 |
+
Mage::logException($ex);
|
| 108 |
+
|
| 109 |
+
$response = new PriceWaiter_NYPWidget_Controller_Endpoint_Response(
|
| 110 |
+
500,
|
| 111 |
+
array(
|
| 112 |
+
'error' => array(
|
| 113 |
+
'code' => get_class($ex),
|
| 114 |
+
'message' => $ex->getMessage(),
|
| 115 |
+
),
|
| 116 |
+
)
|
| 117 |
+
);
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
$this->sendResponse($response, $httpResponse);
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
/**
|
| 124 |
+
* Override to handle a request to this endpoint.
|
| 125 |
+
* @param PriceWaiter_NYPWidget_Controller_Endpoint_Request $request
|
| 126 |
+
* @return Array Array in the format [ $httpStatusCode, $responseBody ]
|
| 127 |
+
*/
|
| 128 |
+
abstract public function processRequest(PriceWaiter_NYPWidget_Controller_Endpoint_Request $request);
|
| 129 |
+
|
| 130 |
+
/**
|
| 131 |
+
* Checks the X-PriceWaiter-Signature on the incoming request.
|
| 132 |
+
* @param Mage_Core_Controller_Request_Http $request
|
| 133 |
+
* @return Boolean
|
| 134 |
+
* @throws PriceWaiter_NYPWidget_Exception_Signature
|
| 135 |
+
*/
|
| 136 |
+
protected function checkSignature(PriceWaiter_NYPWidget_Controller_Endpoint_Request $request, $signature)
|
| 137 |
+
{
|
| 138 |
+
$secret = $this->getSharedSecret();
|
| 139 |
+
|
| 140 |
+
if ($request->isSignatureValid($signature, $secret)) {
|
| 141 |
+
return true;
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
throw new PriceWaiter_NYPWidget_Exception_Signature();
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
/**
|
| 148 |
+
* Checks the X-PriceWaiter-Version header on the incoming request.
|
| 149 |
+
* @param Mage_Core_Controller_Request_Http $request
|
| 150 |
+
* @return Boolean
|
| 151 |
+
* @throws PriceWaiter_NYPWidget_Exception_Version
|
| 152 |
+
*/
|
| 153 |
+
protected function checkVersion(PriceWaiter_NYPWidget_Controller_Endpoint_Request $request)
|
| 154 |
+
{
|
| 155 |
+
if (count($this->supportedVersions) === 0) {
|
| 156 |
+
$class = function_exists('get_called_class') ? get_called_class() : get_class();
|
| 157 |
+
throw new RuntimeException("$class does not specify $supportedVersions");
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
if (in_array($request->getVersion(), $this->supportedVersions)) {
|
| 161 |
+
return true;
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
throw new PriceWaiter_NYPWidget_Exception_Version($this->supportedVersions);
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
/**
|
| 168 |
+
* @return String The shared secret used for HMAC signing and verification.
|
| 169 |
+
*/
|
| 170 |
+
protected function getSharedSecret()
|
| 171 |
+
{
|
| 172 |
+
$helper = Mage::helper('nypwidget');
|
| 173 |
+
return $helper->getSecret();
|
| 174 |
+
}
|
| 175 |
+
/**
|
| 176 |
+
* Writes a PriceWaiter Endpoint response object out using a standard Magento response.
|
| 177 |
+
* @param PriceWaiter_NYPWidget_Controller_Endpoint_Response $response
|
| 178 |
+
* @param Mage_Core_Controller_Response_Http $httpResponse
|
| 179 |
+
*/
|
| 180 |
+
protected function sendResponse(PriceWaiter_NYPWidget_Controller_Endpoint_Response $response, Mage_Core_Controller_Response_Http $httpResponse)
|
| 181 |
+
{
|
| 182 |
+
$secret = $this->getSharedSecret();
|
| 183 |
+
$signature = $response->sign($secret);
|
| 184 |
+
|
| 185 |
+
$json = $response->getBodyJson();
|
| 186 |
+
|
| 187 |
+
$httpResponse->setHttpResponseCode($response->getStatusCode());
|
| 188 |
+
$httpResponse->setHeader('Content-type', self::RESPONSE_CONTENT_TYPE, true);
|
| 189 |
+
$httpResponse->setHeader(self::SIGNATURE_HEADER, $signature, true);
|
| 190 |
+
|
| 191 |
+
$about = Mage::helper('nypwidget/about');
|
| 192 |
+
$about->setResponseHeaders($httpResponse);
|
| 193 |
+
|
| 194 |
+
$httpResponse->setBody($json);
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
/**
|
| 198 |
+
* Returns a "not found" page as though this route did not even exist...
|
| 199 |
+
* @param Mage_Core_Controller_Response_Http $response
|
| 200 |
+
*/
|
| 201 |
+
protected function silentNotFound(Mage_Core_Controller_Response_Http $httpResponse)
|
| 202 |
+
{
|
| 203 |
+
$httpResponse->setHttpResponseCode(404);
|
| 204 |
+
$this->norouteAction();
|
| 205 |
+
}
|
| 206 |
+
}
|
app/code/community/PriceWaiter/NYPWidget/Controller/Endpoint/Request.php
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
/*
|
| 3 |
+
* Copyright 2013-2016 Price Waiter, LLC
|
| 4 |
+
*
|
| 5 |
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
| 6 |
+
* you may not use this file except in compliance with the License.
|
| 7 |
+
* You may obtain a copy of the License at
|
| 8 |
+
*
|
| 9 |
+
* http://www.apache.org/licenses/LICENSE-2.0
|
| 10 |
+
*
|
| 11 |
+
* Unless required by applicable law or agreed to in writing, software
|
| 12 |
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
| 13 |
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 14 |
+
* See the License for the specific language governing permissions and
|
| 15 |
+
* limitations under the License.
|
| 16 |
+
*
|
| 17 |
+
*/
|
| 18 |
+
|
| 19 |
+
/**
|
| 20 |
+
* Simplified HTTP request abstraction for PriceWaiter endpoints.
|
| 21 |
+
*/
|
| 22 |
+
class PriceWaiter_NYPWidget_Controller_Endpoint_Request
|
| 23 |
+
{
|
| 24 |
+
private $_apiKey;
|
| 25 |
+
private $_id;
|
| 26 |
+
private $_rawBody;
|
| 27 |
+
private $_version;
|
| 28 |
+
private $_timestamp;
|
| 29 |
+
|
| 30 |
+
public function __construct($id, $apiKey, $version, $rawBody, $timestamp = null)
|
| 31 |
+
{
|
| 32 |
+
$this->_id = $id;
|
| 33 |
+
$this->_apiKey = $apiKey;
|
| 34 |
+
$this->_version = $version;
|
| 35 |
+
$this->_rawBody = $rawBody;
|
| 36 |
+
$this->_timestamp = $timestamp === null ? time() : $timestamp;
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
/**
|
| 40 |
+
* @return String The public PriceWaiter API key for this request.
|
| 41 |
+
*/
|
| 42 |
+
public function getApiKey()
|
| 43 |
+
{
|
| 44 |
+
return $this->_apiKey;
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
/**
|
| 48 |
+
* @return Object The request body.
|
| 49 |
+
*/
|
| 50 |
+
public function getBody()
|
| 51 |
+
{
|
| 52 |
+
return json_decode($this->_rawBody);
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
/**
|
| 56 |
+
* @return String The unique PriceWaiter request id.
|
| 57 |
+
*/
|
| 58 |
+
public function getId()
|
| 59 |
+
{
|
| 60 |
+
return $this->_id;
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
/**
|
| 64 |
+
* @return Integer UNIX timestamp representing request date/time.
|
| 65 |
+
*/
|
| 66 |
+
public function getTimestamp()
|
| 67 |
+
{
|
| 68 |
+
return $this->_timestamp;
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
/**
|
| 72 |
+
* @return String Version of request data.
|
| 73 |
+
*/
|
| 74 |
+
public function getVersion()
|
| 75 |
+
{
|
| 76 |
+
return $this->_version;
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
/**
|
| 80 |
+
* Checks an HMAC signature for this request.
|
| 81 |
+
* @param String $signature
|
| 82 |
+
* @param String $secret
|
| 83 |
+
* @return boolean
|
| 84 |
+
*/
|
| 85 |
+
public function isSignatureValid($signature, $secret)
|
| 86 |
+
{
|
| 87 |
+
// NOTE: Never let an empty secret be used.
|
| 88 |
+
if (trim($secret) === '') {
|
| 89 |
+
return false;
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
$headers = array(
|
| 93 |
+
PriceWaiter_NYPWidget_Controller_Endpoint::API_KEY_HEADER => $this->getApiKey(),
|
| 94 |
+
PriceWaiter_NYPWidget_Controller_Endpoint::REQUEST_ID_HEADER => $this->getId(),
|
| 95 |
+
PriceWaiter_NYPWidget_Controller_Endpoint::VERSION_HEADER => $this->getVersion(),
|
| 96 |
+
);
|
| 97 |
+
|
| 98 |
+
$content = array();
|
| 99 |
+
|
| 100 |
+
foreach ($headers as $key => $value) {
|
| 101 |
+
$content[] = "${key}: $value";
|
| 102 |
+
}
|
| 103 |
+
$content[] = $this->_rawBody;
|
| 104 |
+
$content = implode("\n", $content);
|
| 105 |
+
|
| 106 |
+
$detected = 'sha256=' . hash_hmac('sha256', $content, $secret, false);
|
| 107 |
+
|
| 108 |
+
if (function_exists('hash_equals')) {
|
| 109 |
+
// Favor PHP's secure hash comparison function in 5.6 and up.
|
| 110 |
+
// For a robust drop-in compatibility shim, see: https://github.com/realityking/hash_equals
|
| 111 |
+
return hash_equals($detected, $signature);
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
return $detected === $signature;
|
| 115 |
+
}
|
| 116 |
+
}
|
app/code/community/PriceWaiter/NYPWidget/Controller/Endpoint/Response.php
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
/*
|
| 3 |
+
* Copyright 2013-2016 Price Waiter, LLC
|
| 4 |
+
*
|
| 5 |
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
| 6 |
+
* you may not use this file except in compliance with the License.
|
| 7 |
+
* You may obtain a copy of the License at
|
| 8 |
+
*
|
| 9 |
+
* http://www.apache.org/licenses/LICENSE-2.0
|
| 10 |
+
*
|
| 11 |
+
* Unless required by applicable law or agreed to in writing, software
|
| 12 |
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
| 13 |
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 14 |
+
* See the License for the specific language governing permissions and
|
| 15 |
+
* limitations under the License.
|
| 16 |
+
*
|
| 17 |
+
*/
|
| 18 |
+
|
| 19 |
+
/**
|
| 20 |
+
* A simplified abstract Response model for PriceWaiter HTTP endpoints.
|
| 21 |
+
*/
|
| 22 |
+
class PriceWaiter_NYPWidget_Controller_Endpoint_Response
|
| 23 |
+
{
|
| 24 |
+
private $_body = null;
|
| 25 |
+
private $_statusCode = 200;
|
| 26 |
+
private $_bodyJson = null;
|
| 27 |
+
|
| 28 |
+
public function __construct($statusCode = 200, $body = [])
|
| 29 |
+
{
|
| 30 |
+
$this->_statusCode = $statusCode;
|
| 31 |
+
$this->_body = $body;
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
/**
|
| 35 |
+
* @return String JSON-encoded body.
|
| 36 |
+
*/
|
| 37 |
+
public function getBodyJson()
|
| 38 |
+
{
|
| 39 |
+
if ($this->_bodyJson !== null) {
|
| 40 |
+
return $this->_bodyJson;
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
$options = 0;
|
| 44 |
+
|
| 45 |
+
if (defined('JSON_UNESCAPED_SLASHES')) {
|
| 46 |
+
$options |= JSON_UNESCAPED_SLASHES;
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
if (defined('JSON_PRETTY_PRINT')) {
|
| 50 |
+
$options |= JSON_PRETTY_PRINT;
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
return ($this->_bodyJson = json_encode($this->_body, $options));
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
/**
|
| 57 |
+
* @return Integer The HTTP status code to use.
|
| 58 |
+
*/
|
| 59 |
+
public function getStatusCode()
|
| 60 |
+
{
|
| 61 |
+
return $this->_statusCode;
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
/**
|
| 65 |
+
* Returns the HMAC signature for this response.
|
| 66 |
+
* Meant for use in the X-PriceWaiter-Signature header.
|
| 67 |
+
* @param String $secret Shared secret.
|
| 68 |
+
* @return String.
|
| 69 |
+
*/
|
| 70 |
+
public function sign($secret)
|
| 71 |
+
{
|
| 72 |
+
$json = $this->getBodyJson();
|
| 73 |
+
return 'sha256=' . hash_hmac('sha256', $this->getBodyJson(), $secret, false);
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
/**
|
| 77 |
+
* @return PriceWaiter_NYPWidget_Controller_Endpoint_Response A generic 200 "ok" response.
|
| 78 |
+
*/
|
| 79 |
+
public static function ok()
|
| 80 |
+
{
|
| 81 |
+
return new self();
|
| 82 |
+
}
|
| 83 |
+
}
|
app/code/community/PriceWaiter/NYPWidget/Exception/Abstract.php
CHANGED
|
@@ -18,6 +18,24 @@ abstract class PriceWaiter_NYPWidget_Exception_Abstract extends RuntimeException
|
|
| 18 |
*/
|
| 19 |
public $httpStatusCode = 400;
|
| 20 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
/**
|
| 22 |
* @return Array Representation of this error, ready to be passed to json_encode.
|
| 23 |
*/
|
|
@@ -33,4 +51,46 @@ abstract class PriceWaiter_NYPWidget_Exception_Abstract extends RuntimeException
|
|
| 33 |
|
| 34 |
return $json;
|
| 35 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
}
|
| 18 |
*/
|
| 19 |
public $httpStatusCode = 400;
|
| 20 |
|
| 21 |
+
/**
|
| 22 |
+
* @internal
|
| 23 |
+
*/
|
| 24 |
+
protected static $translations = array(
|
| 25 |
+
array(
|
| 26 |
+
'class' => 'Mage_Core_Exception',
|
| 27 |
+
'message' => 'This product is currently out of stock.',
|
| 28 |
+
'helper' => 'cataloginventory',
|
| 29 |
+
'translatedClass' => 'PriceWaiter_NYPWidget_Exception_Product_OutOfStock',
|
| 30 |
+
),
|
| 31 |
+
array(
|
| 32 |
+
'class' => 'Mage_Core_Exception',
|
| 33 |
+
'message' => 'Not all products are available in the requested quantity',
|
| 34 |
+
'helper' => 'cataloginventory',
|
| 35 |
+
'translatedClass' => 'PriceWaiter_NYPWidget_Exception_Product_OutOfStock',
|
| 36 |
+
),
|
| 37 |
+
);
|
| 38 |
+
|
| 39 |
/**
|
| 40 |
* @return Array Representation of this error, ready to be passed to json_encode.
|
| 41 |
*/
|
| 51 |
|
| 52 |
return $json;
|
| 53 |
}
|
| 54 |
+
|
| 55 |
+
/**
|
| 56 |
+
* @internal Attempts to convert a generic Magento exception into a typed PW one.
|
| 57 |
+
* @param Exception $ex
|
| 58 |
+
*/
|
| 59 |
+
public static function translateMagentoException(Exception $ex)
|
| 60 |
+
{
|
| 61 |
+
foreach (self::$translations as $t) {
|
| 62 |
+
$translated = self::applyExceptionTranslation($ex, $t);
|
| 63 |
+
if ($translated) {
|
| 64 |
+
return $translated;
|
| 65 |
+
}
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
return $ex;
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
private static function applyExceptionTranslation(Exception $ex, array $t)
|
| 72 |
+
{
|
| 73 |
+
if (!($ex instanceof $t['class'])) {
|
| 74 |
+
return false;
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
$messages = array(
|
| 78 |
+
$t['message'],
|
| 79 |
+
);
|
| 80 |
+
|
| 81 |
+
if (!empty($t['helper'])) {
|
| 82 |
+
// Also check for a *translated* version of the message
|
| 83 |
+
$helper = Mage::helper($t['helper']);
|
| 84 |
+
$messages[] = $helper->__($t['message']);
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
$actualMessage = $ex->getMessage();
|
| 88 |
+
if (!in_array($actualMessage, $messages)) {
|
| 89 |
+
// No match
|
| 90 |
+
return false;
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
$translatedClass = $t['translatedClass'];
|
| 94 |
+
return new $translatedClass($actualMessage);
|
| 95 |
+
}
|
| 96 |
}
|
app/code/community/PriceWaiter/NYPWidget/Exception/DealAlreadyCreated.php
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
/**
|
| 4 |
+
* Exception thrown when a Deal cannot be created because it already was.
|
| 5 |
+
*/
|
| 6 |
+
class PriceWaiter_NYPWidget_Exception_DealAlreadyCreated
|
| 7 |
+
extends PriceWaiter_NYPWidget_Exception_Abstract
|
| 8 |
+
{
|
| 9 |
+
public $errorCode = 'deal_already_exists';
|
| 10 |
+
}
|
app/code/community/PriceWaiter/NYPWidget/Exception/DealAlreadyRevoked.php
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
/**
|
| 4 |
+
* Exception thrown when a Deal cannot be revoked because it already was.
|
| 5 |
+
*/
|
| 6 |
+
class PriceWaiter_NYPWidget_Exception_DealAlreadyRevoked
|
| 7 |
+
extends PriceWaiter_NYPWidget_Exception_Abstract
|
| 8 |
+
{
|
| 9 |
+
public $errorCode = 'deal_already_revoked';
|
| 10 |
+
}
|
app/code/community/PriceWaiter/NYPWidget/Exception/DealNotFound.php
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
/**
|
| 4 |
+
* Exception thrown when a Deal cannot be found by ID.
|
| 5 |
+
*/
|
| 6 |
+
class PriceWaiter_NYPWidget_Exception_DealNotFound
|
| 7 |
+
extends PriceWaiter_NYPWidget_Exception_Abstract
|
| 8 |
+
{
|
| 9 |
+
public $errorCode = 'deal_not_found';
|
| 10 |
+
}
|
app/code/community/PriceWaiter/NYPWidget/Exception/InvalidRegion.php
CHANGED
|
@@ -6,7 +6,7 @@
|
|
| 6 |
class PriceWaiter_NYPWidget_Exception_InvalidRegion
|
| 7 |
extends PriceWaiter_NYPWidget_Exception_Abstract
|
| 8 |
{
|
| 9 |
-
public $errorCode = '
|
| 10 |
|
| 11 |
/**
|
| 12 |
* @var String
|
| 6 |
class PriceWaiter_NYPWidget_Exception_InvalidRegion
|
| 7 |
extends PriceWaiter_NYPWidget_Exception_Abstract
|
| 8 |
{
|
| 9 |
+
public $errorCode = 'magento_invalid_region';
|
| 10 |
|
| 11 |
/**
|
| 12 |
* @var String
|
app/code/community/PriceWaiter/NYPWidget/Exception/NoTestDeals.php
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
/**
|
| 4 |
+
* Exception thrown on any attempt to create a "test" deal.
|
| 5 |
+
*/
|
| 6 |
+
class PriceWaiter_NYPWidget_Exception_NoTestDeals
|
| 7 |
+
extends PriceWaiter_NYPWidget_Exception_Abstract
|
| 8 |
+
{
|
| 9 |
+
public $errorCode = 'no_test_deals';
|
| 10 |
+
}
|
app/code/community/PriceWaiter/NYPWidget/Exception/OrderNotFound.php
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
/**
|
| 4 |
+
* Exception thrown when an attempt is made to look up an order that
|
| 5 |
+
* does not exist.
|
| 6 |
+
*/
|
| 7 |
+
class PriceWaiter_NYPWidget_Exception_OrderNotFound
|
| 8 |
+
extends PriceWaiter_NYPWidget_Exception_Abstract
|
| 9 |
+
{
|
| 10 |
+
public $errorCode = 'order_not_found';
|
| 11 |
+
}
|
app/code/community/PriceWaiter/NYPWidget/Exception/OutOfStock.php
DELETED
|
@@ -1,10 +0,0 @@
|
|
| 1 |
-
<?php
|
| 2 |
-
|
| 3 |
-
/**
|
| 4 |
-
* Exception thrown when there's not enough inventory of an item.
|
| 5 |
-
*/
|
| 6 |
-
class PriceWaiter_NYPWidget_Exception_OutOfStock
|
| 7 |
-
extends PriceWaiter_NYPWidget_Exception_Abstract
|
| 8 |
-
{
|
| 9 |
-
public $errorCode = 'out_of_stock';
|
| 10 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/code/community/PriceWaiter/NYPWidget/Exception/Product/Abstract.php
CHANGED
|
@@ -6,4 +6,5 @@
|
|
| 6 |
abstract class PriceWaiter_NYPWidget_Exception_Product_Abstract
|
| 7 |
extends PriceWaiter_NYPWidget_Exception_Abstract
|
| 8 |
{
|
|
|
|
| 9 |
}
|
| 6 |
abstract class PriceWaiter_NYPWidget_Exception_Product_Abstract
|
| 7 |
extends PriceWaiter_NYPWidget_Exception_Abstract
|
| 8 |
{
|
| 9 |
+
public $errorCode = 'invalid_product';
|
| 10 |
}
|
app/code/community/PriceWaiter/NYPWidget/Exception/Product/Invalid.php
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
/**
|
| 4 |
+
* Exception thrown when an attempt is made to resolve an invalid product.
|
| 5 |
+
*/
|
| 6 |
+
class PriceWaiter_NYPWidget_Exception_Product_Invalid
|
| 7 |
+
extends PriceWaiter_NYPWidget_Exception_Product_Abstract
|
| 8 |
+
{
|
| 9 |
+
const DEFAULT_MESSAGE = 'Invalid product data received.';
|
| 10 |
+
|
| 11 |
+
public function __construct($message = self::DEFAULT_MESSAGE)
|
| 12 |
+
{
|
| 13 |
+
parent::__construct($message);
|
| 14 |
+
}
|
| 15 |
+
}
|
app/code/community/PriceWaiter/NYPWidget/Exception/Product/NotFound.php
CHANGED
|
@@ -1,10 +1,9 @@
|
|
| 1 |
<?php
|
| 2 |
|
| 3 |
/**
|
| 4 |
-
* Exception thrown when a product cannot be found.
|
| 5 |
*/
|
| 6 |
class PriceWaiter_NYPWidget_Exception_Product_NotFound
|
| 7 |
extends PriceWaiter_NYPWidget_Exception_Product_Abstract
|
| 8 |
{
|
| 9 |
-
public $errorCode = 'product_not_found';
|
| 10 |
}
|
| 1 |
<?php
|
| 2 |
|
| 3 |
/**
|
| 4 |
+
* Exception thrown when a product cannot be found by id.
|
| 5 |
*/
|
| 6 |
class PriceWaiter_NYPWidget_Exception_Product_NotFound
|
| 7 |
extends PriceWaiter_NYPWidget_Exception_Product_Abstract
|
| 8 |
{
|
|
|
|
| 9 |
}
|
app/code/community/PriceWaiter/NYPWidget/Exception/Product/OutOfStock.php
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
/**
|
| 4 |
+
* Exception thrown when there's not enough inventory of an product.
|
| 5 |
+
*/
|
| 6 |
+
class PriceWaiter_NYPWidget_Exception_Product_OutOfStock
|
| 7 |
+
extends PriceWaiter_NYPWidget_Exception_Product_Abstract
|
| 8 |
+
{
|
| 9 |
+
public $errorCode = 'out_of_stock';
|
| 10 |
+
}
|
app/code/community/PriceWaiter/NYPWidget/Exception/Signature.php
CHANGED
|
@@ -7,5 +7,5 @@
|
|
| 7 |
class PriceWaiter_NYPWidget_Exception_Signature
|
| 8 |
extends PriceWaiter_NYPWidget_Exception_Abstract
|
| 9 |
{
|
| 10 |
-
public $errorCode = '
|
| 11 |
}
|
| 7 |
class PriceWaiter_NYPWidget_Exception_Signature
|
| 8 |
extends PriceWaiter_NYPWidget_Exception_Abstract
|
| 9 |
{
|
| 10 |
+
public $errorCode = 'invalid_signature';
|
| 11 |
}
|
app/code/community/PriceWaiter/NYPWidget/Exception/SingleItemOnly.php
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
/**
|
| 4 |
+
* Exception thrown to indicate this installation is only capable working with single-item deals.
|
| 5 |
+
*/
|
| 6 |
+
class PriceWaiter_NYPWidget_Exception_SingleItemOnly
|
| 7 |
+
extends PriceWaiter_NYPWidget_Exception_Abstract
|
| 8 |
+
{
|
| 9 |
+
public $errorCode = 'single_item_only';
|
| 10 |
+
}
|
app/code/community/PriceWaiter/NYPWidget/Exception/Version.php
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
/**
|
| 4 |
+
* Exception thrown when an incoming request is of a version that we
|
| 5 |
+
* don't support.
|
| 6 |
+
*/
|
| 7 |
+
class PriceWaiter_NYPWidget_Exception_Version
|
| 8 |
+
extends PriceWaiter_NYPWidget_Exception_Abstract
|
| 9 |
+
{
|
| 10 |
+
public $errorCode = 'invalid_version';
|
| 11 |
+
|
| 12 |
+
/**
|
| 13 |
+
* Versions that we *do* support.
|
| 14 |
+
* @var Array
|
| 15 |
+
*/
|
| 16 |
+
public $supportedVersions;
|
| 17 |
+
|
| 18 |
+
public function __construct(Array $supportedVersions)
|
| 19 |
+
{
|
| 20 |
+
$this->supportedVersions = $supportedVersions;
|
| 21 |
+
|
| 22 |
+
parent::__construct("Invalid version.");
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
public function jsonSerialize()
|
| 26 |
+
{
|
| 27 |
+
$json = parent::jsonSerialize();
|
| 28 |
+
$json['data'] = array(
|
| 29 |
+
'supportedVersions' => $this->supportedVersions,
|
| 30 |
+
);
|
| 31 |
+
|
| 32 |
+
return $json;
|
| 33 |
+
}
|
| 34 |
+
}
|
app/code/community/PriceWaiter/NYPWidget/Helper/About.php
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
/**
|
| 4 |
+
* Helper that provides metadata about the operating environment.
|
| 5 |
+
*/
|
| 6 |
+
class PriceWaiter_NYPWidget_Helper_About extends Mage_Core_Helper_Abstract
|
| 7 |
+
{
|
| 8 |
+
/**
|
| 9 |
+
* HTTP header on response that tells PriceWaiter the platform version.
|
| 10 |
+
*/
|
| 11 |
+
const EXTENSION_VERSION_HEADER = 'X-PriceWaiter-Extension-Version';
|
| 12 |
+
|
| 13 |
+
/**
|
| 14 |
+
* HTTP header on response that tells PriceWaiter the platform name.
|
| 15 |
+
*/
|
| 16 |
+
const PLATFORM_HEADER = 'X-PriceWaiter-Platform';
|
| 17 |
+
|
| 18 |
+
/**
|
| 19 |
+
* HTTP header on response that tells PriceWaiter the platform version.
|
| 20 |
+
*/
|
| 21 |
+
const PLATFORM_VERSION_HEADER = 'X-PriceWaiter-Platform-Version';
|
| 22 |
+
|
| 23 |
+
/**
|
| 24 |
+
* @return String PriceWaiter extension version.
|
| 25 |
+
*/
|
| 26 |
+
public function getExtensionVersion()
|
| 27 |
+
{
|
| 28 |
+
try
|
| 29 |
+
{
|
| 30 |
+
return (string)Mage::getConfig()->getNode()->modules->PriceWaiter_NYPWidget->version;
|
| 31 |
+
}
|
| 32 |
+
catch (Exception $ex)
|
| 33 |
+
{
|
| 34 |
+
return 'unknown';
|
| 35 |
+
}
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
/**
|
| 39 |
+
* @return String Platform identification string.
|
| 40 |
+
*/
|
| 41 |
+
public function getPlatform()
|
| 42 |
+
{
|
| 43 |
+
return 'Magento ' . Mage::getEdition();
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
/**
|
| 47 |
+
* @return String Magento version.
|
| 48 |
+
*/
|
| 49 |
+
public function getPlatformVersion()
|
| 50 |
+
{
|
| 51 |
+
return Mage::getVersion();
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
/**
|
| 55 |
+
* @internal Adds "about" response headers.
|
| 56 |
+
* @param Zend_Controller_Response_Http $httpResponse
|
| 57 |
+
*/
|
| 58 |
+
public function setResponseHeaders(Zend_Controller_Response_Http $httpResponse)
|
| 59 |
+
{
|
| 60 |
+
$httpResponse->setHeader(self::PLATFORM_HEADER, $this->getPlatform(), true);
|
| 61 |
+
$httpResponse->setHeader(self::PLATFORM_VERSION_HEADER, $this->getPlatformVersion(), true);
|
| 62 |
+
$httpResponse->setHeader(self::EXTENSION_VERSION_HEADER, $this->getExtensionVersion(), true);
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
}
|
app/code/community/PriceWaiter/NYPWidget/Helper/Data.php
CHANGED
|
@@ -4,21 +4,17 @@ class PriceWaiter_NYPWidget_Helper_Data extends Mage_Core_Helper_Abstract
|
|
| 4 |
{
|
| 5 |
const PRICEWAITER_API_URL = 'https://api.pricewaiter.com';
|
| 6 |
const PRICEWAITER_RETAILER_URL = 'https://retailer.pricewaiter.com';
|
|
|
|
| 7 |
|
|
|
|
|
|
|
|
|
|
| 8 |
const XML_PATH_DEFAULT_ORDER_STATUS = 'pricewaiter/orders/default_status';
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
private $_widgetUrl = 'https://widget.pricewaiter.com';
|
| 15 |
-
|
| 16 |
-
public function __construct()
|
| 17 |
-
{
|
| 18 |
-
if (!!getenv('PRICEWAITER_WIDGET_URL')) {
|
| 19 |
-
$this->_widgetUrl = getenv('PRICEWAITER_WIDGET_URL');
|
| 20 |
-
}
|
| 21 |
-
}
|
| 22 |
|
| 23 |
/**
|
| 24 |
* @return String URL of the PriceWaiter API.
|
|
@@ -74,128 +70,13 @@ class PriceWaiter_NYPWidget_Helper_Data extends Mage_Core_Helper_Abstract
|
|
| 74 |
}
|
| 75 |
|
| 76 |
/**
|
| 77 |
-
* @
|
|
|
|
| 78 |
*/
|
| 79 |
-
public function
|
| 80 |
-
{
|
| 81 |
-
$url = getenv('PRICEWAITER_RETAILER_URL');
|
| 82 |
-
|
| 83 |
-
if ($url) {
|
| 84 |
-
return $url;
|
| 85 |
-
}
|
| 86 |
-
|
| 87 |
-
return self::PRICEWAITER_RETAILER_URL;
|
| 88 |
-
}
|
| 89 |
-
|
| 90 |
-
public function isEnabledForStore()
|
| 91 |
-
{
|
| 92 |
-
// Is the pricewaiter widget enabled for this store and an API Key has been set.
|
| 93 |
-
if (Mage::getStoreConfig('pricewaiter/configuration/enabled')
|
| 94 |
-
&& Mage::getStoreConfig('pricewaiter/configuration/api_key')
|
| 95 |
-
) {
|
| 96 |
-
return true;
|
| 97 |
-
}
|
| 98 |
-
|
| 99 |
-
return false;
|
| 100 |
-
}
|
| 101 |
-
|
| 102 |
-
// Set the values of $_buttonEnabled and $_conversionToolsEnabled
|
| 103 |
-
private function _setEnabledStatus()
|
| 104 |
-
{
|
| 105 |
-
if ($this->_buttonEnabled != null && $this->_conversionToolsEnabled != null) {
|
| 106 |
-
return true;
|
| 107 |
-
}
|
| 108 |
-
|
| 109 |
-
if (Mage::getStoreConfig('pricewaiter/configuration/enabled')) {
|
| 110 |
-
$this->_buttonEnabled = true;
|
| 111 |
-
}
|
| 112 |
-
|
| 113 |
-
if (Mage::getStoreConfig('pricewaiter/conversion_tools/enabled')) {
|
| 114 |
-
$this->_conversionToolsEnabled = true;
|
| 115 |
-
}
|
| 116 |
-
|
| 117 |
-
$product = $this->_getProduct();
|
| 118 |
-
|
| 119 |
-
// Is the PriceWaiter widget enabled for this category
|
| 120 |
-
$category = Mage::registry('current_category');
|
| 121 |
-
if (is_object($category)) {
|
| 122 |
-
$nypcategory = Mage::getModel('nypwidget/category')->loadByCategory($category);
|
| 123 |
-
if (!$nypcategory->isActive()) {
|
| 124 |
-
$this->_buttonEnabled = false;
|
| 125 |
-
}
|
| 126 |
-
if (!$nypcategory->isConversionToolsEnabled()) {
|
| 127 |
-
$this->_conversionToolsEnabled = false;
|
| 128 |
-
}
|
| 129 |
-
} else {
|
| 130 |
-
// We end up here if we are visiting the product page without being
|
| 131 |
-
// "in a category". Basically, we arrived via a search page.
|
| 132 |
-
// The logic here checks to see if there are any categories that this
|
| 133 |
-
// product belongs to that enable the PriceWaiter widget. If not, return false.
|
| 134 |
-
$categories = $product->getCategoryIds();
|
| 135 |
-
$categoryActive = false;
|
| 136 |
-
$categoryCTActive = false;
|
| 137 |
-
foreach ($categories as $categoryId) {
|
| 138 |
-
unset($currentCategory);
|
| 139 |
-
unset($nypcategory);
|
| 140 |
-
$currentCategory = Mage::getModel('catalog/category')->load($categoryId);
|
| 141 |
-
$nypcategory = Mage::getModel('nypwidget/category')->loadByCategory($currentCategory);
|
| 142 |
-
if ($nypcategory->isActive()) {
|
| 143 |
-
if ($nypcategory->isConversionToolsEnabled()) {
|
| 144 |
-
$categoryCTActive = true;
|
| 145 |
-
}
|
| 146 |
-
$categoryActive = true;
|
| 147 |
-
break;
|
| 148 |
-
}
|
| 149 |
-
}
|
| 150 |
-
if (!$categoryActive) {
|
| 151 |
-
$this->_buttonEnabled = false;
|
| 152 |
-
}
|
| 153 |
-
|
| 154 |
-
if (!$categoryCTActive) {
|
| 155 |
-
$this->_conversionToolsEnabled = false;
|
| 156 |
-
}
|
| 157 |
-
|
| 158 |
-
}
|
| 159 |
-
|
| 160 |
-
// Is PriceWaiter enabled for this Customer Group
|
| 161 |
-
$disable = Mage::getStoreConfig('pricewaiter/customer_groups/disable');
|
| 162 |
-
if ($disable) {
|
| 163 |
-
// An admin has chosen to disable the PriceWaiter widget by customer group.
|
| 164 |
-
$customerGroupId = Mage::getSingleton('customer/session')->getCustomerGroupId();
|
| 165 |
-
$customerGroups = Mage::getStoreConfig('pricewaiter/customer_groups/group_select');
|
| 166 |
-
$customerGroups = preg_split('/,/', $customerGroups);
|
| 167 |
-
|
| 168 |
-
if (in_array($customerGroupId, $customerGroups)) {
|
| 169 |
-
$this->_buttonEnabled = false;
|
| 170 |
-
}
|
| 171 |
-
}
|
| 172 |
-
|
| 173 |
-
// Are Conversion Tools enabled for this Customer Group
|
| 174 |
-
$disableCT = Mage::getStoreConfig('pricewaiter/conversion_tools/customer_group_disable');
|
| 175 |
-
if ($disableCT) {
|
| 176 |
-
// An admin has chosen to disable the Conversion Tools by customer group.
|
| 177 |
-
$customerGroupId = Mage::getSingleton('customer/session')->getCustomerGroupId();
|
| 178 |
-
$customerGroups = Mage::getStoreConfig('pricewaiter/conversion_tools/group_select');
|
| 179 |
-
$customerGroups = preg_split('/,/', $customerGroups);
|
| 180 |
-
|
| 181 |
-
if (in_array($customerGroupId, $customerGroups)) {
|
| 182 |
-
$this->_conversionToolsEnabled = false;
|
| 183 |
-
}
|
| 184 |
-
}
|
| 185 |
-
}
|
| 186 |
-
|
| 187 |
-
public function isConversionToolsEnabled()
|
| 188 |
-
{
|
| 189 |
-
$this->_setEnabledStatus();
|
| 190 |
-
|
| 191 |
-
return $this->_conversionToolsEnabled;
|
| 192 |
-
}
|
| 193 |
-
|
| 194 |
-
public function isButtonEnabled()
|
| 195 |
{
|
| 196 |
-
$
|
| 197 |
-
|
| 198 |
-
return $this->_buttonEnabled;
|
| 199 |
}
|
| 200 |
|
| 201 |
public function getPriceWaiterSettingsUrl()
|
|
@@ -203,102 +84,35 @@ class PriceWaiter_NYPWidget_Helper_Data extends Mage_Core_Helper_Abstract
|
|
| 203 |
return $this->getRetailerUrl();
|
| 204 |
}
|
| 205 |
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
. Mage::getStoreConfig('pricewaiter/configuration/api_key')
|
| 211 |
-
. ".js";
|
| 212 |
-
}
|
| 213 |
-
|
| 214 |
-
return $this->_widgetUrl . '/nyp/script/widget.js';
|
| 215 |
-
}
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
public function getProductPrice($product)
|
| 219 |
{
|
| 220 |
-
$
|
| 221 |
-
|
| 222 |
-
if ($product->getId()) {
|
| 223 |
-
if ($product->getTypeId() != 'grouped') {
|
| 224 |
-
$productPrice = $product->getFinalPrice();
|
| 225 |
-
}
|
| 226 |
-
}
|
| 227 |
-
|
| 228 |
-
return $productPrice;
|
| 229 |
-
}
|
| 230 |
-
|
| 231 |
-
private function safeGetAttributeText($product, $code) {
|
| 232 |
-
$value = $product->getData($code);
|
| 233 |
-
|
| 234 |
-
// prevent Magento from rendering "No" when nothing is selected.
|
| 235 |
-
if (!$value) {
|
| 236 |
-
return false;
|
| 237 |
-
}
|
| 238 |
-
|
| 239 |
-
$resource = $product->getResource();
|
| 240 |
-
if (!$resource) {
|
| 241 |
-
return false;
|
| 242 |
-
}
|
| 243 |
-
|
| 244 |
-
$attr = $resource->getAttribute($code);
|
| 245 |
-
if (!$attr) {
|
| 246 |
-
return false;
|
| 247 |
-
}
|
| 248 |
-
|
| 249 |
-
$frontend = $attr->getFrontend();
|
| 250 |
-
if (!$frontend) {
|
| 251 |
-
return false;
|
| 252 |
-
}
|
| 253 |
-
|
| 254 |
-
return $frontend->getValue($product);
|
| 255 |
-
}
|
| 256 |
-
|
| 257 |
-
public function getProductBrand($product) {
|
| 258 |
-
|
| 259 |
-
// prefer brand, but fallback to manufacturer attribute
|
| 260 |
-
$brand = $product->getData('brand');
|
| 261 |
-
|
| 262 |
-
if (!$brand) {
|
| 263 |
-
$manufacturer = $this->safeGetAttributeText($product, 'manufacturer');
|
| 264 |
-
if ($manufacturer) {
|
| 265 |
-
$brand = $manufacturer;
|
| 266 |
-
}
|
| 267 |
-
}
|
| 268 |
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
$manufacturer = $this->safeGetAttributeText($product, 'c2c_brand');
|
| 272 |
-
if ($manufacturer) {
|
| 273 |
-
$brand = $manufacturer;
|
| 274 |
-
}
|
| 275 |
}
|
| 276 |
|
| 277 |
-
return
|
| 278 |
}
|
| 279 |
|
| 280 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 281 |
{
|
| 282 |
-
|
| 283 |
-
$this->_product = Mage::registry('current_product');
|
| 284 |
-
}
|
| 285 |
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
{
|
| 291 |
-
$product = $this->_getProduct();
|
| 292 |
-
$javascript = "var PriceWaiterGroupedProductInfo = new Array();\n";
|
| 293 |
-
|
| 294 |
-
$associatedProducts = $product->getTypeInstance(true)->getAssociatedProducts($product);
|
| 295 |
-
foreach ($associatedProducts as $simpleProduct) {
|
| 296 |
-
$javascript .= "PriceWaiterGroupedProductInfo[" . $simpleProduct->getId() . "] = ";
|
| 297 |
-
$javascript .= "new Array('" . htmlentities($simpleProduct->getName()) . "', '"
|
| 298 |
-
. number_format($simpleProduct->getPrice(), 2) . "')\n";
|
| 299 |
}
|
| 300 |
|
| 301 |
-
return $
|
| 302 |
}
|
| 303 |
|
| 304 |
/**
|
|
@@ -312,9 +126,8 @@ class PriceWaiter_NYPWidget_Helper_Data extends Mage_Core_Helper_Abstract
|
|
| 312 |
$stores = Mage::app()->getStores();
|
| 313 |
|
| 314 |
foreach ($stores as $store) {
|
| 315 |
-
|
| 316 |
$storeApiKey = Mage::getStoreConfig(
|
| 317 |
-
|
| 318 |
$store->getId()
|
| 319 |
);
|
| 320 |
|
|
@@ -327,189 +140,188 @@ class PriceWaiter_NYPWidget_Helper_Data extends Mage_Core_Helper_Abstract
|
|
| 327 |
}
|
| 328 |
|
| 329 |
/**
|
| 330 |
-
*
|
| 331 |
-
* @return
|
| 332 |
*/
|
| 333 |
-
public function
|
| 334 |
{
|
| 335 |
-
$
|
| 336 |
|
| 337 |
-
if (
|
| 338 |
-
|
| 339 |
-
|
| 340 |
|
| 341 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 342 |
}
|
| 343 |
|
| 344 |
-
|
|
|
|
| 345 |
}
|
| 346 |
|
| 347 |
/**
|
| 348 |
-
*
|
| 349 |
-
* @param
|
| 350 |
-
* @return
|
| 351 |
*/
|
| 352 |
-
public function
|
| 353 |
{
|
| 354 |
-
|
| 355 |
-
|
|
|
|
|
|
|
|
|
|
| 356 |
}
|
| 357 |
|
| 358 |
/**
|
| 359 |
-
*
|
| 360 |
-
* @param
|
| 361 |
-
* @
|
| 362 |
-
* @return {Boolean} Wehther the request actually came from PriceWaiter.
|
| 363 |
*/
|
| 364 |
-
public function
|
| 365 |
{
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 369 |
|
| 370 |
-
$
|
|
|
|
| 371 |
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 377 |
|
| 378 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 379 |
}
|
| 380 |
|
| 381 |
/**
|
| 382 |
-
*
|
| 383 |
-
* @
|
| 384 |
-
* @param {Array} $productOptions An array of options for the product, name => value
|
| 385 |
-
* @return {Object} Returns Mage_Catalog_Model_Product of product that matches options.
|
| 386 |
-
* @throws PriceWaiter_NYPWidget_Exception_Product_NotFound If no product can be found.
|
| 387 |
*/
|
| 388 |
-
public function
|
| 389 |
{
|
| 390 |
-
$
|
| 391 |
-
|
| 392 |
-
->addAttributeToSelect('*')
|
| 393 |
-
->getFirstItem();
|
| 394 |
-
|
| 395 |
-
$additionalCost = null;
|
| 396 |
-
|
| 397 |
-
if ($product->getTypeId() == 'configurable') {
|
| 398 |
-
// Do configurable product specific stuff
|
| 399 |
-
$attrs = $product->getTypeInstance(true)->getConfigurableAttributesAsArray($product);
|
| 400 |
-
|
| 401 |
-
// Find our product based on attributes
|
| 402 |
-
foreach ($attrs as $attr) {
|
| 403 |
-
if (array_key_exists($attr['label'], $productOptions)) {
|
| 404 |
-
foreach ($attr['values'] as $value) {
|
| 405 |
-
if ($value['label'] == $productOptions[$attr['label']]) {
|
| 406 |
-
$valueIndex = $value['value_index'];
|
| 407 |
-
// If this attribute has a price assosciated with it, add it to the price later
|
| 408 |
-
if ($value['pricing_value'] != '') {
|
| 409 |
-
$additionalCost += $value['pricing_value'];
|
| 410 |
-
}
|
| 411 |
-
break;
|
| 412 |
-
}
|
| 413 |
-
}
|
| 414 |
-
unset($productOptions[$attr['label']]);
|
| 415 |
-
$productOptions[$attr['attribute_id']] = $valueIndex;
|
| 416 |
-
}
|
| 417 |
-
}
|
| 418 |
|
| 419 |
-
|
| 420 |
-
|
| 421 |
|
| 422 |
-
|
| 423 |
-
|
| 424 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 425 |
|
| 426 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 427 |
}
|
| 428 |
|
| 429 |
-
|
| 430 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 431 |
}
|
| 432 |
|
| 433 |
-
|
| 434 |
-
}
|
| 435 |
|
| 436 |
-
|
| 437 |
-
|
| 438 |
-
|
| 439 |
-
|
| 440 |
-
|
| 441 |
-
|
| 442 |
-
$quantities[] = $associatedProduct->getStockItem()->getQty();
|
| 443 |
}
|
| 444 |
|
| 445 |
-
return
|
| 446 |
}
|
| 447 |
|
| 448 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 449 |
{
|
| 450 |
-
$
|
| 451 |
-
$finalPrice = 0;
|
| 452 |
-
foreach ($associatedProductIds as $associatedProductId) {
|
| 453 |
-
$associatedProduct = Mage::getModel('catalog/product')->load($associatedProductId);
|
| 454 |
-
$finalPrice += ($associatedProduct->getFinalPrice() * $productConfiguration['super_group'][$associatedProductId]);
|
| 455 |
-
}
|
| 456 |
-
return $finalPrice;
|
| 457 |
-
}
|
| 458 |
|
| 459 |
-
|
| 460 |
-
|
| 461 |
-
|
| 462 |
-
$costs = array();
|
| 463 |
-
foreach ($associatedProductIds as $associatedProductId) {
|
| 464 |
-
$associatedProduct = Mage::getModel('catalog/product')->load($associatedProductId);
|
| 465 |
-
$costs[] = $associatedProduct->getData('cost');
|
| 466 |
}
|
| 467 |
|
| 468 |
-
|
| 469 |
-
|
|
|
|
| 470 |
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
$magentoEdition = 'Magento ' . Mage::getEdition();
|
| 474 |
-
$magentoVersion = Mage::getVersion();
|
| 475 |
-
$extensionVersion = Mage::getConfig()->getNode()->modules->PriceWaiter_NYPWidget->version;
|
| 476 |
-
Mage::app()->getResponse()->setHeader('X-Platform', $magentoEdition, true);
|
| 477 |
-
Mage::app()->getResponse()->setHeader('X-Platform-Version', $magentoVersion, true);
|
| 478 |
-
Mage::app()->getResponse()->setHeader('X-Platform-Extension-Version', $extensionVersion, true);
|
| 479 |
-
|
| 480 |
-
return true;
|
| 481 |
-
}
|
| 482 |
|
| 483 |
-
|
| 484 |
-
{
|
| 485 |
-
$categorization = array();
|
| 486 |
-
$assignedCategories = $product->getCategoryCollection()
|
| 487 |
-
->addAttributeToSelect('name');
|
| 488 |
-
|
| 489 |
-
$baseUrl = Mage::app()->getStore()->getBaseUrl();
|
| 490 |
-
|
| 491 |
-
// Find the path (parents) of each category, and add their information
|
| 492 |
-
// to the categorization array
|
| 493 |
-
foreach ($assignedCategories as $assignedCategory) {
|
| 494 |
-
$parentCategories = array();
|
| 495 |
-
$path = $assignedCategory->getPath();
|
| 496 |
-
$parentIds = explode('/', $path);
|
| 497 |
-
array_shift($parentIds); // We don't care about the root category
|
| 498 |
-
|
| 499 |
-
$categoryModel = Mage::getModel('catalog/category');
|
| 500 |
-
foreach($parentIds as $parentCategoryId) {
|
| 501 |
-
$parentCategory = $categoryModel->load($parentCategoryId);
|
| 502 |
-
$parentCategoryUrl = preg_replace('/^\//', '', $parentCategory->getUrlPath());
|
| 503 |
-
|
| 504 |
-
$parentCategories[] = array(
|
| 505 |
-
'name' => $parentCategory->getName(),
|
| 506 |
-
'url' => $baseUrl . '/' . $parentCategoryUrl
|
| 507 |
-
);
|
| 508 |
-
}
|
| 509 |
|
| 510 |
-
|
|
|
|
| 511 |
}
|
| 512 |
|
| 513 |
-
return
|
| 514 |
}
|
| 515 |
}
|
| 4 |
{
|
| 5 |
const PRICEWAITER_API_URL = 'https://api.pricewaiter.com';
|
| 6 |
const PRICEWAITER_RETAILER_URL = 'https://retailer.pricewaiter.com';
|
| 7 |
+
const PRICEWAITER_WIDGET_URL = 'https://widget.pricewaiter.com';
|
| 8 |
|
| 9 |
+
const XML_PATH_API_KEY = 'pricewaiter/configuration/api_key';
|
| 10 |
+
const XML_PATH_BUTTON_ENABLED = 'pricewaiter/configuration/enabled';
|
| 11 |
+
const XML_PATH_CONVERSION_TOOLS_ENABLED = 'pricewaiter/conversion_tools/enabled';
|
| 12 |
const XML_PATH_DEFAULT_ORDER_STATUS = 'pricewaiter/orders/default_status';
|
| 13 |
+
const XML_PATH_DISABLE_BY_CUSTOMER_GROUP = 'pricewaiter/customer_groups/disable';
|
| 14 |
+
const XML_PATH_BUTTON_DISABLED_CUSTOMER_GROUPS = 'pricewaiter/customer_groups/group_select';
|
| 15 |
+
const XML_PATH_CONVERSION_TOOLS_DISABLED_CUSTOMER_GROUPS = 'pricewaiter/conversion_tools/customer_group_disable';
|
| 16 |
+
const XML_PATH_DISABLED_BY_CATEGORY = 'pricewaiter/categories/disable_by_category';
|
| 17 |
+
const XML_PATH_SECRET = 'pricewaiter/configuration/api_secret';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
|
| 19 |
/**
|
| 20 |
* @return String URL of the PriceWaiter API.
|
| 70 |
}
|
| 71 |
|
| 72 |
/**
|
| 73 |
+
* @param Mage_Core_Model_Store|number|null $store
|
| 74 |
+
* @return String|false API key or false if not configured.
|
| 75 |
*/
|
| 76 |
+
public function getPriceWaiterApiKey($store = null)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
{
|
| 78 |
+
$apiKey = Mage::getStoreConfig(self::XML_PATH_API_KEY, $store);
|
| 79 |
+
return $apiKey ? (string)$apiKey : false;
|
|
|
|
| 80 |
}
|
| 81 |
|
| 82 |
public function getPriceWaiterSettingsUrl()
|
| 84 |
return $this->getRetailerUrl();
|
| 85 |
}
|
| 86 |
|
| 87 |
+
/**
|
| 88 |
+
* @return String The URL of the PriceWaiter Retailer area.
|
| 89 |
+
*/
|
| 90 |
+
public function getRetailerUrl()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
{
|
| 92 |
+
$url = getenv('PRICEWAITER_RETAILER_URL');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
|
| 94 |
+
if ($url) {
|
| 95 |
+
return $url;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
}
|
| 97 |
|
| 98 |
+
return self::PRICEWAITER_RETAILER_URL;
|
| 99 |
}
|
| 100 |
|
| 101 |
+
/**
|
| 102 |
+
* Returns the secret token used when communicating with PriceWaiter.
|
| 103 |
+
* @return {String} Secret token
|
| 104 |
+
*/
|
| 105 |
+
public function getSecret($store = null)
|
| 106 |
{
|
| 107 |
+
$token = Mage::getStoreConfig(self::XML_PATH_SECRET, $store);
|
|
|
|
|
|
|
| 108 |
|
| 109 |
+
if (is_null($token) || $token == '') {
|
| 110 |
+
$token = bin2hex(openssl_random_pseudo_bytes(24));
|
| 111 |
+
$config = Mage::getModel('core/config');
|
| 112 |
+
$config->saveConfig(self::XML_PATH_SECRET, $token);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
}
|
| 114 |
|
| 115 |
+
return $token;
|
| 116 |
}
|
| 117 |
|
| 118 |
/**
|
| 126 |
$stores = Mage::app()->getStores();
|
| 127 |
|
| 128 |
foreach ($stores as $store) {
|
|
|
|
| 129 |
$storeApiKey = Mage::getStoreConfig(
|
| 130 |
+
self::XML_PATH_API_KEY,
|
| 131 |
$store->getId()
|
| 132 |
);
|
| 133 |
|
| 140 |
}
|
| 141 |
|
| 142 |
/**
|
| 143 |
+
* @param Mage_Core_Model_Store|number|null $store
|
| 144 |
+
* @return String|false The widget.js URL, or false if not available.
|
| 145 |
*/
|
| 146 |
+
public function getWidgetJsUrl($store = null)
|
| 147 |
{
|
| 148 |
+
$apiKey = $this->getPriceWaiterApiKey($store);
|
| 149 |
|
| 150 |
+
if (!$apiKey) {
|
| 151 |
+
return false;
|
| 152 |
+
}
|
| 153 |
|
| 154 |
+
// Allow overriding widget js url via ENV
|
| 155 |
+
$url = getenv('PRICEWAITER_WIDGET_URL');
|
| 156 |
+
|
| 157 |
+
if (!$url) {
|
| 158 |
+
$url = self::PRICEWAITER_WIDGET_URL;
|
| 159 |
}
|
| 160 |
|
| 161 |
+
$apiKey = rawurlencode($apiKey);
|
| 162 |
+
return "{$url}/script/$apiKey.js";
|
| 163 |
}
|
| 164 |
|
| 165 |
/**
|
| 166 |
+
* @param array $categories An array of categories (or category ids).
|
| 167 |
+
* @param Mage_Core_Model_Store|number|null $store
|
| 168 |
+
* @return boolean Whether the button is enabled for *at least one* of the given categories.
|
| 169 |
*/
|
| 170 |
+
public function isButtonEnabledForAnyCategory(array $categories, $store = null)
|
| 171 |
{
|
| 172 |
+
return $this->isFeatureEnabledForAnyCategory(
|
| 173 |
+
$categories,
|
| 174 |
+
$store,
|
| 175 |
+
'isActive' // e.g. $nypcategory->isActive()
|
| 176 |
+
);
|
| 177 |
}
|
| 178 |
|
| 179 |
/**
|
| 180 |
+
* @param $customerGroup
|
| 181 |
+
* @param Mage_Core_Model_Store|number|null $store
|
| 182 |
+
* @return boolean
|
|
|
|
| 183 |
*/
|
| 184 |
+
public function isButtonEnabledForCustomerGroup($customerGroup, $store = null)
|
| 185 |
{
|
| 186 |
+
return $this->isFeatureEnabledForCustomerGroup(
|
| 187 |
+
$customerGroup,
|
| 188 |
+
$store,
|
| 189 |
+
self::XML_PATH_BUTTON_DISABLED_CUSTOMER_GROUPS
|
| 190 |
+
);
|
| 191 |
+
}
|
| 192 |
+
/**
|
| 193 |
+
* @param Mage_Core_Model_Store|number|null $store
|
| 194 |
+
* @return boolean Whether the PW button is enabled for the given store.
|
| 195 |
+
*/
|
| 196 |
+
public function isButtonEnabledForStore($store = null)
|
| 197 |
+
{
|
| 198 |
+
$enabled = !!Mage::getStoreConfig(self::XML_PATH_BUTTON_ENABLED, $store);
|
| 199 |
+
$apiKey = Mage::getStoreConfig(self::XML_PATH_API_KEY, $store);
|
| 200 |
|
| 201 |
+
return $enabled && $apiKey;
|
| 202 |
+
}
|
| 203 |
|
| 204 |
+
/**
|
| 205 |
+
* @param $customerGroup
|
| 206 |
+
* @param Mage_Core_Model_Store|number|null $store
|
| 207 |
+
* @return boolean
|
| 208 |
+
*/
|
| 209 |
+
public function isConversionToolsEnabledForCustomerGroup($customerGroup, $store = null)
|
| 210 |
+
{
|
| 211 |
+
return $this->isFeatureEnabledForCustomerGroup(
|
| 212 |
+
$customerGroup,
|
| 213 |
+
$store,
|
| 214 |
+
self::XML_PATH_CONVERSION_TOOLS_DISABLED_CUSTOMER_GROUPS
|
| 215 |
+
);
|
| 216 |
+
}
|
| 217 |
|
| 218 |
+
/**
|
| 219 |
+
* @param array $categories An array of categories (or category ids).
|
| 220 |
+
* @param Mage_Core_Model_Store|number|null $store
|
| 221 |
+
* @return boolean Whether the conversion tools are enabled for *at least one* of the given categories.
|
| 222 |
+
*/
|
| 223 |
+
public function isConversionToolsEnabledForAnyCategory(array $categories, $store = null)
|
| 224 |
+
{
|
| 225 |
+
return $this->isFeatureEnabledForAnyCategory(
|
| 226 |
+
$categories,
|
| 227 |
+
$store,
|
| 228 |
+
'isConversionToolsEnabled' // e.g. $nypcategory->isConversionToolsEnabled()
|
| 229 |
+
);
|
| 230 |
}
|
| 231 |
|
| 232 |
/**
|
| 233 |
+
* @param Mage_Core_Model_Store|number|null $store
|
| 234 |
+
* @return boolean Whether the PW conversion tools are enabled for the given store.
|
|
|
|
|
|
|
|
|
|
| 235 |
*/
|
| 236 |
+
public function isConversionToolsEnabledForStore($store = null)
|
| 237 |
{
|
| 238 |
+
$enabled = !!Mage::getStoreConfig(self::XML_PATH_CONVERSION_TOOLS_ENABLED, $store);
|
| 239 |
+
$apiKey = Mage::getStoreConfig(self::XML_PATH_API_KEY, $store);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 240 |
|
| 241 |
+
return $enabled && $apiKey;
|
| 242 |
+
}
|
| 243 |
|
| 244 |
+
/**
|
| 245 |
+
* @param array $categories
|
| 246 |
+
* @param Mage_Core_Model_Store|number|null $store
|
| 247 |
+
* @param string $categoryGetter
|
| 248 |
+
* @return boolean
|
| 249 |
+
*/
|
| 250 |
+
protected function isFeatureEnabledForAnyCategory(
|
| 251 |
+
array $categories,
|
| 252 |
+
$store,
|
| 253 |
+
$categoryGetter
|
| 254 |
+
)
|
| 255 |
+
{
|
| 256 |
+
$store = Mage::app()->getStore($store);
|
| 257 |
|
| 258 |
+
// See if we're even doing "disable by category"
|
| 259 |
+
// This helps avoid recursive category lookups...
|
| 260 |
+
$areDisablingByCategory = Mage::getStoreConfig(self::XML_PATH_DISABLED_BY_CATEGORY, $store);
|
| 261 |
+
if (!$areDisablingByCategory) {
|
| 262 |
+
return true;
|
| 263 |
}
|
| 264 |
|
| 265 |
+
// Resolve the $categories array into a an actual
|
| 266 |
+
// array of Mage_Catalog_Model_Category instances
|
| 267 |
+
$resolvedCategories = array();
|
| 268 |
+
foreach ($categories as $cat) {
|
| 269 |
+
if (is_object($cat)) {
|
| 270 |
+
$resolvedCategories[] = $cat;
|
| 271 |
+
} else if (is_numeric($cat)) {
|
| 272 |
+
// Load category by id
|
| 273 |
+
$cat = Mage::getModel('catalog/category')->load($cat);
|
| 274 |
+
if ($cat->getId()) {
|
| 275 |
+
$resolvedCategories[] = $cat;
|
| 276 |
+
}
|
| 277 |
+
}
|
| 278 |
}
|
| 279 |
|
| 280 |
+
$enabled = false;
|
|
|
|
| 281 |
|
| 282 |
+
foreach($resolvedCategories as $category) {
|
| 283 |
+
// We store category config in a parallel model.
|
| 284 |
+
$nypcategory = Mage::getModel('nypwidget/category')->loadByCategory($category, $store->getId());
|
| 285 |
+
if ($nypcategory->$categoryGetter()) {
|
| 286 |
+
return true;
|
| 287 |
+
}
|
|
|
|
| 288 |
}
|
| 289 |
|
| 290 |
+
return false;
|
| 291 |
}
|
| 292 |
|
| 293 |
+
/**
|
| 294 |
+
* @param object|number $customerGroup
|
| 295 |
+
* @param Mage_Core_Model_Store|number|null $store
|
| 296 |
+
* @param string $xmlPath Path to the setting that holds the customer group ids.
|
| 297 |
+
* @return boolean
|
| 298 |
+
*/
|
| 299 |
+
protected function isFeatureEnabledForCustomerGroup(
|
| 300 |
+
$customerGroup,
|
| 301 |
+
$store,
|
| 302 |
+
$xmlPath
|
| 303 |
+
)
|
| 304 |
{
|
| 305 |
+
$anyDisabledByCustomerGroup = Mage::getStoreConfig(self::XML_PATH_DISABLE_BY_CUSTOMER_GROUP, $store);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 306 |
|
| 307 |
+
if (!$anyDisabledByCustomerGroup) {
|
| 308 |
+
// Not using "disable by customer group" feature
|
| 309 |
+
return true;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 310 |
}
|
| 311 |
|
| 312 |
+
$id = is_object($customerGroup) ?
|
| 313 |
+
$customerGroup->getId() :
|
| 314 |
+
$customerGroup;
|
| 315 |
|
| 316 |
+
$customerGroupIds = Mage::getStoreConfig($xmlPath, $store);
|
| 317 |
+
$customerGroupIds = explode(',', $customerGroupIds);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 318 |
|
| 319 |
+
// $customerGroupIds contains ids of groups for which feature is *disabled*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 320 |
|
| 321 |
+
if (in_array($id, $customerGroupIds)) {
|
| 322 |
+
return false;
|
| 323 |
}
|
| 324 |
|
| 325 |
+
return true;
|
| 326 |
}
|
| 327 |
}
|
app/code/community/PriceWaiter/NYPWidget/Helper/Product.php
DELETED
|
@@ -1,108 +0,0 @@
|
|
| 1 |
-
<?php
|
| 2 |
-
|
| 3 |
-
class PriceWaiter_NYPWidget_Helper_Product extends Mage_Core_Helper_Abstract
|
| 4 |
-
{
|
| 5 |
-
public function lookupData(Array $productConfiguration) {
|
| 6 |
-
$productInformation = array();
|
| 7 |
-
$productInformation['allow_pricewaiter'] = Mage::helper('nypwidget')->isEnabledForStore();
|
| 8 |
-
|
| 9 |
-
$cart = Mage::getModel('checkout/cart');
|
| 10 |
-
|
| 11 |
-
$product = Mage::getModel('catalog/product')
|
| 12 |
-
->setStoreId(Mage::app()->getStore()->getId())
|
| 13 |
-
->load($productConfiguration['product']);
|
| 14 |
-
|
| 15 |
-
// adding out-of-stock items to cart will fail
|
| 16 |
-
try {
|
| 17 |
-
$cart->addProduct($product, $productConfiguration);
|
| 18 |
-
$cart->save();
|
| 19 |
-
} catch (Mage_Core_Exception $e) {
|
| 20 |
-
$productInformation['inventory'] = 0;
|
| 21 |
-
$productInformation['can_backorder'] = false;
|
| 22 |
-
return $productInformation;
|
| 23 |
-
}
|
| 24 |
-
|
| 25 |
-
$cartItem = $cart->getQuote()->getAllItems();
|
| 26 |
-
if ($product->getTypeId() == Mage_Catalog_Model_Product_Type::TYPE_SIMPLE
|
| 27 |
-
|| $product->getTypeId() == Mage_Catalog_Model_Product_Type::TYPE_BUNDLE
|
| 28 |
-
|| $product->getTypeId() == Mage_Catalog_Model_Product_Type::TYPE_GROUPED
|
| 29 |
-
) {
|
| 30 |
-
$cartItem = $cartItem[0];
|
| 31 |
-
} else {
|
| 32 |
-
$cartItem = $cartItem[1];
|
| 33 |
-
}
|
| 34 |
-
|
| 35 |
-
if ($product->getTypeId() != Mage_Catalog_Model_Product_Type::TYPE_GROUPED) {
|
| 36 |
-
$product = Mage::getModel('catalog/product')->load($cartItem->getProduct()->getId());
|
| 37 |
-
}
|
| 38 |
-
|
| 39 |
-
$productFound = is_object($product) && $product->getId();
|
| 40 |
-
if (!$productFound) {
|
| 41 |
-
return false;
|
| 42 |
-
}
|
| 43 |
-
|
| 44 |
-
// Pull the product information from the cart item.
|
| 45 |
-
$productType = $product->getTypeId();
|
| 46 |
-
if ($productType == Mage_Catalog_Model_Product_Type::TYPE_SIMPLE
|
| 47 |
-
|| $productType == Mage_Catalog_Model_Product_Type::TYPE_CONFIGURABLE
|
| 48 |
-
) {
|
| 49 |
-
$qty = $product->getStockItem()->getQty();
|
| 50 |
-
$productFinalPrice = $product->getFinalPrice();
|
| 51 |
-
$productPrice = $product->getPrice();
|
| 52 |
-
$msrp = $product->getData('msrp');
|
| 53 |
-
$cost = $product->getData('cost');
|
| 54 |
-
} elseif ($productType == Mage_Catalog_Model_Product_Type::TYPE_GROUPED) {
|
| 55 |
-
$qty = Mage::helper('nypwidget')->getGroupedQuantity($productConfiguration);
|
| 56 |
-
$productFinalPrice = Mage::helper('nypwidget')->getGroupedFinalPrice($productConfiguration);
|
| 57 |
-
$productPrice = $productFinalPrice;
|
| 58 |
-
$msrp = false;
|
| 59 |
-
$cost = Mage::helper('nypwidget')->getGroupedCost($productConfiguration);
|
| 60 |
-
} else {
|
| 61 |
-
$qty = $cartItem->getProduct()->getStockItem()->getQty();
|
| 62 |
-
$productFinalPrice = $cartItem->getPrice();
|
| 63 |
-
$productPrice = $cartItem->getFinalPrice();
|
| 64 |
-
$msrp = $cartItem->getData('msrp');
|
| 65 |
-
$cost = $cartItem->getData('cost');
|
| 66 |
-
}
|
| 67 |
-
|
| 68 |
-
// Check for backorders set for the site
|
| 69 |
-
$backorder = false;
|
| 70 |
-
if ($product->getStockItem()->getUseConfigBackorders() &&
|
| 71 |
-
Mage::getStoreConfig('cataloginventory/item_options/backorders')
|
| 72 |
-
) {
|
| 73 |
-
$backorder = true;
|
| 74 |
-
} else if ($product->getStockItem()->getBackorders()) {
|
| 75 |
-
$backorder = true;
|
| 76 |
-
}
|
| 77 |
-
|
| 78 |
-
// If the product is returning a '0' quantity, but is "In Stock", set the "backorder" flag to true.
|
| 79 |
-
if ($product->getStockItem()->getIsInStock() == 1 && $qty == 0) {
|
| 80 |
-
$backorder = true;
|
| 81 |
-
}
|
| 82 |
-
|
| 83 |
-
$productInformation['inventory'] = (int)$qty;
|
| 84 |
-
$productInformation['can_backorder'] = $backorder;
|
| 85 |
-
|
| 86 |
-
$currency = Mage::app()->getStore()->getCurrentCurrencyCode();
|
| 87 |
-
|
| 88 |
-
if ($productFinalPrice != 0) {
|
| 89 |
-
$productInformation['retail_price'] = (string)$productFinalPrice;
|
| 90 |
-
$productInformation['retail_price_currency'] = $currency;
|
| 91 |
-
}
|
| 92 |
-
|
| 93 |
-
if ($msrp != '') {
|
| 94 |
-
$productInformation['regular_price'] = (string)$msrp;
|
| 95 |
-
$productInformation['regular_price_currency'] = $currency;
|
| 96 |
-
} elseif ($productPrice != 0) {
|
| 97 |
-
$productInformation['regular_price'] = (string)$productPrice;
|
| 98 |
-
$productInformation['regular_price_currency'] = $currency;
|
| 99 |
-
}
|
| 100 |
-
|
| 101 |
-
if ($cost) {
|
| 102 |
-
$productInformation['cost'] = (string)$cost;
|
| 103 |
-
$productInformation['cost_currency'] = (string)$productInformation['retail_price_currency'];
|
| 104 |
-
}
|
| 105 |
-
|
| 106 |
-
return $productInformation;
|
| 107 |
-
}
|
| 108 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/code/community/PriceWaiter/NYPWidget/Model/Callback.php
CHANGED
|
@@ -393,7 +393,7 @@ class PriceWaiter_NYPWidget_Model_Callback
|
|
| 393 |
$productSku = $request['product_sku'];
|
| 394 |
$productOptions = $this->buildProductOptionsArray($request);
|
| 395 |
|
| 396 |
-
$product = $this->
|
| 397 |
|
| 398 |
return $product->getId() ? $product : false;
|
| 399 |
}
|
|
@@ -433,6 +433,47 @@ class PriceWaiter_NYPWidget_Model_Callback
|
|
| 433 |
);
|
| 434 |
}
|
| 435 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 436 |
/**
|
| 437 |
* @param Array $request
|
| 438 |
* @return Mage_Core_Model_Store
|
|
@@ -661,6 +702,55 @@ class PriceWaiter_NYPWidget_Model_Callback
|
|
| 661 |
return $valid;
|
| 662 |
}
|
| 663 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 664 |
/**
|
| 665 |
* @internal
|
| 666 |
* @param Mage_Sales_Model_Order $order
|
|
@@ -767,6 +857,66 @@ class PriceWaiter_NYPWidget_Model_Callback
|
|
| 767 |
->save();
|
| 768 |
}
|
| 769 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 770 |
/**
|
| 771 |
* @param Mage_Sales_Model_Order $order
|
| 772 |
*/
|
| 393 |
$productSku = $request['product_sku'];
|
| 394 |
$productOptions = $this->buildProductOptionsArray($request);
|
| 395 |
|
| 396 |
+
$product = $this->getProductWithOptions($productSku, $productOptions);
|
| 397 |
|
| 398 |
return $product->getId() ? $product : false;
|
| 399 |
}
|
| 433 |
);
|
| 434 |
}
|
| 435 |
|
| 436 |
+
/**
|
| 437 |
+
* Finds the Product that matches the given options and SKU
|
| 438 |
+
* @param {String} $sku SKU of the product
|
| 439 |
+
* @param {Array} $productOptions An array of options for the product, name => value
|
| 440 |
+
* @return {Object} Returns Mage_Catalog_Model_Product of product that matches options.
|
| 441 |
+
* @throws PriceWaiter_NYPWidget_Exception_Product_NotFound If no product can be found.
|
| 442 |
+
*/
|
| 443 |
+
public function getProductWithOptions($sku, $productOptions)
|
| 444 |
+
{
|
| 445 |
+
$product = Mage::getModel('catalog/product')->getCollection()
|
| 446 |
+
->addAttributeToFilter('sku', $sku)
|
| 447 |
+
->addAttributeToSelect('*')
|
| 448 |
+
->getFirstItem();
|
| 449 |
+
|
| 450 |
+
if (!$product->getId()) {
|
| 451 |
+
throw new PriceWaiter_NYPWidget_Exception_Product_NotFound();
|
| 452 |
+
}
|
| 453 |
+
|
| 454 |
+
// NOTE: If buyer was looking at a configurable product,
|
| 455 |
+
// SKU *should* be set to that of the simple product generated
|
| 456 |
+
// based on their configuration. It $product is configurable,
|
| 457 |
+
// it indicates that either:
|
| 458 |
+
//
|
| 459 |
+
// 1. SKU of child product was not properly resolved client-side
|
| 460 |
+
// before offer was submitted.
|
| 461 |
+
//
|
| 462 |
+
// - or -
|
| 463 |
+
//
|
| 464 |
+
// 2. Something is messed up with SKUs in this store.
|
| 465 |
+
if ($product->getTypeId() == 'configurable') {
|
| 466 |
+
$product = $this->resolveConfigurableProductForOrderWrite(
|
| 467 |
+
$product,
|
| 468 |
+
$productOptions
|
| 469 |
+
);
|
| 470 |
+
}
|
| 471 |
+
|
| 472 |
+
$this->applyCustomOptionPricesToProduct($product, $productOptions);
|
| 473 |
+
|
| 474 |
+
return $product;
|
| 475 |
+
}
|
| 476 |
+
|
| 477 |
/**
|
| 478 |
* @param Array $request
|
| 479 |
* @return Mage_Core_Model_Store
|
| 702 |
return $valid;
|
| 703 |
}
|
| 704 |
|
| 705 |
+
/**
|
| 706 |
+
* For any custom options in use, modify the product's price accordingly.
|
| 707 |
+
* @param Mage_Catalog_Model_Product $product
|
| 708 |
+
* @param array $productOptions
|
| 709 |
+
*/
|
| 710 |
+
protected function applyCustomOptionPricesToProduct(
|
| 711 |
+
Mage_Catalog_Model_Product $product,
|
| 712 |
+
array $productOptions
|
| 713 |
+
)
|
| 714 |
+
{
|
| 715 |
+
// Check if any values in $productOptions map to custom options
|
| 716 |
+
// available on the product.
|
| 717 |
+
$options = $product->getOptions();
|
| 718 |
+
|
| 719 |
+
$amountToAdd = 0;
|
| 720 |
+
|
| 721 |
+
foreach($options as $opt) {
|
| 722 |
+
if (!array_key_exists($opt->getTitle(), $productOptions)) {
|
| 723 |
+
continue;
|
| 724 |
+
}
|
| 725 |
+
|
| 726 |
+
$productOptionValue = $productOptions[$opt->getTitle()];
|
| 727 |
+
|
| 728 |
+
// If the option changes the *price* of the product, attempt to
|
| 729 |
+
// get that change reflected. If this fails, order *total* will
|
| 730 |
+
// still be accurate, but the applied PriceWaiter discount will be
|
| 731 |
+
// wonky.
|
| 732 |
+
// This is not 100% accurate--it's possible that option names could
|
| 733 |
+
// change or be tweaked on the client side. But it's good enough
|
| 734 |
+
//
|
| 735 |
+
|
| 736 |
+
// 1. Apply any price that this *option* alone has
|
| 737 |
+
$amountToAdd += $opt->getPrice(true);
|
| 738 |
+
|
| 739 |
+
// 2. If the option has values, see if any of them are selected
|
| 740 |
+
// and apply their price changes.
|
| 741 |
+
foreach($opt->getValues() as $v) {
|
| 742 |
+
if ($v->getTitle() === $productOptionValue) {
|
| 743 |
+
$amountToAdd += $v->getPrice(true);
|
| 744 |
+
break;
|
| 745 |
+
}
|
| 746 |
+
};
|
| 747 |
+
}
|
| 748 |
+
|
| 749 |
+
// Apply custom option price changes all in one go
|
| 750 |
+
// (to avoid them interfering with each other)
|
| 751 |
+
$product->setPrice($product->getPrice() + $amountToAdd);
|
| 752 |
+
}
|
| 753 |
+
|
| 754 |
/**
|
| 755 |
* @internal
|
| 756 |
* @param Mage_Sales_Model_Order $order
|
| 857 |
->save();
|
| 858 |
}
|
| 859 |
|
| 860 |
+
/**
|
| 861 |
+
* Attempts to look up a simple product based on a configurable product + a
|
| 862 |
+
* hash of PriceWaiter product options.
|
| 863 |
+
*/
|
| 864 |
+
protected function resolveConfigurableProductForOrderWrite(
|
| 865 |
+
Mage_Catalog_Model_Product $product,
|
| 866 |
+
array &$productOptions
|
| 867 |
+
)
|
| 868 |
+
{
|
| 869 |
+
if ($product->getTypeId() !== 'configurable') {
|
| 870 |
+
return $product;
|
| 871 |
+
}
|
| 872 |
+
|
| 873 |
+
$attrs = $product->getTypeInstance(true)->getConfigurableAttributesAsArray($product);
|
| 874 |
+
$attributesForLookup = array();
|
| 875 |
+
$additionalCost = 0;
|
| 876 |
+
|
| 877 |
+
// Resolve product options into attribute id/value pairs
|
| 878 |
+
foreach ($attrs as $attr) {
|
| 879 |
+
if (!array_key_exists($attr['label'], $productOptions)) {
|
| 880 |
+
// No product option value exists for this attribute.
|
| 881 |
+
// This will most likely make the lookup fail.
|
| 882 |
+
continue;
|
| 883 |
+
}
|
| 884 |
+
|
| 885 |
+
$productOptionValue = $productOptions[$attr['label']];
|
| 886 |
+
$valueIndex = null;
|
| 887 |
+
|
| 888 |
+
// Find the corresponding attribute value
|
| 889 |
+
foreach ($attr['values'] as $value) {
|
| 890 |
+
if ($value['label'] === $productOptionValue) {
|
| 891 |
+
$valueIndex = $value['value_index'];
|
| 892 |
+
// If this attribute has a price assosciated with it, add it to the price later
|
| 893 |
+
if ($value['pricing_value']) {
|
| 894 |
+
$additionalCost += $value['pricing_value'];
|
| 895 |
+
}
|
| 896 |
+
break;
|
| 897 |
+
}
|
| 898 |
+
}
|
| 899 |
+
|
| 900 |
+
if ($valueIndex !== null) {
|
| 901 |
+
// We found a corresponding attribute to look for
|
| 902 |
+
$attributesForLookup[$attr['attribute_id']] = $valueIndex;
|
| 903 |
+
}
|
| 904 |
+
}
|
| 905 |
+
|
| 906 |
+
$simpleProduct = $product
|
| 907 |
+
->getTypeInstance()
|
| 908 |
+
->getProductByAttributes($attributesForLookup, $product);
|
| 909 |
+
|
| 910 |
+
if (!$product || !$product->getId()) {
|
| 911 |
+
throw new PriceWaiter_NYPWidget_Exception_Product_NotFound();
|
| 912 |
+
}
|
| 913 |
+
|
| 914 |
+
$product->load($product->getId());
|
| 915 |
+
$product->setPrice($product->getPrice() + $additionalCost);
|
| 916 |
+
|
| 917 |
+
return $product;
|
| 918 |
+
}
|
| 919 |
+
|
| 920 |
/**
|
| 921 |
* @param Mage_Sales_Model_Order $order
|
| 922 |
*/
|
app/code/community/PriceWaiter/NYPWidget/Model/Callback/Inventory.php
CHANGED
|
@@ -48,13 +48,8 @@ class PriceWaiter_NYPWidget_Model_Callback_Inventory
|
|
| 48 |
}
|
| 49 |
catch (Mage_Core_Exception $ex)
|
| 50 |
{
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
throw new PriceWaiter_NYPWidget_Exception_OutOfStock(
|
| 54 |
-
$ex->getMessage(),
|
| 55 |
-
0,
|
| 56 |
-
$ex
|
| 57 |
-
);
|
| 58 |
}
|
| 59 |
|
| 60 |
// 2. If that resulted in items going out of stock, they need to be saved + reindexed.
|
| 48 |
}
|
| 49 |
catch (Mage_Core_Exception $ex)
|
| 50 |
{
|
| 51 |
+
$translatedEx = PriceWaiter_NYPWidget_Exception_Abstract::translateMagentoException($ex);
|
| 52 |
+
throw $translatedEx;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
}
|
| 54 |
|
| 55 |
// 2. If that resulted in items going out of stock, they need to be saved + reindexed.
|
app/code/community/PriceWaiter/NYPWidget/Model/Deal.php
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
/**
|
| 4 |
+
* A Deal is a single opportunity for a single buyer to purchase one or more
|
| 5 |
+
* products at a certain price.
|
| 6 |
+
*/
|
| 7 |
+
class PriceWaiter_NYPWidget_Model_Deal extends Mage_Core_Model_Abstract
|
| 8 |
+
{
|
| 9 |
+
/**
|
| 10 |
+
* Querystring arg used to specify deal id.
|
| 11 |
+
*/
|
| 12 |
+
const CHECKOUT_URL_DEAL_ID_ARG = 'd';
|
| 13 |
+
|
| 14 |
+
/**
|
| 15 |
+
* @var Array
|
| 16 |
+
*/
|
| 17 |
+
private $_offerItems = null;
|
| 18 |
+
|
| 19 |
+
/**
|
| 20 |
+
* @var Array
|
| 21 |
+
*/
|
| 22 |
+
private $_resolvedItems = null;
|
| 23 |
+
|
| 24 |
+
/**
|
| 25 |
+
* {@inheritdoc}
|
| 26 |
+
*/
|
| 27 |
+
public function __construct()
|
| 28 |
+
{
|
| 29 |
+
$this->_init('nypwidget/deal');
|
| 30 |
+
parent::__construct();
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
/**
|
| 34 |
+
* Initializes the parameters of this deal from a request made to the
|
| 35 |
+
* "create deal" endpoint.
|
| 36 |
+
* Does not actually save this Deal to the Db.
|
| 37 |
+
* @return PriceWaiter_NYPWidget_Model_Deal $this
|
| 38 |
+
*/
|
| 39 |
+
public function initFromCreateRequest(PriceWaiter_NYPWidget_Controller_Endpoint_Request $request)
|
| 40 |
+
{
|
| 41 |
+
$body = $request->getBody();
|
| 42 |
+
|
| 43 |
+
if (!empty($body->test)) {
|
| 44 |
+
throw new PriceWaiter_NYPWidget_Exception_NoTestDeals();
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
$this->setId($body->id);
|
| 48 |
+
$this->setCreateRequestId($request->getId());
|
| 49 |
+
$this->setCreatedAt(date('Y-m-d H:i:s', $request->getTimestamp()));
|
| 50 |
+
|
| 51 |
+
$storeId = $this->getStoreIdForApiKey($request->getApiKey());
|
| 52 |
+
if (!$storeId) {
|
| 53 |
+
throw new PriceWaiter_NYPWidget_Exception_ApiKey();
|
| 54 |
+
}
|
| 55 |
+
$this->setStoreId($storeId);
|
| 56 |
+
|
| 57 |
+
if (!empty($body->expires_at)) {
|
| 58 |
+
$expires = strtotime($body->expires_at);
|
| 59 |
+
$this->setExpiresAt(date('Y-m-d H:i:s', $expires));
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
$this->setPricewaiterBuyerId($body->buyer->id);
|
| 63 |
+
|
| 64 |
+
// Stash full JSON for create request, so we have access to items later on.
|
| 65 |
+
$this->setCreateRequestBodyJson($body);
|
| 66 |
+
|
| 67 |
+
return $this;
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
/**
|
| 71 |
+
*/
|
| 72 |
+
public function ensurePresentInQuote(Mage_Sales_Model_Quote $quote)
|
| 73 |
+
{
|
| 74 |
+
$items = $this->getOfferItems();
|
| 75 |
+
|
| 76 |
+
foreach($items as $item) {
|
| 77 |
+
$item->ensurePresentInQuote($quote);
|
| 78 |
+
}
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
/**
|
| 82 |
+
* @return Array Array of PriceWaiter_NYPWidget_Model_OfferItem instances.
|
| 83 |
+
*/
|
| 84 |
+
public function getOfferItems()
|
| 85 |
+
{
|
| 86 |
+
if (!is_null($this->_offerItems)) {
|
| 87 |
+
return $this->_offerItems;
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
$this->_offerItems = array_map(
|
| 91 |
+
function($itemData) {
|
| 92 |
+
return Mage::getModel('nypwidget/offer_item', $itemData);
|
| 93 |
+
},
|
| 94 |
+
$this->getCreateRequestBodyField('items', array())
|
| 95 |
+
);
|
| 96 |
+
|
| 97 |
+
return $this->_offerItems;
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
/**
|
| 101 |
+
* @param String $formKey Magento form key used for the add to cart form.
|
| 102 |
+
* @return String A URL that, when followed, will end up on the cart page with this deal applied.
|
| 103 |
+
* @throws PriceWaiter_NYPWidget_Exception_SingleItemOnly
|
| 104 |
+
*/
|
| 105 |
+
public function getAddToCartUrl($formKey)
|
| 106 |
+
{
|
| 107 |
+
$offerItems = $this->getOfferItems();
|
| 108 |
+
|
| 109 |
+
if (count($offerItems) !== 1) {
|
| 110 |
+
throw new PriceWaiter_NYPWidget_Exception_SingleItemOnly();
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
$onlyItem = $offerItems[0];
|
| 114 |
+
$formValues = $onlyItem->getAddToCartForm();
|
| 115 |
+
|
| 116 |
+
$addToCartQuery = array(
|
| 117 |
+
'form_key' => $formKey,
|
| 118 |
+
'product' => $formValues['product'],
|
| 119 |
+
'qty' => $onlyItem->getMinimumQuantity(),
|
| 120 |
+
);
|
| 121 |
+
|
| 122 |
+
if (isset($formValues['super_attribute'])) {
|
| 123 |
+
$addToCartQuery['super_attribute'] = $formValues['super_attribute'];
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
$urls = array(
|
| 127 |
+
// 1. Add to cart.
|
| 128 |
+
array(
|
| 129 |
+
'checkout/cart/add',
|
| 130 |
+
'_query' => $addToCartQuery,
|
| 131 |
+
),
|
| 132 |
+
);
|
| 133 |
+
|
| 134 |
+
return array_reduce(array_reverse($urls), function($prevUrl, $params) {
|
| 135 |
+
|
| 136 |
+
$path = array_shift($params);
|
| 137 |
+
|
| 138 |
+
if ($prevUrl) {
|
| 139 |
+
$params['_query']['return_url'] = $prevUrl;
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
return Mage::getUrl($path, $params);
|
| 143 |
+
|
| 144 |
+
});
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
/**
|
| 148 |
+
* @return String A publically routeable URL to take advantage of this deal.
|
| 149 |
+
*/
|
| 150 |
+
public function getCheckoutUrl()
|
| 151 |
+
{
|
| 152 |
+
return Mage::getUrl("_dealpw/checkout", array(
|
| 153 |
+
'_store' => $this->getStoreId(),
|
| 154 |
+
'_query' => array(
|
| 155 |
+
self::CHECKOUT_URL_DEAL_ID_ARG => $this->getId(),
|
| 156 |
+
),
|
| 157 |
+
));
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
/**
|
| 161 |
+
* @return Mage_Core_Model_Store The Store this deal is part of.
|
| 162 |
+
*/
|
| 163 |
+
public function getStore()
|
| 164 |
+
{
|
| 165 |
+
$id = $this->getStoreId();
|
| 166 |
+
if ($id) {
|
| 167 |
+
$store = Mage::getModel('core/store');
|
| 168 |
+
return $store->load($id);
|
| 169 |
+
}
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
/**
|
| 173 |
+
* @param Integer $now UNIX timestamp representing current time.
|
| 174 |
+
* @return boolean
|
| 175 |
+
*/
|
| 176 |
+
public function isExpired($now = null)
|
| 177 |
+
{
|
| 178 |
+
$expiry = $this->getExpiresAt();
|
| 179 |
+
|
| 180 |
+
if (empty($expiry)) {
|
| 181 |
+
return false;
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
$expiry = strtotime($expiry);
|
| 185 |
+
$now = ($now === null ? time() : $now);
|
| 186 |
+
|
| 187 |
+
return $expiry < $now;
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
/**
|
| 191 |
+
* Nice alias for getRevoked()
|
| 192 |
+
* @return boolean
|
| 193 |
+
*/
|
| 194 |
+
public function isRevoked()
|
| 195 |
+
{
|
| 196 |
+
return !!$this->getRevoked();
|
| 197 |
+
}
|
| 198 |
+
|
| 199 |
+
/**
|
| 200 |
+
* @return PriceWaiter_NYPWidget_Model_Deal $this
|
| 201 |
+
*/
|
| 202 |
+
public function processCreateRequest(PriceWaiter_NYPWidget_Controller_Endpoint_Request $request)
|
| 203 |
+
{
|
| 204 |
+
$this->initFromCreateRequest($request);
|
| 205 |
+
|
| 206 |
+
$existingDeal = Mage::getModel('nypwidget/deal')->load($this->getId());
|
| 207 |
+
|
| 208 |
+
if ($existingDeal && $existingDeal->getId()) {
|
| 209 |
+
// This deal has already been created.
|
| 210 |
+
throw new PriceWaiter_NYPWidget_Exception_DealAlreadyCreated();
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
$saveTransaction = Mage::getModel('core/resource_transaction');
|
| 214 |
+
$saveTransaction->addObject($this);
|
| 215 |
+
$saveTransaction->save();
|
| 216 |
+
|
| 217 |
+
return $this;
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
+
/**
|
| 221 |
+
* Main "Revoke Deal" logic.
|
| 222 |
+
* @param PriceWaiter_NYPWidget_Controller_Endpoint_Request $request
|
| 223 |
+
* @return PriceWaiter_NYPWidget_Model_Deal $this
|
| 224 |
+
*/
|
| 225 |
+
public function processRevokeRequest(PriceWaiter_NYPWidget_Controller_Endpoint_Request $request)
|
| 226 |
+
{
|
| 227 |
+
if ($this->revoked) {
|
| 228 |
+
throw new PriceWaiter_NYPWidget_Exception_DealAlreadyRevoked();
|
| 229 |
+
}
|
| 230 |
+
|
| 231 |
+
$this->setRevoked(1);
|
| 232 |
+
$this->setRevokeRequestId($request->getId());
|
| 233 |
+
$this->setRevokedAt(date('Y-m-d H:i:s', $request->getTimestamp()));
|
| 234 |
+
|
| 235 |
+
$saveTransaction = Mage::getModel('core/resource_transaction');
|
| 236 |
+
$saveTransaction->addObject($this);
|
| 237 |
+
$saveTransaction->save();
|
| 238 |
+
|
| 239 |
+
return $this;
|
| 240 |
+
}
|
| 241 |
+
|
| 242 |
+
/**
|
| 243 |
+
* @internal
|
| 244 |
+
* @param String $json
|
| 245 |
+
*/
|
| 246 |
+
public function setCreateRequestBodyJson($json)
|
| 247 |
+
{
|
| 248 |
+
if (!is_string($json)) {
|
| 249 |
+
$json = json_encode($json);
|
| 250 |
+
}
|
| 251 |
+
|
| 252 |
+
$this->setData('create_request_body_json', $json);
|
| 253 |
+
$this->_createRequestBody = null;
|
| 254 |
+
$this->_items = null;
|
| 255 |
+
|
| 256 |
+
return $this;
|
| 257 |
+
}
|
| 258 |
+
|
| 259 |
+
/**
|
| 260 |
+
* @internal Reads a field off the original "create" request.
|
| 261 |
+
* @param String $key
|
| 262 |
+
* @param Mixed $default
|
| 263 |
+
* @return Mixed
|
| 264 |
+
*/
|
| 265 |
+
protected function getCreateRequestBodyField($key, $default = null)
|
| 266 |
+
{
|
| 267 |
+
if ($this->_createRequestBody === null) {
|
| 268 |
+
// Parse it!
|
| 269 |
+
$this->_createRequestBody = json_decode($this->getCreateRequestBodyJson());
|
| 270 |
+
}
|
| 271 |
+
|
| 272 |
+
$b = $this->_createRequestBody;
|
| 273 |
+
return isset($b->$key) ? $b->$key : null;
|
| 274 |
+
}
|
| 275 |
+
|
| 276 |
+
protected function getStoreIdForApiKey($apiKey)
|
| 277 |
+
{
|
| 278 |
+
$helper = Mage::helper('nypwidget');
|
| 279 |
+
$store = $helper->getStoreByPriceWaiterApiKey($apiKey);
|
| 280 |
+
return $store ? $store->getId() : false;
|
| 281 |
+
}
|
| 282 |
+
}
|
app/code/community/PriceWaiter/NYPWidget/Model/Discounter.php
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
/**
|
| 4 |
+
* Class responsible for calculating PriceWaiter discounts.
|
| 5 |
+
*/
|
| 6 |
+
class PriceWaiter_NYPWidget_Model_Discounter
|
| 7 |
+
{
|
| 8 |
+
protected $offerAmountPerItem = null;
|
| 9 |
+
protected $offerMinQty = 1;
|
| 10 |
+
protected $offerMaxQty = 1;
|
| 11 |
+
|
| 12 |
+
/**
|
| 13 |
+
* @var Mage_Directory_Model_Currency
|
| 14 |
+
*/
|
| 15 |
+
protected $offerCurrency = null;
|
| 16 |
+
|
| 17 |
+
/**
|
| 18 |
+
* @var double
|
| 19 |
+
*/
|
| 20 |
+
protected $productPrice = 0;
|
| 21 |
+
|
| 22 |
+
/**
|
| 23 |
+
* @var Mage_Directory_Model_Currency
|
| 24 |
+
*/
|
| 25 |
+
protected $quoteBaseCurrency = null;
|
| 26 |
+
|
| 27 |
+
/**
|
| 28 |
+
* @var Mage_Directory_Model_Currency
|
| 29 |
+
*/
|
| 30 |
+
protected $quoteCurrency = null;
|
| 31 |
+
|
| 32 |
+
/**
|
| 33 |
+
* @var integer
|
| 34 |
+
*/
|
| 35 |
+
protected $quoteItemQty = 1;
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
/**
|
| 39 |
+
* @param Mage_Directory_Model_Currency $currency
|
| 40 |
+
*/
|
| 41 |
+
public function setQuoteBaseCurrency(Mage_Directory_Model_Currency $currency)
|
| 42 |
+
{
|
| 43 |
+
$this->quoteBaseCurrency = $currency;
|
| 44 |
+
return $this;
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
/**
|
| 48 |
+
* @param Mage_Directory_Model_Currency $currency
|
| 49 |
+
*/
|
| 50 |
+
public function setQuoteCurrency(Mage_Directory_Model_Currency $currency)
|
| 51 |
+
{
|
| 52 |
+
$this->quoteCurrency = $currency;
|
| 53 |
+
return $this;
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
/**
|
| 57 |
+
* @param double|string $amount
|
| 58 |
+
*/
|
| 59 |
+
public function setOfferAmountPerItem($amount)
|
| 60 |
+
{
|
| 61 |
+
$this->offerAmountPerItem = $amount;
|
| 62 |
+
return $this;
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
/**
|
| 66 |
+
* @param Mage_Directory_Model_Currency $currency
|
| 67 |
+
*/
|
| 68 |
+
public function setOfferCurrency(Mage_Directory_Model_Currency $currency)
|
| 69 |
+
{
|
| 70 |
+
$this->offerCurrency = $currency;
|
| 71 |
+
return $this;
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
/**
|
| 75 |
+
* @param integer $qty
|
| 76 |
+
*/
|
| 77 |
+
public function setOfferMinQty($qty)
|
| 78 |
+
{
|
| 79 |
+
$this->offerMinQty = $qty;
|
| 80 |
+
return $this;
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
/**
|
| 84 |
+
* @param integer $qty
|
| 85 |
+
*/
|
| 86 |
+
public function setOfferMaxQty($qty)
|
| 87 |
+
{
|
| 88 |
+
$this->offerMaxQty = $qty;
|
| 89 |
+
return $this;
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
/**
|
| 93 |
+
* Product's price, expressed in the quote's currency.
|
| 94 |
+
* @param double|string $price
|
| 95 |
+
*/
|
| 96 |
+
public function setProductPrice($price)
|
| 97 |
+
{
|
| 98 |
+
$this->productPrice = $price;
|
| 99 |
+
return $this;
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
/**
|
| 103 |
+
* Product's "original price", expressed in the quote's currency.
|
| 104 |
+
* @param double|string $price
|
| 105 |
+
*/
|
| 106 |
+
public function setProductOriginalPrice($price)
|
| 107 |
+
{
|
| 108 |
+
$this->productOriginalPrice = $price;
|
| 109 |
+
return $this;
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
/**
|
| 113 |
+
* @param Integer $qty Quantity being ordered.
|
| 114 |
+
*/
|
| 115 |
+
public function setQuoteItemQty($qty)
|
| 116 |
+
{
|
| 117 |
+
$this->quoteItemQty = $qty;
|
| 118 |
+
return $this;
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
/**
|
| 122 |
+
* @return double The discount amount to apply to the quote item (as a positive number), expressed in the quote's currency.
|
| 123 |
+
*/
|
| 124 |
+
public function getDiscount()
|
| 125 |
+
{
|
| 126 |
+
return $this->calculateDiscount(
|
| 127 |
+
$this->productPrice,
|
| 128 |
+
$this->quoteCurrency
|
| 129 |
+
);
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
/**
|
| 133 |
+
* @return double The discount amount to apply to the quote item, expressed in the quote's base currency.
|
| 134 |
+
*/
|
| 135 |
+
public function getBaseDiscount()
|
| 136 |
+
{
|
| 137 |
+
return $this->calculateDiscount(
|
| 138 |
+
$this->productPrice,
|
| 139 |
+
$this->quoteBaseCurrency
|
| 140 |
+
);
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
/**
|
| 144 |
+
* @return double The discount amount over the original price (in quote currency)
|
| 145 |
+
*/
|
| 146 |
+
public function getOriginalDiscount()
|
| 147 |
+
{
|
| 148 |
+
$price = $this->productOriginalPrice ?
|
| 149 |
+
$this->productOriginalPrice :
|
| 150 |
+
$this->productPrice;
|
| 151 |
+
|
| 152 |
+
return $this->calculateDiscount($price, $this->quoteCurrency);
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
/**
|
| 156 |
+
* @return double The discount amount over the product's base price (in quote base currency)
|
| 157 |
+
*/
|
| 158 |
+
public function getBaseOriginalDiscount()
|
| 159 |
+
{
|
| 160 |
+
$price = $this->productOriginalPrice ?
|
| 161 |
+
$this->productOriginalPrice :
|
| 162 |
+
$this->productPrice;
|
| 163 |
+
|
| 164 |
+
return $this->calculateDiscount($price, $this->quoteBaseCurrency);
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
/**
|
| 168 |
+
* Calculates a discount in the given currency.
|
| 169 |
+
* @param double|string $price Product price
|
| 170 |
+
*/
|
| 171 |
+
protected function calculateDiscount($priceInQuoteCurrency, Mage_Directory_Model_Currency $currency)
|
| 172 |
+
{
|
| 173 |
+
$effectiveQty = min($this->quoteItemQty, $this->offerMaxQty);
|
| 174 |
+
|
| 175 |
+
if ($effectiveQty < $this->offerMinQty) {
|
| 176 |
+
// Not enough of item on quote to qualify.
|
| 177 |
+
return 0;
|
| 178 |
+
}
|
| 179 |
+
|
| 180 |
+
// Standardize offer into quote currency
|
| 181 |
+
$amountPerItemInQuoteCurrency = $this->offerCurrency->convert(
|
| 182 |
+
$this->offerAmountPerItem,
|
| 183 |
+
$this->quoteCurrency
|
| 184 |
+
);
|
| 185 |
+
|
| 186 |
+
if ($amountPerItemInQuoteCurrency >= $priceInQuoteCurrency) {
|
| 187 |
+
// Offer is for more than product price. No discount applied.
|
| 188 |
+
return 0;
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
$discountInQuoteCurrency = ($priceInQuoteCurrency - $amountPerItemInQuoteCurrency) * $effectiveQty;
|
| 192 |
+
return $this->quoteCurrency->convert($discountInQuoteCurrency, $currency);
|
| 193 |
+
}
|
| 194 |
+
|
| 195 |
+
}
|
app/code/community/PriceWaiter/NYPWidget/Model/Embed.php
ADDED
|
@@ -0,0 +1,520 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
/**
|
| 4 |
+
* Model responsible for figuring how to format PW's embed on the product page.
|
| 5 |
+
*
|
| 6 |
+
* @method Mage_Core_Model_Store getStore() Gets the store being embedded on.
|
| 7 |
+
* @method Mage_Customer_Model_Customer getCustomer()
|
| 8 |
+
* @method Mage_Catalog_Model_Product getProduct()
|
| 9 |
+
* @method Mage_Catalog_Model_Category getCategory()
|
| 10 |
+
*/
|
| 11 |
+
class PriceWaiter_NYPWidget_Model_Embed
|
| 12 |
+
extends Varien_Object
|
| 13 |
+
{
|
| 14 |
+
// Cache values for these because they can be expensive to compute.
|
| 15 |
+
protected $_isButtonEnabled = null;
|
| 16 |
+
protected $_isConversionToolsEnabled = null;
|
| 17 |
+
protected $_scriptTags = null;
|
| 18 |
+
|
| 19 |
+
/**
|
| 20 |
+
* Build a variable describing *all* categories product is part of.
|
| 21 |
+
* @param Mage_Catalog_Model_Product $product
|
| 22 |
+
* @param Mage_Core_Model_Store $store
|
| 23 |
+
* @return Array
|
| 24 |
+
*/
|
| 25 |
+
public function buildCategoriesVar(Mage_Catalog_Model_Product $product, Mage_Core_Model_Store $store)
|
| 26 |
+
{
|
| 27 |
+
$categorization = array();
|
| 28 |
+
$assignedCategories = $product->getCategoryCollection()
|
| 29 |
+
->addAttributeToSelect('name');
|
| 30 |
+
|
| 31 |
+
$baseUrl = $store->getBaseUrl();
|
| 32 |
+
|
| 33 |
+
// Find the path (parents) of each category, and add their information
|
| 34 |
+
// to the categorization array
|
| 35 |
+
foreach ($assignedCategories as $assignedCategory) {
|
| 36 |
+
$parentCategories = array();
|
| 37 |
+
$path = $assignedCategory->getPath();
|
| 38 |
+
$parentIds = explode('/', $path);
|
| 39 |
+
array_shift($parentIds); // We don't care about the root category
|
| 40 |
+
|
| 41 |
+
$categoryModel = Mage::getModel('catalog/category');
|
| 42 |
+
foreach($parentIds as $parentCategoryId) {
|
| 43 |
+
$parentCategory = $categoryModel->load($parentCategoryId);
|
| 44 |
+
$parentCategoryUrl = preg_replace('/^\//', '', $parentCategory->getUrlPath());
|
| 45 |
+
|
| 46 |
+
$parentCategories[] = array(
|
| 47 |
+
'name' => $parentCategory->getName(),
|
| 48 |
+
'url' => $baseUrl . '/' . $parentCategoryUrl
|
| 49 |
+
);
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
$categorization[] = $parentCategories;
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
return $categorization;
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
/**
|
| 59 |
+
* Builds a variable describing the custom options configuration for $product.
|
| 60 |
+
* @param Mage_Catalog_Model_Product $product
|
| 61 |
+
* @return Array
|
| 62 |
+
*/
|
| 63 |
+
public function buildCustomOptionsVar(Mage_Catalog_Model_Product $product)
|
| 64 |
+
{
|
| 65 |
+
$options = $product->getOptions();
|
| 66 |
+
$result = array();
|
| 67 |
+
|
| 68 |
+
foreach($options as $opt) {
|
| 69 |
+
$jsonOpt = array(
|
| 70 |
+
'id' => $opt->getId(),
|
| 71 |
+
'type' => $opt->getType(),
|
| 72 |
+
'title' => $opt->getTitle(),
|
| 73 |
+
'required' => !!$opt->getIsRequire(),
|
| 74 |
+
);
|
| 75 |
+
|
| 76 |
+
$sku = $opt->getSku();
|
| 77 |
+
if ($sku !== null && $sku !== '') {
|
| 78 |
+
$jsonOpt['sku'] = $opt->getSku();
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
$values = $opt->getValues();
|
| 82 |
+
if ($values) {
|
| 83 |
+
$jsonOpt['values'] = array();
|
| 84 |
+
foreach($values as $v) {
|
| 85 |
+
$jsonValue = array(
|
| 86 |
+
'id' => $v->getId(),
|
| 87 |
+
'title' => $v->getTitle(),
|
| 88 |
+
);
|
| 89 |
+
|
| 90 |
+
$sku = $v->getSku();
|
| 91 |
+
if ($sku !== null && $sku !== '') {
|
| 92 |
+
$jsonValue['sku'] = $v->getSku();
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
$jsonOpt['values'][] = $jsonValue;
|
| 96 |
+
}
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
$result[] = $jsonOpt;
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
return $result;
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
/**
|
| 106 |
+
* @param Mage_Catalog_Model_Product $product
|
| 107 |
+
* @return Array|false A variable mapping simple product ids to skus, or false if not available.
|
| 108 |
+
*/
|
| 109 |
+
public function buildIdToSkuVar(Mage_Catalog_Model_Product $product)
|
| 110 |
+
{
|
| 111 |
+
if ($product->getTypeId() !== Mage_Catalog_Model_Product_Type::TYPE_CONFIGURABLE) {
|
| 112 |
+
// This is only used for configurables
|
| 113 |
+
return false;
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
$simples = Mage::getModel('catalog/product_type_configurable')
|
| 117 |
+
->setProduct($product)
|
| 118 |
+
->getUsedProductCollection()
|
| 119 |
+
->addAttributeToSelect('sku');
|
| 120 |
+
|
| 121 |
+
$idsToSkus = array();
|
| 122 |
+
|
| 123 |
+
foreach ($simples as $simple) {
|
| 124 |
+
$id = $simple->getId();
|
| 125 |
+
$sku = $simple->getSku();
|
| 126 |
+
$idsToSkus[$id] = $sku;
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
return count($idsToSkus) > 0 ? $idsToSkus : false;
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
/**
|
| 133 |
+
* @return Object An object containing the PriceWaiterOptions structure.
|
| 134 |
+
*/
|
| 135 |
+
public function buildPriceWaiterOptionsVar()
|
| 136 |
+
{
|
| 137 |
+
$options = new StdClass();
|
| 138 |
+
|
| 139 |
+
if (!$this->isButtonEnabled()) {
|
| 140 |
+
$options->enableButton = false;
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
if (!$this->isConversionToolsEnabled()) {
|
| 144 |
+
$options->enableConversionTools = false;
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
$currency = $this->getStore()->getCurrentCurrencyCode();
|
| 148 |
+
if ($currency) {
|
| 149 |
+
$options->currency = $currency;
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
$options->metadata = (object)array(
|
| 153 |
+
'_magento_version' => Mage::helper('nypwidget/about')->getPlatformVersion(),
|
| 154 |
+
'_magento_extension_version' => Mage::helper('nypwidget/about')->getExtensionVersion(),
|
| 155 |
+
);
|
| 156 |
+
|
| 157 |
+
$product = $this->getProduct();
|
| 158 |
+
if ($product) {
|
| 159 |
+
$options->product = $this->buildProductObject($product);
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
// Set user.email and postal_code when available
|
| 163 |
+
$customer = $this->getCustomer();
|
| 164 |
+
if ($customer && $customer->getId()) {
|
| 165 |
+
$options->user = new StdClass();
|
| 166 |
+
$options->user->email = $customer->getEmail();
|
| 167 |
+
|
| 168 |
+
$addr = $customer->getDefaultShippingAddress();
|
| 169 |
+
if ($addr && $addr->getId()) {
|
| 170 |
+
$postcode = $addr->getPostcode();
|
| 171 |
+
if ($postcode) {
|
| 172 |
+
$options->postal_code = $postcode;
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
+
$country = $addr->getCountryId(); // 2-char ISO code, e.g. 'US'
|
| 176 |
+
if ($country) {
|
| 177 |
+
$options->country = $country;
|
| 178 |
+
}
|
| 179 |
+
}
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
return $options;
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
/**
|
| 186 |
+
* Builds the `product` object for PriceWaiterOptions.
|
| 187 |
+
* @param Mage_Catalog_Model_Product $product
|
| 188 |
+
* @return Object
|
| 189 |
+
*/
|
| 190 |
+
public function buildProductObject(Mage_Catalog_Model_Product $product)
|
| 191 |
+
{
|
| 192 |
+
$result = new StdClass();
|
| 193 |
+
$result->sku = $product->getSku();
|
| 194 |
+
$result->name = $product->getName();
|
| 195 |
+
|
| 196 |
+
$brand = $this->getProductBrand($product);
|
| 197 |
+
if ($brand !== false) {
|
| 198 |
+
$result->brand = $brand;
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
$image = $product->getImageUrl();
|
| 202 |
+
if ($image) {
|
| 203 |
+
$result->image = $image;
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
// If possible, set the base price.
|
| 207 |
+
// For configurables etc, platform JS will have to take over and
|
| 208 |
+
// dynamically calculate price.
|
| 209 |
+
$price = $product->getFinalPrice();
|
| 210 |
+
if ($price > 0) {
|
| 211 |
+
$result->price = $price;
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
return $result;
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
/**
|
| 218 |
+
* @return array An array of script tags to be rendered onto the page.
|
| 219 |
+
*/
|
| 220 |
+
public function getScriptTags()
|
| 221 |
+
{
|
| 222 |
+
// NOTE: Anything coming out of this method is rendered directly to
|
| 223 |
+
// the page. Be sure to escape your HTML etc.
|
| 224 |
+
|
| 225 |
+
if ($this->_scriptTags !== null) {
|
| 226 |
+
return $this->_scriptTags;
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
$store = $this->getStore();
|
| 230 |
+
|
| 231 |
+
$helper = Mage::helper('nypwidget');
|
| 232 |
+
$widgetJsUrl = $helper->getWidgetJsUrl($store);
|
| 233 |
+
|
| 234 |
+
if (!$widgetJsUrl) {
|
| 235 |
+
// Can't actually embed.
|
| 236 |
+
$this->_scriptTags = array();
|
| 237 |
+
return $this->_scriptTags;
|
| 238 |
+
}
|
| 239 |
+
|
| 240 |
+
$JSON_OPTIONS = 0;
|
| 241 |
+
if (defined('JSON_UNESCAPED_SLASHES')) {
|
| 242 |
+
$JSON_OPTIONS |= JSON_UNESCAPED_SLASHES;
|
| 243 |
+
}
|
| 244 |
+
|
| 245 |
+
// First, a <script> tag containing global JS variables used
|
| 246 |
+
// by our platform JS.
|
| 247 |
+
|
| 248 |
+
$variablesTag = array('<script>');
|
| 249 |
+
foreach($this->getJavascriptVariables() as $name => $value) {
|
| 250 |
+
$value = json_encode($value, $JSON_OPTIONS);
|
| 251 |
+
$variablesTag[] = "var $name = $value;";
|
| 252 |
+
}
|
| 253 |
+
$variablesTag[] = '</script>';
|
| 254 |
+
|
| 255 |
+
// Then, the actual PW embed
|
| 256 |
+
$embedTag = array(
|
| 257 |
+
'<script src="',
|
| 258 |
+
htmlspecialchars($widgetJsUrl),
|
| 259 |
+
'" async></script>',
|
| 260 |
+
);
|
| 261 |
+
|
| 262 |
+
$this->_scriptTags = array(
|
| 263 |
+
implode('', $variablesTag),
|
| 264 |
+
implode('', $embedTag),
|
| 265 |
+
);
|
| 266 |
+
return $this->_scriptTags;
|
| 267 |
+
}
|
| 268 |
+
|
| 269 |
+
/**
|
| 270 |
+
* @return Array An array of global variables to register on the page. Key is variable name, value is value (to be JSON-encoded);
|
| 271 |
+
*/
|
| 272 |
+
public function getJavascriptVariables()
|
| 273 |
+
{
|
| 274 |
+
$vars = array(
|
| 275 |
+
'PriceWaiterOptions' => $this->buildPriceWaiterOptionsVar(),
|
| 276 |
+
);
|
| 277 |
+
|
| 278 |
+
$product = $this->getProduct();
|
| 279 |
+
$store = $this->getStore();
|
| 280 |
+
|
| 281 |
+
if ($product) {
|
| 282 |
+
// Provide a hint to JS about what *kind* of product we're looking at.
|
| 283 |
+
$vars['PriceWaiterProductType'] = $product->getTypeId();
|
| 284 |
+
|
| 285 |
+
// Platform JS picks this up to refer back to.
|
| 286 |
+
// TODO: It seems like we shoudl not have to provide PriceWaiterRegularPrice--
|
| 287 |
+
// there should be enough data available already on the frontend to
|
| 288 |
+
// figure this out.
|
| 289 |
+
$vars['PriceWaiterRegularPrice'] = (double)$product->getPrice();
|
| 290 |
+
|
| 291 |
+
// Provide frontend a means to map product ids to skus
|
| 292 |
+
// (Used for configurable product support)
|
| 293 |
+
$idsToSkus = $this->buildIdToSkuVar($product);
|
| 294 |
+
if ($idsToSkus !== false) {
|
| 295 |
+
$vars['PriceWaiterIdToSkus'] = $idsToSkus;
|
| 296 |
+
}
|
| 297 |
+
|
| 298 |
+
// Provide detailed information about product categories
|
| 299 |
+
$vars['PriceWaiterCategories'] = $this->buildCategoriesVar($product, $store);
|
| 300 |
+
|
| 301 |
+
// Provide data about custom options (SKU modifiers + labels, mostly)
|
| 302 |
+
$custom = $this->buildCustomOptionsVar($product);
|
| 303 |
+
if ($custom) {
|
| 304 |
+
$vars['PriceWaiterCustomOptions'] = $custom;
|
| 305 |
+
}
|
| 306 |
+
}
|
| 307 |
+
|
| 308 |
+
return $vars;
|
| 309 |
+
}
|
| 310 |
+
|
| 311 |
+
/**
|
| 312 |
+
* @return boolean Whether the PW button is enabled.
|
| 313 |
+
*/
|
| 314 |
+
public function isButtonEnabled()
|
| 315 |
+
{
|
| 316 |
+
if ($this->_isButtonEnabled === null) {
|
| 317 |
+
$this->_isButtonEnabled = $this->isFeatureEnabled(
|
| 318 |
+
'isButtonEnabledForStore',
|
| 319 |
+
'isButtonEnabledForCustomerGroup',
|
| 320 |
+
'isButtonEnabledForAnyCategory'
|
| 321 |
+
);
|
| 322 |
+
}
|
| 323 |
+
|
| 324 |
+
return $this->_isButtonEnabled;
|
| 325 |
+
}
|
| 326 |
+
|
| 327 |
+
/**
|
| 328 |
+
* @return boolean Whether PW's non-button "conversion tools" are enabled.
|
| 329 |
+
*/
|
| 330 |
+
public function isConversionToolsEnabled()
|
| 331 |
+
{
|
| 332 |
+
if ($this->_isConversionToolsEnabled === null) {
|
| 333 |
+
$this->_isConversionToolsEnabled = $this->isFeatureEnabled(
|
| 334 |
+
'isConversionToolsEnabledForStore',
|
| 335 |
+
'isConversionToolsEnabledForCustomerGroup',
|
| 336 |
+
'isConversionToolsEnabledForAnyCategory'
|
| 337 |
+
);
|
| 338 |
+
}
|
| 339 |
+
|
| 340 |
+
return $this->_isConversionToolsEnabled;
|
| 341 |
+
}
|
| 342 |
+
|
| 343 |
+
/**
|
| 344 |
+
* @param Mage_Catalog_Model_Product $product
|
| 345 |
+
* @return String|false
|
| 346 |
+
*/
|
| 347 |
+
public function getProductBrand(Mage_Catalog_Model_Product $product)
|
| 348 |
+
{
|
| 349 |
+
// prefer brand, but fallback to manufacturer attribute
|
| 350 |
+
$brand = $product->getData('brand');
|
| 351 |
+
|
| 352 |
+
if ($brand) {
|
| 353 |
+
return $brand;
|
| 354 |
+
}
|
| 355 |
+
|
| 356 |
+
$attributesToTry = array(
|
| 357 |
+
'manufacturer',
|
| 358 |
+
'c2c_brand',
|
| 359 |
+
);
|
| 360 |
+
|
| 361 |
+
foreach($attributesToTry as $attr) {
|
| 362 |
+
$value = $this->safeGetAttributeText($product, $attr);
|
| 363 |
+
if ($value) {
|
| 364 |
+
return $value;
|
| 365 |
+
}
|
| 366 |
+
}
|
| 367 |
+
|
| 368 |
+
return false;
|
| 369 |
+
}
|
| 370 |
+
|
| 371 |
+
/**
|
| 372 |
+
* @param Mage_Catalog_Model_Category $category
|
| 373 |
+
*/
|
| 374 |
+
public function setCategory($category)
|
| 375 |
+
{
|
| 376 |
+
$this->invalidateCachedValues();
|
| 377 |
+
return parent::setCategory($category);
|
| 378 |
+
}
|
| 379 |
+
|
| 380 |
+
/**
|
| 381 |
+
* @param Number $id
|
| 382 |
+
*/
|
| 383 |
+
public function setCustomerGroupId($id)
|
| 384 |
+
{
|
| 385 |
+
$this->invalidateCachedValues();
|
| 386 |
+
return parent::setCustomerGroupId($id);
|
| 387 |
+
}
|
| 388 |
+
|
| 389 |
+
/**
|
| 390 |
+
* @param Mage_Catalog_Model_Product $product
|
| 391 |
+
*/
|
| 392 |
+
public function setProduct($product)
|
| 393 |
+
{
|
| 394 |
+
$this->invalidateCachedValues();
|
| 395 |
+
return parent::setProduct($product);
|
| 396 |
+
}
|
| 397 |
+
|
| 398 |
+
/**
|
| 399 |
+
* @param Mage_Core_Model_Store $store
|
| 400 |
+
*/
|
| 401 |
+
public function setStore($store)
|
| 402 |
+
{
|
| 403 |
+
$this->invalidateCachedValues();
|
| 404 |
+
return parent::setStore($store);
|
| 405 |
+
}
|
| 406 |
+
|
| 407 |
+
/**
|
| 408 |
+
* @return Boolean Whether we should render the placeholder span.
|
| 409 |
+
*/
|
| 410 |
+
public function shouldRenderButtonPlaceholder()
|
| 411 |
+
{
|
| 412 |
+
// 1. If no <script> tags to render, no placeholder
|
| 413 |
+
$scriptTags = $this->getScriptTags();
|
| 414 |
+
if (empty($scriptTags)) {
|
| 415 |
+
return false;
|
| 416 |
+
}
|
| 417 |
+
|
| 418 |
+
// If Button + Conversion tools disabled, no placeholder required.
|
| 419 |
+
// (Exit Intent can lead to button being shown later, so we need
|
| 420 |
+
// the placeholder present.)
|
| 421 |
+
if ($this->isButtonEnabled() || $this->isConversionToolsEnabled()) {
|
| 422 |
+
return true;
|
| 423 |
+
}
|
| 424 |
+
|
| 425 |
+
return false;
|
| 426 |
+
}
|
| 427 |
+
|
| 428 |
+
/**
|
| 429 |
+
* @internal Resets cached values
|
| 430 |
+
*/
|
| 431 |
+
protected function invalidateCachedValues()
|
| 432 |
+
{
|
| 433 |
+
$this->_isButtonEnabled = null;
|
| 434 |
+
$this->_isConversionToolsEnabled = null;
|
| 435 |
+
$this->_scriptTags = null;
|
| 436 |
+
}
|
| 437 |
+
|
| 438 |
+
/**
|
| 439 |
+
* @internal
|
| 440 |
+
* @return boolean
|
| 441 |
+
*/
|
| 442 |
+
protected function isFeatureEnabled(
|
| 443 |
+
$isEnabledForStore,
|
| 444 |
+
$isEnabledForCustomerGroup,
|
| 445 |
+
$isEnabledForAnyCategory
|
| 446 |
+
)
|
| 447 |
+
{
|
| 448 |
+
$store = $this->getStore();
|
| 449 |
+
$product = $this->getProduct();
|
| 450 |
+
|
| 451 |
+
if (!$store) {
|
| 452 |
+
return false;
|
| 453 |
+
}
|
| 454 |
+
|
| 455 |
+
if (!$product) {
|
| 456 |
+
return false;
|
| 457 |
+
}
|
| 458 |
+
|
| 459 |
+
$helper = Mage::helper('nypwidget');
|
| 460 |
+
|
| 461 |
+
if (!$helper->$isEnabledForStore($store)) {
|
| 462 |
+
// Store has us globally disabled
|
| 463 |
+
return false;
|
| 464 |
+
}
|
| 465 |
+
|
| 466 |
+
$customerGroupId = $this->getCustomerGroupId();
|
| 467 |
+
if (!$helper->$isEnabledForCustomerGroup($customerGroupId, $store)) {
|
| 468 |
+
// Disabled for customer group
|
| 469 |
+
return false;
|
| 470 |
+
}
|
| 471 |
+
|
| 472 |
+
$category = $this->getCategory();
|
| 473 |
+
if ($category) {
|
| 474 |
+
// We're looking at a product page via a specific category.
|
| 475 |
+
$enabled = $helper->$isEnabledForAnyCategory(
|
| 476 |
+
array($category),
|
| 477 |
+
$store
|
| 478 |
+
);
|
| 479 |
+
|
| 480 |
+
return $enabled;
|
| 481 |
+
}
|
| 482 |
+
|
| 483 |
+
// Look at *all* categories the product is in. If *any* of them
|
| 484 |
+
// enable the button, enable it here.
|
| 485 |
+
// We can hit this code path when viewing the product page *not*
|
| 486 |
+
// through the lens of a certain category (i.e. when linked from
|
| 487 |
+
// the home page or search results).
|
| 488 |
+
return $helper->$isEnabledForAnyCategory(
|
| 489 |
+
$product->getCategoryIds(),
|
| 490 |
+
$store
|
| 491 |
+
);
|
| 492 |
+
}
|
| 493 |
+
|
| 494 |
+
protected function safeGetAttributeText(Mage_Catalog_Model_Product $product, $code)
|
| 495 |
+
{
|
| 496 |
+
$value = $product->getData($code);
|
| 497 |
+
|
| 498 |
+
// prevent Magento from rendering "No" when nothing is selected.
|
| 499 |
+
if (!$value) {
|
| 500 |
+
return false;
|
| 501 |
+
}
|
| 502 |
+
|
| 503 |
+
$resource = $product->getResource();
|
| 504 |
+
if (!$resource) {
|
| 505 |
+
return false;
|
| 506 |
+
}
|
| 507 |
+
|
| 508 |
+
$attr = $resource->getAttribute($code);
|
| 509 |
+
if (!$attr) {
|
| 510 |
+
return false;
|
| 511 |
+
}
|
| 512 |
+
|
| 513 |
+
$frontend = $attr->getFrontend();
|
| 514 |
+
if (!$frontend) {
|
| 515 |
+
return false;
|
| 516 |
+
}
|
| 517 |
+
|
| 518 |
+
return $frontend->getValue($product);
|
| 519 |
+
}
|
| 520 |
+
}
|
app/code/community/PriceWaiter/NYPWidget/Model/Mysql4/Deal.php
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
class PriceWaiter_NYPWidget_Model_Mysql4_Deal extends Mage_Core_Model_Mysql4_Abstract
|
| 4 |
+
{
|
| 5 |
+
/**
|
| 6 |
+
* @internal
|
| 7 |
+
* deal_id is provided externally and used as the PK.
|
| 8 |
+
* @var boolean
|
| 9 |
+
*/
|
| 10 |
+
protected $_isPkAutoIncrement = false;
|
| 11 |
+
|
| 12 |
+
public function _construct()
|
| 13 |
+
{
|
| 14 |
+
$this->_init('nypwidget/deal', 'deal_id');
|
| 15 |
+
}
|
| 16 |
+
}
|
app/code/community/PriceWaiter/NYPWidget/Model/Mysql4/Deal/Collection.php
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
class PriceWaiter_NYPWidget_Model_Mysql4_Deal_Collection
|
| 4 |
+
extends Mage_Core_Model_Mysql4_Collection_Abstract
|
| 5 |
+
{
|
| 6 |
+
public function _construct()
|
| 7 |
+
{
|
| 8 |
+
$this->_init('nypwidget/deal', 'id');
|
| 9 |
+
}
|
| 10 |
+
}
|
app/code/community/PriceWaiter/NYPWidget/Model/Mysql4/Deal/Usage.php
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
class PriceWaiter_NYPWidget_Model_Mysql4_Deal_Usage extends Mage_Core_Model_Mysql4_Abstract
|
| 4 |
+
{
|
| 5 |
+
public function _construct()
|
| 6 |
+
{
|
| 7 |
+
$this->_init('nypwidget/deal_usage', '');
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
/**
|
| 11 |
+
* @param Mage_Sales_Model_Quote $quote
|
| 12 |
+
* @return Array An Array of Deal models.
|
| 13 |
+
*/
|
| 14 |
+
public function getDealsUsedByQuote(
|
| 15 |
+
Mage_Sales_Model_Quote $quote
|
| 16 |
+
)
|
| 17 |
+
{
|
| 18 |
+
$adapter = $this->_getReadAdapter();
|
| 19 |
+
$select = $adapter->select();
|
| 20 |
+
|
| 21 |
+
$select
|
| 22 |
+
->from($this->getMainTable(), array('deal_id'))
|
| 23 |
+
->where(
|
| 24 |
+
'quote_id = ?',
|
| 25 |
+
$quote->getId()
|
| 26 |
+
);
|
| 27 |
+
|
| 28 |
+
$ids = $adapter->fetchCol($select);
|
| 29 |
+
|
| 30 |
+
$collection = Mage::getModel('nypwidget/deal')
|
| 31 |
+
->getCollection()
|
| 32 |
+
->addFieldToFilter(
|
| 33 |
+
'deal_id',
|
| 34 |
+
array('in' => $ids)
|
| 35 |
+
);
|
| 36 |
+
|
| 37 |
+
return $collection->getItems();
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
/**
|
| 41 |
+
* @param PriceWaiter_NYPWidget_Model_Deal|string $deal
|
| 42 |
+
* @return Array IDs of quotes that have used $deal.
|
| 43 |
+
*/
|
| 44 |
+
public function getQuoteIdsUsingDeal(
|
| 45 |
+
$deal
|
| 46 |
+
)
|
| 47 |
+
{
|
| 48 |
+
$adapter = $this->_getReadAdapter();
|
| 49 |
+
$select = $adapter->select();
|
| 50 |
+
|
| 51 |
+
$select
|
| 52 |
+
->from($this->getMainTable(), array('quote_id'))
|
| 53 |
+
->where(
|
| 54 |
+
'deal_id = ?',
|
| 55 |
+
is_object($deal) ? $deal->getId() : $deal
|
| 56 |
+
);
|
| 57 |
+
|
| 58 |
+
$result = $adapter->fetchCol($select);
|
| 59 |
+
return $result;
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
/**
|
| 63 |
+
* Looks up the (unique) ids of orders that use any of the given deals.
|
| 64 |
+
* @param array $dealIds
|
| 65 |
+
* @return array Array of order ids
|
| 66 |
+
*/
|
| 67 |
+
public function getOrderIdsForDealIds(array $dealIds)
|
| 68 |
+
{
|
| 69 |
+
$dealIds = array_unique($dealIds);
|
| 70 |
+
|
| 71 |
+
if (empty($dealIds)) {
|
| 72 |
+
return array();
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
$adapter = $this->_getReadAdapter();
|
| 76 |
+
$select = $adapter->select();
|
| 77 |
+
|
| 78 |
+
$select
|
| 79 |
+
->distinct()
|
| 80 |
+
->from(
|
| 81 |
+
array(
|
| 82 |
+
'deal' => $this->getTable('nypwidget/deal'),
|
| 83 |
+
),
|
| 84 |
+
array('order_id')
|
| 85 |
+
)
|
| 86 |
+
->join(
|
| 87 |
+
array('order' => $this->getTable('sales/order')),
|
| 88 |
+
'order.entity_id = deal.order_id',
|
| 89 |
+
array()
|
| 90 |
+
)
|
| 91 |
+
// We're *really* not concerned with orders in certain states.
|
| 92 |
+
->where('order.state NOT IN (?)', array(
|
| 93 |
+
Mage_Sales_Model_Order::STATE_CANCELED,
|
| 94 |
+
))
|
| 95 |
+
->where('deal.deal_id IN (?)', $dealIds);
|
| 96 |
+
|
| 97 |
+
return $adapter->fetchCol($select);
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
/**
|
| 101 |
+
* Given an array of order entity_ids, returns an array
|
| 102 |
+
* array(
|
| 103 |
+
* 'order_id_1' => array('deal_id_1', 'deal_id_2')
|
| 104 |
+
* )
|
| 105 |
+
* Describing the deals applied to the orders.
|
| 106 |
+
* Any orders without deals will be excluded from the results.
|
| 107 |
+
* @param array $orderIds
|
| 108 |
+
* @return array
|
| 109 |
+
*/
|
| 110 |
+
public function getDealUsageForOrderIds(array $orderIds)
|
| 111 |
+
{
|
| 112 |
+
$orderIds = array_unique($orderIds);
|
| 113 |
+
|
| 114 |
+
$adapter = $this->_getReadAdapter();
|
| 115 |
+
$select = $adapter->select();
|
| 116 |
+
|
| 117 |
+
$select
|
| 118 |
+
->from($this->getTable('nypwidget/deal'), array('deal_id', 'order_id'))
|
| 119 |
+
->where('order_id in (?)', $orderIds);
|
| 120 |
+
|
| 121 |
+
$rows = $adapter->fetchAll($select);
|
| 122 |
+
$result = array();
|
| 123 |
+
|
| 124 |
+
foreach($rows as $row) {
|
| 125 |
+
$orderId = $row['order_id'];
|
| 126 |
+
$dealId = $row['deal_id'];
|
| 127 |
+
|
| 128 |
+
if (!isset($result[$orderId])) {
|
| 129 |
+
$result[$orderId] = array();
|
| 130 |
+
}
|
| 131 |
+
$result[$orderId][] = $dealId;
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
return $result;
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
/**
|
| 138 |
+
* @param array $dealIds
|
| 139 |
+
* @return array An array of arrays, each with 'order' and 'dealIds' keys.
|
| 140 |
+
*/
|
| 141 |
+
public function getOrdersAndDealUsageForDealIds(array $dealIds)
|
| 142 |
+
{
|
| 143 |
+
// This method does all the work needed to turn an abitrary set of deal
|
| 144 |
+
// ids into order + deal usage information in a minimal number of DB queries:
|
| 145 |
+
|
| 146 |
+
// 1. Translate $dealIds into an array of order ids
|
| 147 |
+
$orderIds = $this->getOrderIdsForDealIds($dealIds);
|
| 148 |
+
|
| 149 |
+
// 2. Look up all deal usage for those order ids (including those not in $dealIds)
|
| 150 |
+
$dealUsage = $this->getDealUsageForOrderIds($orderIds);
|
| 151 |
+
|
| 152 |
+
// 3. Query for order models by id
|
| 153 |
+
$orders = Mage::getModel('sales/order')
|
| 154 |
+
->getCollection()
|
| 155 |
+
->addFieldToFilter('entity_id', array('in' => $orderIds))
|
| 156 |
+
->getItems();
|
| 157 |
+
|
| 158 |
+
// Finally, stitch together order models with arrays of deals
|
| 159 |
+
$result = array();
|
| 160 |
+
|
| 161 |
+
/** @var Mage_Sales_Model_Order $order */
|
| 162 |
+
foreach($orders as $order) {
|
| 163 |
+
$result[] = array(
|
| 164 |
+
'order' => $order,
|
| 165 |
+
'dealIds' => $dealUsage[$order->getId()],
|
| 166 |
+
);
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
return $result;
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
/**
|
| 173 |
+
* Records a set of zero or more Deals used on a quote.
|
| 174 |
+
* Previous links between Deal <-> Quote are overwritten.
|
| 175 |
+
*
|
| 176 |
+
* @param Mage_Sales_Model_Quote $quote
|
| 177 |
+
* @param array $deals Array of Deal models or Deal ids.
|
| 178 |
+
*/
|
| 179 |
+
public function recordDealUsageForQuote(
|
| 180 |
+
Mage_Sales_Model_Quote $quote,
|
| 181 |
+
array $deals
|
| 182 |
+
)
|
| 183 |
+
{
|
| 184 |
+
$quoteId = $quote->getId();
|
| 185 |
+
|
| 186 |
+
// In normal circumstances, $quote will have an ID.
|
| 187 |
+
// There are *some* times when it is technically possible for it
|
| 188 |
+
// not to (like if someone is fiddling with the cart in code).
|
| 189 |
+
if (!$quoteId) {
|
| 190 |
+
// We can't record usage without a quote id.
|
| 191 |
+
return;
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
$rowsToInsert = array();
|
| 195 |
+
foreach ($deals as $deal) {
|
| 196 |
+
$rowsToInsert[] = array(
|
| 197 |
+
'deal_id' => is_object($deal) ? $deal->getId() : $deal,
|
| 198 |
+
'quote_id' => $quoteId,
|
| 199 |
+
);
|
| 200 |
+
}
|
| 201 |
+
|
| 202 |
+
$adapter = $this->_getWriteAdapter();
|
| 203 |
+
$adapter->beginTransaction();
|
| 204 |
+
try
|
| 205 |
+
{
|
| 206 |
+
$adapter->delete(
|
| 207 |
+
$this->getMainTable(),
|
| 208 |
+
array(
|
| 209 |
+
'quote_id = ?' => $quote->getId(),
|
| 210 |
+
)
|
| 211 |
+
);
|
| 212 |
+
|
| 213 |
+
if (!empty($rowsToInsert)) {
|
| 214 |
+
$adapter->insertArray(
|
| 215 |
+
$this->getMainTable(),
|
| 216 |
+
array('deal_id', 'quote_id'),
|
| 217 |
+
$rowsToInsert
|
| 218 |
+
);
|
| 219 |
+
}
|
| 220 |
+
} catch (Exception $ex)
|
| 221 |
+
{
|
| 222 |
+
$adapter->rollBack();
|
| 223 |
+
throw $ex;
|
| 224 |
+
}
|
| 225 |
+
|
| 226 |
+
$adapter->commit();
|
| 227 |
+
}
|
| 228 |
+
}
|
app/code/community/PriceWaiter/NYPWidget/Model/Observer.php
CHANGED
|
@@ -30,4 +30,52 @@ class PriceWaiter_NYPWidget_Model_Observer
|
|
| 30 |
|
| 31 |
return true;
|
| 32 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
}
|
| 30 |
|
| 31 |
return true;
|
| 32 |
}
|
| 33 |
+
|
| 34 |
+
/**
|
| 35 |
+
* Called when the customer logs out.
|
| 36 |
+
* @param Varien_Event_Observer $observer
|
| 37 |
+
*/
|
| 38 |
+
public function handleCustomerLogout(Varien_Event_Observer $observer)
|
| 39 |
+
{
|
| 40 |
+
// Reset the PriceWaiter Buyer ID in session
|
| 41 |
+
$session = Mage::getSingleton('nypwidget/session');
|
| 42 |
+
$session->reset();
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
/**
|
| 46 |
+
* Called when a quote is converted into an order.
|
| 47 |
+
* Used to tie the order to any PriceWaiter Deals used to establish the pricing.
|
| 48 |
+
*
|
| 49 |
+
* @param Varien_Event_Observer $observer
|
| 50 |
+
*/
|
| 51 |
+
public function tieOrderToPriceWaiterDeals(Varien_Event_Observer $observer)
|
| 52 |
+
{
|
| 53 |
+
$event = $observer->getEvent();
|
| 54 |
+
$quote = $event->getQuote();
|
| 55 |
+
$order = $event->getOrder();
|
| 56 |
+
|
| 57 |
+
try
|
| 58 |
+
{
|
| 59 |
+
$res = Mage::getResourceModel('nypwidget/deal_usage');
|
| 60 |
+
$deals = $res->getDealsUsedByQuote($quote);
|
| 61 |
+
|
| 62 |
+
if (empty($deals)) {
|
| 63 |
+
return;
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
$transaction = Mage::getModel('core/resource_transaction');
|
| 67 |
+
|
| 68 |
+
foreach($deals as $deal) {
|
| 69 |
+
$deal->setOrderId($order->getId());
|
| 70 |
+
$transaction->addObject($deal);
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
$transaction->save();
|
| 74 |
+
}
|
| 75 |
+
catch (Exception $ex)
|
| 76 |
+
{
|
| 77 |
+
// Never let our code prevent an order from being processed.
|
| 78 |
+
Mage::logException($ex);
|
| 79 |
+
}
|
| 80 |
+
}
|
| 81 |
}
|
app/code/community/PriceWaiter/NYPWidget/Model/Offer/Item.php
ADDED
|
@@ -0,0 +1,501 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
/**
|
| 4 |
+
* Represents a single item in a PriceWaiter Offer / Deal.
|
| 5 |
+
*
|
| 6 |
+
* This item can map to more than 1 Magento product (for example, if the offer
|
| 7 |
+
* was for a bundle product).
|
| 8 |
+
*/
|
| 9 |
+
class PriceWaiter_NYPWidget_Model_Offer_Item
|
| 10 |
+
{
|
| 11 |
+
/**
|
| 12 |
+
* Key in metadata that holds the serialized add to cart form.
|
| 13 |
+
*/
|
| 14 |
+
const ADD_TO_CART_FORM_METADATA_KEY = '_magento_product_configuration';
|
| 15 |
+
|
| 16 |
+
/**
|
| 17 |
+
* The absolute minimum quantity we support for things. Numbers lower
|
| 18 |
+
* than this will be set to this value.
|
| 19 |
+
*/
|
| 20 |
+
const MINIMUM_QUANTITY = 1;
|
| 21 |
+
|
| 22 |
+
/**
|
| 23 |
+
* @var Object
|
| 24 |
+
*/
|
| 25 |
+
private $_data;
|
| 26 |
+
|
| 27 |
+
/**
|
| 28 |
+
* @internal Cached array representation of item metadata.
|
| 29 |
+
* @var Array
|
| 30 |
+
*/
|
| 31 |
+
private $_metadataArray = null;
|
| 32 |
+
|
| 33 |
+
/**
|
| 34 |
+
* @internal Cached array of product option data.
|
| 35 |
+
* @var Array
|
| 36 |
+
*/
|
| 37 |
+
private $_optionsArray = null;
|
| 38 |
+
|
| 39 |
+
/**
|
| 40 |
+
* ID of the Magento store this item is for.
|
| 41 |
+
* @var integer
|
| 42 |
+
*/
|
| 43 |
+
protected $_storeId;
|
| 44 |
+
|
| 45 |
+
/**
|
| 46 |
+
* @param Object $data
|
| 47 |
+
* @param Object|integer $store Store (or just ID) this item is for.
|
| 48 |
+
*/
|
| 49 |
+
public function __construct($data = null, $store = null)
|
| 50 |
+
{
|
| 51 |
+
$this->_data = $data ? $data : array();
|
| 52 |
+
|
| 53 |
+
if ($store === null) {
|
| 54 |
+
$store = Mage::app()->getStore();
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
if ($store instanceof Mage_Core_Model_Store) {
|
| 58 |
+
$this->_storeId = $store->getId();
|
| 59 |
+
} else if (is_numeric($store)) {
|
| 60 |
+
$this->_storeId = $store;
|
| 61 |
+
} else {
|
| 62 |
+
throw new InvalidArgumentException(__CLASS__ . ' constructor requires store.');
|
| 63 |
+
}
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
/**
|
| 67 |
+
* Adds the product(s) contained in this Offer item to the given quote.
|
| 68 |
+
* @param Mage_Sales_Model_Quote $quote
|
| 69 |
+
* @return Mage_Sales_Model_Quote_Item The item added.
|
| 70 |
+
*/
|
| 71 |
+
public function addToQuote(Mage_Sales_Model_Quote $quote)
|
| 72 |
+
{
|
| 73 |
+
list($product, $addToCartForm, $handler) = $this->loadProduct();
|
| 74 |
+
|
| 75 |
+
return $handler->addProductToQuote(
|
| 76 |
+
$quote,
|
| 77 |
+
$product,
|
| 78 |
+
$addToCartForm,
|
| 79 |
+
$this->getMaximumQuantity()
|
| 80 |
+
);
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
/**
|
| 84 |
+
* Checks that the product(s) represented by this Offer Item is present
|
| 85 |
+
* (in at least the minimum qty) in the given quote.
|
| 86 |
+
*
|
| 87 |
+
* If not present, the product(s) is/are added to the quote.
|
| 88 |
+
* @param Mage_Sales_Model_Quote $quote
|
| 89 |
+
* @return Mage_Sales_Model_Quote_Item The quote item found or added.
|
| 90 |
+
*/
|
| 91 |
+
public function ensurePresentInQuote(Mage_Sales_Model_Quote $quote)
|
| 92 |
+
{
|
| 93 |
+
$items = $quote->getAllItems();
|
| 94 |
+
$item = $this->findQuoteItem($items);
|
| 95 |
+
|
| 96 |
+
try
|
| 97 |
+
{
|
| 98 |
+
if ($item) {
|
| 99 |
+
$minOk = $item->getQty() >= $this->getMinimumQuantity();
|
| 100 |
+
$maxOk = $item->getQty() <= $this->getMaximumQuantity();
|
| 101 |
+
|
| 102 |
+
if ($minOk && $maxOk) {
|
| 103 |
+
// Already in quote, no update required.
|
| 104 |
+
return $item;
|
| 105 |
+
} else if (!$minOk) {
|
| 106 |
+
// Need to bring quantity *up* to minimum
|
| 107 |
+
$item->setQty($this->getMinimumQuantity());
|
| 108 |
+
} else if (!$maxOk) {
|
| 109 |
+
// Need to bring quantity *down* to maximum
|
| 110 |
+
$item->setQty($this->getMaximumQuantity());
|
| 111 |
+
}
|
| 112 |
+
} else {
|
| 113 |
+
// Item is not even present in quote. So add it.
|
| 114 |
+
$item = $this->addToQuote($quote);
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
return $item;
|
| 118 |
+
}
|
| 119 |
+
catch (Exception $ex)
|
| 120 |
+
{
|
| 121 |
+
$translatedEx = PriceWaiter_NYPWidget_Exception_Abstract::translateMagentoException($ex);
|
| 122 |
+
throw $translatedEx;
|
| 123 |
+
}
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
/**
|
| 127 |
+
* Given a set of Mage_Sales_Model_Quote_Item instances, returns the *1*
|
| 128 |
+
* that (when also considering its children) contains the product(s)
|
| 129 |
+
* contained in this PriceWaiter order.
|
| 130 |
+
*
|
| 131 |
+
* NOTE: This does *not* consider min and max quantity--the result is based
|
| 132 |
+
* solely on whether the product in the quote item is the same product
|
| 133 |
+
* we're considering.
|
| 134 |
+
*
|
| 135 |
+
* If no matching quote item is found, returns false.
|
| 136 |
+
* @param array $items
|
| 137 |
+
* @return Mage_Sales_Model_Quote_Item|false
|
| 138 |
+
*/
|
| 139 |
+
public function findQuoteItem(array $items)
|
| 140 |
+
{
|
| 141 |
+
list($product, $cart, $handler) = $this->loadProduct();
|
| 142 |
+
|
| 143 |
+
// $handler handles trickiness associated with bundle products etc.
|
| 144 |
+
$item = $handler->findQuoteItem($product, $cart, $items);
|
| 145 |
+
|
| 146 |
+
if (!$item) {
|
| 147 |
+
return false;
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
return $item;
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
/**
|
| 154 |
+
* Attempts to read a value from the serialized Magento Add to Cart form
|
| 155 |
+
* stored in the Offer metadata. Uses parse_str internally, so array-style
|
| 156 |
+
* keys[] are expanded into arrays.
|
| 157 |
+
* If called without arguments, returns the full contents of the
|
| 158 |
+
* add to cart form as an array.
|
| 159 |
+
* @param String $key
|
| 160 |
+
* @param Mixed $default
|
| 161 |
+
* @return Mixed
|
| 162 |
+
*/
|
| 163 |
+
public function getAddToCartForm($key = null, $default = null)
|
| 164 |
+
{
|
| 165 |
+
$values = $this->getAllAddToCartFormValues();
|
| 166 |
+
|
| 167 |
+
// Allow getAddToCartForm() to return full form. (like getMetadata()).
|
| 168 |
+
if (func_num_args() === 0) {
|
| 169 |
+
return $values;
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
return array_key_exists($key, $values) ?
|
| 173 |
+
$values[$key] :
|
| 174 |
+
$default;
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
/**
|
| 178 |
+
* @return Number Amount per item offered (as a decimal).
|
| 179 |
+
*/
|
| 180 |
+
public function getAmountPerItem()
|
| 181 |
+
{
|
| 182 |
+
return doubleval($this->get('amount_per_item.value', 0));
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
/**
|
| 186 |
+
* @return String The 3-letter ISO currency code the offer was made in.
|
| 187 |
+
*/
|
| 188 |
+
public function getCurrencyCode()
|
| 189 |
+
{
|
| 190 |
+
return $this->get('currency');
|
| 191 |
+
}
|
| 192 |
+
|
| 193 |
+
/**
|
| 194 |
+
* @return PriceWaiter_NYPWidget_Model_Offer_Inventory
|
| 195 |
+
*/
|
| 196 |
+
public function getInventory()
|
| 197 |
+
{
|
| 198 |
+
list($product, $cart, $handler) = $this->loadProduct();
|
| 199 |
+
return $handler->getInventory($product, $cart);
|
| 200 |
+
}
|
| 201 |
+
|
| 202 |
+
/**
|
| 203 |
+
* @return PriceWaiter_NYPWidget_Model_Offer_Pricing
|
| 204 |
+
*/
|
| 205 |
+
public function getPricing()
|
| 206 |
+
{
|
| 207 |
+
list($product, $cart, $handler) = $this->loadProduct();
|
| 208 |
+
return $handler->getPricing($product, $cart);
|
| 209 |
+
}
|
| 210 |
+
|
| 211 |
+
/**
|
| 212 |
+
* @return Integer Maximum quantity of this item that can be purchased.
|
| 213 |
+
*/
|
| 214 |
+
public function getMaximumQuantity()
|
| 215 |
+
{
|
| 216 |
+
$max = $this->get('quantity.max', null);
|
| 217 |
+
|
| 218 |
+
if (!is_numeric($max)) {
|
| 219 |
+
return $this->getMinimumQuantity();
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
return max($max, $this->getMinimumQuantity());
|
| 223 |
+
}
|
| 224 |
+
|
| 225 |
+
/**
|
| 226 |
+
* Returns the URL of the Magento product referenced by this item.
|
| 227 |
+
* @throws PriceWaiter_NYPWidget_Exception_Product_NotFound
|
| 228 |
+
* @return String
|
| 229 |
+
*/
|
| 230 |
+
public function getMagentoProductUrl()
|
| 231 |
+
{
|
| 232 |
+
list($product) = $this->loadProduct();
|
| 233 |
+
return $product->getProductUrl();
|
| 234 |
+
}
|
| 235 |
+
|
| 236 |
+
/**
|
| 237 |
+
* Attempts to read a string value from the Offer's metadata.
|
| 238 |
+
* If you don't pass in any arguments, returns the full metadata array.
|
| 239 |
+
* @param String $key
|
| 240 |
+
* @param Mixed $default
|
| 241 |
+
* @return Mixed
|
| 242 |
+
*/
|
| 243 |
+
public function getMetadata($key = null, $default = null)
|
| 244 |
+
{
|
| 245 |
+
// Read metadata out of _data and into a standard array first
|
| 246 |
+
if (is_null($this->_metadataArray)) {
|
| 247 |
+
|
| 248 |
+
$this->_metadataArray = array();
|
| 249 |
+
|
| 250 |
+
$m = $this->get('metadata');
|
| 251 |
+
|
| 252 |
+
if ($m) {
|
| 253 |
+
foreach ($m as $k => $v) {
|
| 254 |
+
$this->_metadataArray[$k] = $v;
|
| 255 |
+
}
|
| 256 |
+
}
|
| 257 |
+
}
|
| 258 |
+
|
| 259 |
+
if (func_num_args() === 0) {
|
| 260 |
+
// No args = return all metadata
|
| 261 |
+
return $this->_metadataArray;
|
| 262 |
+
}
|
| 263 |
+
|
| 264 |
+
return array_key_exists($key, $this->_metadataArray) ?
|
| 265 |
+
$this->_metadataArray[$key] :
|
| 266 |
+
$default;
|
| 267 |
+
}
|
| 268 |
+
|
| 269 |
+
/**
|
| 270 |
+
* @return Integer The minimum quantity of this item that must be purchased.
|
| 271 |
+
*/
|
| 272 |
+
public function getMinimumQuantity()
|
| 273 |
+
{
|
| 274 |
+
$min = $this->get('quantity.min', null);
|
| 275 |
+
|
| 276 |
+
if (is_null($min)) {
|
| 277 |
+
// Allow specifying min+max with a single number
|
| 278 |
+
$min = $this->get('quantity');
|
| 279 |
+
}
|
| 280 |
+
|
| 281 |
+
if (!is_numeric($min)) {
|
| 282 |
+
return self::MINIMUM_QUANTITY;
|
| 283 |
+
}
|
| 284 |
+
|
| 285 |
+
return max(self::MINIMUM_QUANTITY, $min);
|
| 286 |
+
}
|
| 287 |
+
|
| 288 |
+
/**
|
| 289 |
+
* @return String The reported name of the product.
|
| 290 |
+
*/
|
| 291 |
+
public function getProductName()
|
| 292 |
+
{
|
| 293 |
+
return (string)$this->get('product.name');
|
| 294 |
+
}
|
| 295 |
+
|
| 296 |
+
/**
|
| 297 |
+
* @return Array An associative array whose keys are product option names and values are the associated values.
|
| 298 |
+
*/
|
| 299 |
+
public function getProductOptions()
|
| 300 |
+
{
|
| 301 |
+
if (!is_null($this->_optionsArray)) {
|
| 302 |
+
return $this->_optionsArray;
|
| 303 |
+
}
|
| 304 |
+
|
| 305 |
+
$this->_optionsArray = array();
|
| 306 |
+
|
| 307 |
+
$options = $this->get('product.options', null);
|
| 308 |
+
if ($options) {
|
| 309 |
+
foreach ($options as $o) {
|
| 310 |
+
$name = (string)self::_get($o, 'name', '');
|
| 311 |
+
$value = (string)self::_get($o, 'value', '');
|
| 312 |
+
$this->_optionsArray[$name] = $value;
|
| 313 |
+
}
|
| 314 |
+
}
|
| 315 |
+
|
| 316 |
+
return $this->_optionsArray;
|
| 317 |
+
}
|
| 318 |
+
|
| 319 |
+
/**
|
| 320 |
+
* @return String The SKU of the product on the PriceWaiter offer.
|
| 321 |
+
*/
|
| 322 |
+
public function getProductSku()
|
| 323 |
+
{
|
| 324 |
+
return (string)$this->get('product.sku');
|
| 325 |
+
}
|
| 326 |
+
|
| 327 |
+
/**
|
| 328 |
+
* @return Integer ID of the store this Offer Item is meant for.
|
| 329 |
+
*/
|
| 330 |
+
public function getStoreId()
|
| 331 |
+
{
|
| 332 |
+
return $this->_storeId;
|
| 333 |
+
}
|
| 334 |
+
|
| 335 |
+
/**
|
| 336 |
+
* @return Boolean
|
| 337 |
+
*/
|
| 338 |
+
public function quoteItemMeetsQuantityRequirements(Mage_Sales_Model_Quote_Item $item)
|
| 339 |
+
{
|
| 340 |
+
$qty = $item->getQty();
|
| 341 |
+
return ($qty >= $this->getMinimumQuantity()) && ($qty <= $this->getMaximumQuantity());
|
| 342 |
+
}
|
| 343 |
+
|
| 344 |
+
/**
|
| 345 |
+
* @param Array $addToCartForm
|
| 346 |
+
* @return PriceWaiter_NYPWidget_Model_Offer_Item A clone of this item with a new add to cart form.
|
| 347 |
+
*/
|
| 348 |
+
public function withAddToCartForm(array $addToCartForm)
|
| 349 |
+
{
|
| 350 |
+
$metadata = $this->getMetadata();
|
| 351 |
+
$metadata[self::ADD_TO_CART_FORM_METADATA_KEY] = http_build_query($addToCartForm, '', '&');
|
| 352 |
+
|
| 353 |
+
$newData = $this->_data;
|
| 354 |
+
$newData['metadata'] = $metadata;
|
| 355 |
+
|
| 356 |
+
return new self($newData, $this->_storeId);
|
| 357 |
+
}
|
| 358 |
+
|
| 359 |
+
/**
|
| 360 |
+
* @internal
|
| 361 |
+
*/
|
| 362 |
+
protected function get($key, $default = null)
|
| 363 |
+
{
|
| 364 |
+
return self::_get($this->_data, $key, $default);
|
| 365 |
+
}
|
| 366 |
+
|
| 367 |
+
/**
|
| 368 |
+
* @internal
|
| 369 |
+
* @return Array The full add to cart form.
|
| 370 |
+
*/
|
| 371 |
+
protected function getAllAddToCartFormValues()
|
| 372 |
+
{
|
| 373 |
+
// Magento platform JS serializes the add to cart form into metadata.
|
| 374 |
+
$serializedAddToCartForm = $this->getMetadata(self::ADD_TO_CART_FORM_METADATA_KEY, null);
|
| 375 |
+
|
| 376 |
+
if (is_null($serializedAddToCartForm)) {
|
| 377 |
+
return array();
|
| 378 |
+
}
|
| 379 |
+
|
| 380 |
+
// Ok. $serializedAddToCartForm is an url-encoded form string like
|
| 381 |
+
// 'foo=bar&baz=bat'
|
| 382 |
+
|
| 383 |
+
$addToCartFormValues = array();
|
| 384 |
+
parse_str($serializedAddToCartForm, $addToCartFormValues);
|
| 385 |
+
|
| 386 |
+
// HACK: Ok, for some reason we are currently double-encoding the contents
|
| 387 |
+
// of this key--platform JS is taking the serialized form string and
|
| 388 |
+
// passing it through encodeURIComponent(). Here we detect that and try again.
|
| 389 |
+
if (count($addToCartFormValues) === 1) {
|
| 390 |
+
|
| 391 |
+
// $values *might* look something like
|
| 392 |
+
// `array('field=value&field2=value' => '')`
|
| 393 |
+
// which would indicate it was double-encoded.
|
| 394 |
+
|
| 395 |
+
$keys = array_keys($addToCartFormValues);
|
| 396 |
+
$values = array_values($addToCartFormValues);
|
| 397 |
+
|
| 398 |
+
$looksDoubleEncoded = (
|
| 399 |
+
$values[0] === '' &&
|
| 400 |
+
strpos($keys[0], '&') !== false
|
| 401 |
+
);
|
| 402 |
+
|
| 403 |
+
if ($looksDoubleEncoded) {
|
| 404 |
+
$addToCartFormValues = array();
|
| 405 |
+
parse_str($keys[0], $addToCartFormValues);
|
| 406 |
+
}
|
| 407 |
+
}
|
| 408 |
+
|
| 409 |
+
return $addToCartFormValues;
|
| 410 |
+
}
|
| 411 |
+
|
| 412 |
+
/**
|
| 413 |
+
* Returns a separate class that thinks about the dirty details of Magento
|
| 414 |
+
* products.
|
| 415 |
+
* @param Mage_Catalog_Model_Product $product
|
| 416 |
+
* @return PriceWaiter_NYPWidget_Model_Offer_Item_Handler
|
| 417 |
+
*/
|
| 418 |
+
protected function getHandlerForProduct(Mage_Catalog_Model_Product $product)
|
| 419 |
+
{
|
| 420 |
+
$class = 'nypwidget/offer_item_handler';
|
| 421 |
+
$handler = Mage::getSingleton($class);
|
| 422 |
+
return $handler;
|
| 423 |
+
}
|
| 424 |
+
|
| 425 |
+
/**
|
| 426 |
+
* Returns an array with 3 elements:
|
| 427 |
+
*
|
| 428 |
+
* 1. The main Magento product this item refers to.
|
| 429 |
+
* 2. A Varien_Object of add to cart data
|
| 430 |
+
* 3. A handler instance to use to query for more information
|
| 431 |
+
*
|
| 432 |
+
* @return Array
|
| 433 |
+
* @throws PriceWaiter_NYPWidget_Exception_Product_NotFound
|
| 434 |
+
*/
|
| 435 |
+
protected function loadProduct()
|
| 436 |
+
{
|
| 437 |
+
// This method resembles what CartController::_initProduct does when
|
| 438 |
+
// reconstituting a product instance from add to cart data.
|
| 439 |
+
|
| 440 |
+
$addToCartForm = $this->getAddToCartForm();
|
| 441 |
+
$id = isset($addToCartForm['product']) ? $addToCartForm['product'] : null;
|
| 442 |
+
|
| 443 |
+
if (!$id) {
|
| 444 |
+
throw new PriceWaiter_NYPWidget_Exception_Product_NotFound('product not specified in add to cart form.');
|
| 445 |
+
}
|
| 446 |
+
|
| 447 |
+
$product = Mage::getModel('catalog/product')
|
| 448 |
+
->setStoreId($this->getStoreId())
|
| 449 |
+
->load($id);
|
| 450 |
+
|
| 451 |
+
if (!$product->getId()) {
|
| 452 |
+
throw new PriceWaiter_NYPWidget_Exception_Product_NotFound("Product with id '{$id}' not found.");
|
| 453 |
+
}
|
| 454 |
+
|
| 455 |
+
$handler = $this->getHandlerForProduct($product);
|
| 456 |
+
|
| 457 |
+
$addToCartForm = new Varien_Object($addToCartForm);
|
| 458 |
+
|
| 459 |
+
// Slight HACK: Ensure that we're always considering 1 of the main product at a time.
|
| 460 |
+
// We support > 1 qty for *child* products (such as items in a bundle),
|
| 461 |
+
// but don't want quantities to affect price calculations for parent products.
|
| 462 |
+
if ($addToCartForm->hasQty()) {
|
| 463 |
+
$addToCartForm->setQty(1);
|
| 464 |
+
}
|
| 465 |
+
|
| 466 |
+
return array(
|
| 467 |
+
$product,
|
| 468 |
+
$addToCartForm,
|
| 469 |
+
$handler,
|
| 470 |
+
);
|
| 471 |
+
}
|
| 472 |
+
|
| 473 |
+
/**
|
| 474 |
+
* @internal Reads dot.separated.keys off an object or array.
|
| 475 |
+
*/
|
| 476 |
+
private static function _get($obj, $key, $default = null)
|
| 477 |
+
{
|
| 478 |
+
$keyParts = is_array($key) ? $key : explode('.', $key);
|
| 479 |
+
$k = array_shift($keyParts);
|
| 480 |
+
|
| 481 |
+
if (is_object($obj)) {
|
| 482 |
+
if (isset($obj->$k)) {
|
| 483 |
+
if (count($keyParts) === 0) {
|
| 484 |
+
return $obj->$k;
|
| 485 |
+
} else {
|
| 486 |
+
return self::_get($obj->$k, $keyParts, $default);
|
| 487 |
+
}
|
| 488 |
+
}
|
| 489 |
+
} else if (is_array($obj)) {
|
| 490 |
+
if (array_key_exists($k, $obj)) {
|
| 491 |
+
if (count($keyParts) === 0) {
|
| 492 |
+
return $obj[$k];
|
| 493 |
+
} else {
|
| 494 |
+
return self::_get($obj[$k], $keyParts, $default);
|
| 495 |
+
}
|
| 496 |
+
}
|
| 497 |
+
}
|
| 498 |
+
|
| 499 |
+
return $default;
|
| 500 |
+
}
|
| 501 |
+
}
|
app/code/community/PriceWaiter/NYPWidget/Model/Offer/Item/Handler.php
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
/**
|
| 4 |
+
* Class responsible for actually touching Magento products to do things
|
| 5 |
+
* for PriceWaiter offers. Here be dragons.
|
| 6 |
+
*/
|
| 7 |
+
class PriceWaiter_NYPWidget_Model_Offer_Item_Handler
|
| 8 |
+
{
|
| 9 |
+
/**
|
| 10 |
+
* Adds the given product to the given quote.
|
| 11 |
+
* @param Mage_Sales_Model_Quote $quote
|
| 12 |
+
* @param Mage_Catalog_Model_Product $product
|
| 13 |
+
* @param Varien_Object $addToCartForm
|
| 14 |
+
* @param Integer $qty
|
| 15 |
+
* @return Mage_Sales_Model_Quote_Item
|
| 16 |
+
*/
|
| 17 |
+
public function addProductToQuote(
|
| 18 |
+
Mage_Sales_Model_Quote $quote,
|
| 19 |
+
Mage_Catalog_Model_Product $product,
|
| 20 |
+
Varien_Object $addToCartForm,
|
| 21 |
+
$qty
|
| 22 |
+
)
|
| 23 |
+
{
|
| 24 |
+
$addToCartForm = clone($addToCartForm);
|
| 25 |
+
$addToCartForm->setQty($qty);
|
| 26 |
+
|
| 27 |
+
return $quote->addProduct($product, $addToCartForm);
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
/**
|
| 31 |
+
* @param Mage_Catalog_Model_Product $product
|
| 32 |
+
* @param Varien_Object $addToCartForm
|
| 33 |
+
* @param array $quoteItems
|
| 34 |
+
* @return Mage_Sales_Model_Quote_Item|false
|
| 35 |
+
*/
|
| 36 |
+
public function findQuoteItem(
|
| 37 |
+
Mage_Catalog_Model_Product $product,
|
| 38 |
+
Varien_Object $addToCartForm,
|
| 39 |
+
array $quoteItems
|
| 40 |
+
)
|
| 41 |
+
{
|
| 42 |
+
$products = $this->getConfiguredProducts($product, $addToCartForm);
|
| 43 |
+
list($parent, $children) = $this->splitParentAndChildProducts($products);
|
| 44 |
+
|
| 45 |
+
foreach ($quoteItems as $quoteItem) {
|
| 46 |
+
$matches = $this->quoteItemMatches(
|
| 47 |
+
$quoteItem,
|
| 48 |
+
$parent,
|
| 49 |
+
$children
|
| 50 |
+
);
|
| 51 |
+
|
| 52 |
+
if ($matches) {
|
| 53 |
+
return $quoteItem;
|
| 54 |
+
}
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
return false;
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
/**
|
| 61 |
+
* Returns a structure describing the inventory tracking for this product.
|
| 62 |
+
* @param Mage_Catalog_Model_Product $product
|
| 63 |
+
* @param Varien_Object $addToCartForm
|
| 64 |
+
* @return PriceWaiter_NYPWidget_Model_Offer_Item_Inventory
|
| 65 |
+
*/
|
| 66 |
+
public function getInventory(
|
| 67 |
+
Mage_Catalog_Model_Product $product,
|
| 68 |
+
Varien_Object $addToCartForm
|
| 69 |
+
)
|
| 70 |
+
{
|
| 71 |
+
$products = $this->getConfiguredProducts($product, $addToCartForm);
|
| 72 |
+
return Mage::getModel('nypwidget/offer_item_inventory', $products);
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
/**
|
| 76 |
+
* @return PriceWaiter_NYPWidget_Model_Offer_Item_Pricing
|
| 77 |
+
*/
|
| 78 |
+
public function getPricing(
|
| 79 |
+
Mage_Catalog_Model_Product $product,
|
| 80 |
+
Varien_Object $addToCartForm
|
| 81 |
+
)
|
| 82 |
+
{
|
| 83 |
+
$products = $this->getConfiguredProducts($product, $addToCartForm);
|
| 84 |
+
|
| 85 |
+
$pricing = Mage::getModel(
|
| 86 |
+
'nypwidget/offer_item_pricing',
|
| 87 |
+
$products
|
| 88 |
+
);
|
| 89 |
+
|
| 90 |
+
return $pricing;
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
/**
|
| 94 |
+
* Returns a set of *configured* products for the given parent product / cart data combo.
|
| 95 |
+
* @param Mage_Catalog_Model_Product $product
|
| 96 |
+
* @param Varien_Object $cart
|
| 97 |
+
* @return array
|
| 98 |
+
*/
|
| 99 |
+
public function getConfiguredProducts(Mage_Catalog_Model_Product $product, Varien_Object $addToCartForm)
|
| 100 |
+
{
|
| 101 |
+
$type = $product->getTypeInstance();
|
| 102 |
+
$products = $type->prepareForCart($addToCartForm);
|
| 103 |
+
|
| 104 |
+
if (is_string($products)) {
|
| 105 |
+
// Magento communicates "add to cart" errors by returning a string here.
|
| 106 |
+
// Most likely, $addToCartForm does not contain data for all required
|
| 107 |
+
// product options.
|
| 108 |
+
$id = $product->getId();
|
| 109 |
+
|
| 110 |
+
throw new PriceWaiter_NYPWidget_Exception_Product_Invalid(
|
| 111 |
+
"Error preparing product (id: {$id}): {$products}"
|
| 112 |
+
);
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
return $products;
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
/**
|
| 119 |
+
* @param Mage_Catalog_Model_Product $product
|
| 120 |
+
* @return Array a simple array of [custom option id => value] for $product
|
| 121 |
+
*/
|
| 122 |
+
protected function buildCustomOptionArray(Mage_Catalog_Model_Product $product)
|
| 123 |
+
{
|
| 124 |
+
$result = array();
|
| 125 |
+
|
| 126 |
+
/** @var Mage_Catalog_Model_Product_Option $opt */
|
| 127 |
+
foreach($product->getOptions() as $opt) {
|
| 128 |
+
$code = 'option_' . $opt->getId();
|
| 129 |
+
$customOption = $product->getCustomOption($code);
|
| 130 |
+
|
| 131 |
+
if (!$customOption) {
|
| 132 |
+
continue;
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
$result[$opt->getId()] = $customOption->getValue();
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
return $result;
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
/**
|
| 142 |
+
* Tests that the products referred to by $childQuoteItems exactly
|
| 143 |
+
* matches the products in $childProducts.
|
| 144 |
+
* Used for matching quote items for non-simple products that
|
| 145 |
+
* exploit quote item hierarchy.
|
| 146 |
+
* @param array $childQuoteItems
|
| 147 |
+
* @param array $childProducts
|
| 148 |
+
* @return boolean
|
| 149 |
+
*/
|
| 150 |
+
protected function childQuoteItemsMatchChildProducts(
|
| 151 |
+
array $childQuoteItems,
|
| 152 |
+
array $childProducts
|
| 153 |
+
)
|
| 154 |
+
{
|
| 155 |
+
if (count($childQuoteItems) !== count($childProducts)) {
|
| 156 |
+
return false;
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
// Compare the product ids / quantities of the given quote items
|
| 160 |
+
// with the product ids / quantities of the products.
|
| 161 |
+
// This additional quantity check prevents matching quote items
|
| 162 |
+
// for bundle products where the same products are used but
|
| 163 |
+
// quantities differ.
|
| 164 |
+
|
| 165 |
+
$productIds = array();
|
| 166 |
+
foreach ($childProducts as $product) {
|
| 167 |
+
$id = strval($product->getId());
|
| 168 |
+
$productIds[$id] = strval($product->getCartQty());
|
| 169 |
+
}
|
| 170 |
+
|
| 171 |
+
$quoteItemProductIds = array();
|
| 172 |
+
foreach ($childQuoteItems as $quoteItem) {
|
| 173 |
+
$id = strval($quoteItem->getProductId());
|
| 174 |
+
$quoteItemProductIds[$id] = strval($quoteItem->getQty());
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
$diff = array_diff_assoc($productIds, $quoteItemProductIds);
|
| 178 |
+
|
| 179 |
+
return empty($diff);
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
protected function productCustomOptionsMatch(
|
| 183 |
+
Mage_Catalog_Model_Product $productA,
|
| 184 |
+
Mage_Catalog_Model_Product $productB
|
| 185 |
+
)
|
| 186 |
+
{
|
| 187 |
+
$customOptionsA = $this->buildCustomOptionArray($productA);
|
| 188 |
+
$customOptionsB = $this->buildCustomOptionArray($productB);
|
| 189 |
+
|
| 190 |
+
if (count($customOptionsA) !== count($customOptionsB)) {
|
| 191 |
+
return false;
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
$diff = array_diff_assoc($customOptionsA, $customOptionsB);
|
| 195 |
+
return empty($diff);
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
/**
|
| 199 |
+
* @param Mage_Sales_Model_Quote_Item $quoteItem
|
| 200 |
+
* @param Mage_Catalog_Model_Product $parent Parent product
|
| 201 |
+
* @param array $children Child products
|
| 202 |
+
* @return Boolean
|
| 203 |
+
*/
|
| 204 |
+
protected function quoteItemMatches(
|
| 205 |
+
Mage_Sales_Model_Quote_Item $quoteItem,
|
| 206 |
+
Mage_Catalog_Model_Product $parent,
|
| 207 |
+
array $children
|
| 208 |
+
)
|
| 209 |
+
{
|
| 210 |
+
// We only match against parent quote items.
|
| 211 |
+
// This prevents things like having PW deals apply to
|
| 212 |
+
// products *inside* bundles.
|
| 213 |
+
$isParent = !$quoteItem->getParentItemId();
|
| 214 |
+
if (!$isParent) {
|
| 215 |
+
return false;
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
$isSameProduct = $quoteItem->getProductId() == $parent->getId();
|
| 219 |
+
if (!$isSameProduct) {
|
| 220 |
+
return false;
|
| 221 |
+
}
|
| 222 |
+
|
| 223 |
+
$customOptionsMatch = $this->productCustomOptionsMatch(
|
| 224 |
+
$parent,
|
| 225 |
+
$quoteItem->getProduct()
|
| 226 |
+
);
|
| 227 |
+
|
| 228 |
+
if (!$customOptionsMatch) {
|
| 229 |
+
return false;
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
// Ok, This *parent* quote item matches well enough.
|
| 233 |
+
// But we also need to verify that any children of this quote item
|
| 234 |
+
// match the incoming child products (this is for configurable +
|
| 235 |
+
// bundle support).
|
| 236 |
+
|
| 237 |
+
$childQuoteItems = $quoteItem->getChildren();
|
| 238 |
+
return $this->childQuoteItemsMatchChildProducts($childQuoteItems, $children);
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
/**
|
| 242 |
+
* Splits an array of products into a single parent product and 0 or more
|
| 243 |
+
* child products.
|
| 244 |
+
* @param array $products
|
| 245 |
+
* @return array
|
| 246 |
+
*/
|
| 247 |
+
protected function splitParentAndChildProducts(array $products)
|
| 248 |
+
{
|
| 249 |
+
$parent = null;
|
| 250 |
+
$children = array();
|
| 251 |
+
|
| 252 |
+
/** @var Mage_Catalog_Model_Product $product */
|
| 253 |
+
foreach($products as $product) {
|
| 254 |
+
if ($product->getParentProductId()) {
|
| 255 |
+
$children[] = $product;
|
| 256 |
+
continue;
|
| 257 |
+
}
|
| 258 |
+
$parent = $product;
|
| 259 |
+
}
|
| 260 |
+
|
| 261 |
+
return array($parent, $children);
|
| 262 |
+
}
|
| 263 |
+
}
|
app/code/community/PriceWaiter/NYPWidget/Model/Offer/Item/Inventory.php
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
/**
|
| 4 |
+
* Layer that adapts the way Magento thinks about inventory to the way
|
| 5 |
+
* PriceWaiter thinks about inventory.
|
| 6 |
+
*/
|
| 7 |
+
class PriceWaiter_NYPWidget_Model_Offer_Item_Inventory
|
| 8 |
+
{
|
| 9 |
+
protected $_products;
|
| 10 |
+
protected $_productsWithStockItems = null;
|
| 11 |
+
|
| 12 |
+
public function __construct(array $products)
|
| 13 |
+
{
|
| 14 |
+
$this->_products = $products;
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
/**
|
| 18 |
+
* @return Boolean Whether backorders are allowed for this item.
|
| 19 |
+
*/
|
| 20 |
+
public function canBackorder()
|
| 21 |
+
{
|
| 22 |
+
// NOTE: Any *one* item not being backorderable means we can't consider
|
| 23 |
+
// the group backorderable.
|
| 24 |
+
|
| 25 |
+
foreach($this->getProductsWithStockItems() as $p) {
|
| 26 |
+
list($product, $stockItem) = $p;
|
| 27 |
+
|
| 28 |
+
// Technically getBackorders() is a flag with multiple states but
|
| 29 |
+
// BACKORDERS_NO = 0 so this works.
|
| 30 |
+
if (!$stockItem->getBackorders()) {
|
| 31 |
+
return false;
|
| 32 |
+
}
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
return true;
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
/**
|
| 39 |
+
* @return Integer|false The current # in stock (or false if unknown).
|
| 40 |
+
*/
|
| 41 |
+
public function getStock()
|
| 42 |
+
{
|
| 43 |
+
$result = false;
|
| 44 |
+
|
| 45 |
+
foreach($this->getProductsWithStockItems() as $p) {
|
| 46 |
+
list($product, $stockItem) = $p;
|
| 47 |
+
|
| 48 |
+
if (!$stockItem->getManageStock()) {
|
| 49 |
+
// Not tracking stock for this item, so ignore.
|
| 50 |
+
continue;
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
// Since we are considering the products as a group, return the
|
| 54 |
+
// *minimum* quantity available.
|
| 55 |
+
$qty = $this->getQty($product, $stockItem);
|
| 56 |
+
|
| 57 |
+
if ($result === false || $qty < $result) {
|
| 58 |
+
$result = $qty;
|
| 59 |
+
}
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
return $result === false ? $result : intval($result);
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
/**
|
| 66 |
+
* @param Mage_Catalog_Model_Product $product
|
| 67 |
+
* @param Mage_CatalogInventory_Model_Stock_Item $stockItem
|
| 68 |
+
* @return Integer the # of the product available.
|
| 69 |
+
*/
|
| 70 |
+
protected function getQty(
|
| 71 |
+
Mage_Catalog_Model_Product $product,
|
| 72 |
+
Mage_CatalogInventory_Model_Stock_Item $stockItem
|
| 73 |
+
)
|
| 74 |
+
{
|
| 75 |
+
// For products that are part of a bundle, we have to
|
| 76 |
+
// return how many are available in that increment.
|
| 77 |
+
//
|
| 78 |
+
// So if bundle contains 2 x Shirt, and Shirt has 100 left,
|
| 79 |
+
// that means the effective quantity for the Shirt in the bundle
|
| 80 |
+
// is 50 (100 / 2).
|
| 81 |
+
$increment = $product->getCartQty();
|
| 82 |
+
|
| 83 |
+
if ($increment > 1) {
|
| 84 |
+
return floor($stockItem->getQty() / $increment);
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
// Ordinarily, though, we just use the stock item's quantity.
|
| 88 |
+
return $stockItem->getQty();
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
/**
|
| 92 |
+
* @return array The set of stock items for the products being considered.
|
| 93 |
+
*/
|
| 94 |
+
protected function getProductsWithStockItems()
|
| 95 |
+
{
|
| 96 |
+
if ($this->_productsWithStockItems !== null) {
|
| 97 |
+
return $this->_productsWithStockItems;
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
$this->_productsWithStockItems = array();
|
| 101 |
+
|
| 102 |
+
// If we have any child products, only return stock items for those.
|
| 103 |
+
// Otherwise (for e.g. grouped products), return stock items for *all* products.
|
| 104 |
+
$haveAnyChildren = false;
|
| 105 |
+
|
| 106 |
+
foreach($this->_products as $product) {
|
| 107 |
+
$isChild = !!$product->getParentProductId();
|
| 108 |
+
if ($isChild) {
|
| 109 |
+
$haveAnyChildren = true;
|
| 110 |
+
break;
|
| 111 |
+
}
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
foreach($this->_products as $product) {
|
| 115 |
+
$isChild = !!$product->getParentProductId();
|
| 116 |
+
|
| 117 |
+
if ($haveAnyChildren && !$isChild) {
|
| 118 |
+
// Ignore parents for inventory purposes
|
| 119 |
+
continue;
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
$this->_productsWithStockItems[] = array($product, $product->getStockItem());
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
return $this->_productsWithStockItems;
|
| 126 |
+
}
|
| 127 |
+
}
|
app/code/community/PriceWaiter/NYPWidget/Model/Offer/Item/Pricing.php
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
/**
|
| 4 |
+
* Class that adapts how Magento thinks about prices to how PriceWaiter does.
|
| 5 |
+
*/
|
| 6 |
+
class PriceWaiter_NYPWidget_Model_Offer_Item_Pricing
|
| 7 |
+
{
|
| 8 |
+
protected $_products;
|
| 9 |
+
|
| 10 |
+
public function __construct(array $products)
|
| 11 |
+
{
|
| 12 |
+
$this->_products = $products;
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
public function getCost()
|
| 16 |
+
{
|
| 17 |
+
return $this->calculatePrice(
|
| 18 |
+
$this->getProductsForOtherPriceFields(),
|
| 19 |
+
'getCostForProduct'
|
| 20 |
+
);
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
/**
|
| 24 |
+
* @return Mage_Directory_Model_Currency
|
| 25 |
+
*/
|
| 26 |
+
public function getCurrency()
|
| 27 |
+
{
|
| 28 |
+
foreach($this->_products as $product) {
|
| 29 |
+
$store = Mage::app()->getStore($product->getStoreId());
|
| 30 |
+
return $store->getDefaultCurrency();
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
throw new RuntimeException("Cannot determine currency.");
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
/**
|
| 37 |
+
* @return string 3-character currency code.
|
| 38 |
+
*/
|
| 39 |
+
public function getCurrencyCode()
|
| 40 |
+
{
|
| 41 |
+
$currency = $this->getCurrency();
|
| 42 |
+
return $currency->getCode();
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
/**
|
| 46 |
+
* @return double|false Manufacturer's suggested retail price.
|
| 47 |
+
*/
|
| 48 |
+
public function getMsrp()
|
| 49 |
+
{
|
| 50 |
+
return $this->calculatePrice(
|
| 51 |
+
$this->getProductsForOtherPriceFields(),
|
| 52 |
+
'getMsrpForProduct'
|
| 53 |
+
);
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
/**
|
| 57 |
+
* @return double|false The regular or "compare at" price, if known.
|
| 58 |
+
*/
|
| 59 |
+
public function getRegularPrice()
|
| 60 |
+
{
|
| 61 |
+
$regular = $this->calculatePrice(
|
| 62 |
+
$this->getProductsForOtherPriceFields(),
|
| 63 |
+
'getRegularPriceForProduct'
|
| 64 |
+
);
|
| 65 |
+
|
| 66 |
+
if ($regular === false) {
|
| 67 |
+
return false;
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
// ONly return regular price if it is greater than than retail
|
| 71 |
+
$retail = $this->getRetailPrice();
|
| 72 |
+
if ($retail === false) {
|
| 73 |
+
// No retail = can't figure out regular price
|
| 74 |
+
return false;
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
// Only return "regular price" if it 0.01 or greater than retail
|
| 78 |
+
$diff = $regular - $retail;
|
| 79 |
+
if ($diff > 0.01) {
|
| 80 |
+
return $regular;
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
return false;
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
/**
|
| 87 |
+
* @return double|false The current full retail price of the product(s).
|
| 88 |
+
*/
|
| 89 |
+
public function getRetailPrice()
|
| 90 |
+
{
|
| 91 |
+
return $this->calculatePrice(
|
| 92 |
+
$this->getProductsForRetailPrice(),
|
| 93 |
+
'getRetailPriceForProduct'
|
| 94 |
+
);
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
/**
|
| 98 |
+
* @internal Aggregates the value returned by $getter for all products considered for pricing.
|
| 99 |
+
* @param array $products
|
| 100 |
+
* @param string $getter
|
| 101 |
+
* @return double|false
|
| 102 |
+
*/
|
| 103 |
+
protected function calculatePrice(array $products, $getter)
|
| 104 |
+
{
|
| 105 |
+
$result = false;
|
| 106 |
+
|
| 107 |
+
foreach($products as $product) {
|
| 108 |
+
$valueForProduct = $this->$getter($product);
|
| 109 |
+
if ($valueForProduct === false) {
|
| 110 |
+
// False for any 1 product = false for all.
|
| 111 |
+
return false;
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
$qty = $this->getEffectiveQtyForProduct($product);
|
| 115 |
+
$valueForProduct *= $qty;
|
| 116 |
+
|
| 117 |
+
if ($result === false) {
|
| 118 |
+
$result = $valueForProduct;
|
| 119 |
+
} else {
|
| 120 |
+
$result += $valueForProduct;
|
| 121 |
+
}
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
return $result === false ? $result : doubleval($result);
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
/**
|
| 128 |
+
* @param Mage_Catalog_Model_Product $product
|
| 129 |
+
* @return double|false Retailer's cost for the given product.
|
| 130 |
+
*/
|
| 131 |
+
protected function getCostForProduct(Mage_Catalog_Model_Product $product)
|
| 132 |
+
{
|
| 133 |
+
$cost = $product->getCost();
|
| 134 |
+
return is_numeric($cost) ? doubleval($cost) : false;
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
/**
|
| 138 |
+
* @param Mage_Catalog_Model_Product $product
|
| 139 |
+
* @return integer The number of $product being bought.
|
| 140 |
+
*/
|
| 141 |
+
protected function getEffectiveQtyForProduct(Mage_Catalog_Model_Product $product)
|
| 142 |
+
{
|
| 143 |
+
$isChild = !!$product->getParentProductId();
|
| 144 |
+
if (!$isChild) {
|
| 145 |
+
// Don't consider quantity for parent products
|
| 146 |
+
return 1;
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
// For child products (i.e., items in a bundle, use cart qty as multiplier to determine price
|
| 150 |
+
|
| 151 |
+
$qty = $product->getCartQty();
|
| 152 |
+
|
| 153 |
+
if ($qty > 0) {
|
| 154 |
+
return intval($qty);
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
return 1;
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
/**
|
| 161 |
+
* @param Mage_Catalog_Model_Product $product
|
| 162 |
+
* @return double|false Manufacturer's suggested retail price (if known).
|
| 163 |
+
*/
|
| 164 |
+
protected function getMsrpForProduct(Mage_Catalog_Model_Product $product)
|
| 165 |
+
{
|
| 166 |
+
$msrp = $product->getMsrp();
|
| 167 |
+
|
| 168 |
+
if (is_numeric($msrp)) {
|
| 169 |
+
return doubleval($msrp);
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
return false;
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
+
/**
|
| 176 |
+
* @param Mage_Catalog_Model_Product $product
|
| 177 |
+
* @return double|false "Compare at" price for the product (if known).
|
| 178 |
+
*/
|
| 179 |
+
protected function getRegularPriceForProduct(Mage_Catalog_Model_Product $product)
|
| 180 |
+
{
|
| 181 |
+
$candidates = array();
|
| 182 |
+
|
| 183 |
+
$nonSpecialPrice = $product->getPrice();
|
| 184 |
+
if ($nonSpecialPrice > 0) {
|
| 185 |
+
$candidates[] = doubleval($nonSpecialPrice);
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
// Let MSRP factor in
|
| 189 |
+
$msrp = $this->getMsrpForProduct($product);
|
| 190 |
+
if ($msrp > 0) {
|
| 191 |
+
$candidates[] = doubleval($msrp);
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
if (empty($candidates)) {
|
| 195 |
+
return false;
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
sort($candidates);
|
| 199 |
+
return $candidates[0];
|
| 200 |
+
}
|
| 201 |
+
|
| 202 |
+
/**
|
| 203 |
+
* @param Mage_Catalog_Model_Product $product
|
| 204 |
+
* @return double|false The current full retail price for the given product.
|
| 205 |
+
*/
|
| 206 |
+
protected function getRetailPriceForProduct(Mage_Catalog_Model_Product $product)
|
| 207 |
+
{
|
| 208 |
+
$retail = $product->getFinalPrice();
|
| 209 |
+
|
| 210 |
+
if (is_numeric($retail)) {
|
| 211 |
+
return doubleval($retail);
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
return false;
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
/**
|
| 218 |
+
* @return array The set of products to use when summing up the final retail price.
|
| 219 |
+
*/
|
| 220 |
+
protected function getProductsForRetailPrice()
|
| 221 |
+
{
|
| 222 |
+
// Any non-child product *should* have a good retail price attached to it.
|
| 223 |
+
|
| 224 |
+
$result = array();
|
| 225 |
+
|
| 226 |
+
foreach($this->_products as $product) {
|
| 227 |
+
$isChild = !!$product->getParentProductId();
|
| 228 |
+
if (!$isChild) {
|
| 229 |
+
$result[] = $product;
|
| 230 |
+
}
|
| 231 |
+
}
|
| 232 |
+
|
| 233 |
+
return $result;
|
| 234 |
+
}
|
| 235 |
+
|
| 236 |
+
/**
|
| 237 |
+
* @return array The set of products to use when summing up fields *other* than final retail price.
|
| 238 |
+
*/
|
| 239 |
+
protected function getProductsForOtherPriceFields()
|
| 240 |
+
{
|
| 241 |
+
// *If* we have child products here, return *only* those.
|
| 242 |
+
// Otherwise return all products.
|
| 243 |
+
|
| 244 |
+
$haveChildProducts = false;
|
| 245 |
+
foreach($this->_products as $product) {
|
| 246 |
+
$isChild = !!$product->getParentProductId();
|
| 247 |
+
if ($isChild) {
|
| 248 |
+
$haveChildProducts = true;
|
| 249 |
+
break;
|
| 250 |
+
}
|
| 251 |
+
}
|
| 252 |
+
|
| 253 |
+
if (!$haveChildProducts) {
|
| 254 |
+
return $this->_products;
|
| 255 |
+
}
|
| 256 |
+
|
| 257 |
+
// Include only the child products.
|
| 258 |
+
$result = array();
|
| 259 |
+
foreach($this->_products as $product) {
|
| 260 |
+
$isChild = !!$product->getParentProductId();
|
| 261 |
+
if ($isChild) {
|
| 262 |
+
$result[] = $product;
|
| 263 |
+
}
|
| 264 |
+
}
|
| 265 |
+
|
| 266 |
+
return $result;
|
| 267 |
+
}
|
| 268 |
+
|
| 269 |
+
}
|
app/code/community/PriceWaiter/NYPWidget/Model/Session.php
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
/**
|
| 4 |
+
* Class responsible for tracking the user's currently applied deals.
|
| 5 |
+
*/
|
| 6 |
+
class PriceWaiter_NYPWidget_Model_Session
|
| 7 |
+
extends Mage_Core_Model_Session_Abstract
|
| 8 |
+
{
|
| 9 |
+
private $_activeDeals = null;
|
| 10 |
+
|
| 11 |
+
private $_now = null;
|
| 12 |
+
|
| 13 |
+
public function __construct()
|
| 14 |
+
{
|
| 15 |
+
$this->init('pricewaiter');
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
/**
|
| 19 |
+
* @return String The unique PriceWaiter ID of the current buyer.s
|
| 20 |
+
*/
|
| 21 |
+
public function getBuyerId()
|
| 22 |
+
{
|
| 23 |
+
return $this->getData('buyer_id');
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
/**
|
| 27 |
+
* Sets the current buyer id.
|
| 28 |
+
* @param $id
|
| 29 |
+
* @return PriceWaiter_NYPWidget_Model_Session $this
|
| 30 |
+
*/
|
| 31 |
+
public function setBuyerId($id)
|
| 32 |
+
{
|
| 33 |
+
$this->setData('buyer_id', $id);
|
| 34 |
+
$this->_activeDeals = null;
|
| 35 |
+
return $this;
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
/**
|
| 39 |
+
* @return Integer UNIX timestamp representing "now".
|
| 40 |
+
*/
|
| 41 |
+
public function getNow()
|
| 42 |
+
{
|
| 43 |
+
if ($this->_now === null) {
|
| 44 |
+
return time();
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
return $this->_now;
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
/**
|
| 51 |
+
* @internal For tests.
|
| 52 |
+
* @param Integer $now
|
| 53 |
+
*/
|
| 54 |
+
public function setNow($now)
|
| 55 |
+
{
|
| 56 |
+
if (is_string($now)) {
|
| 57 |
+
$now = strtotime($now);
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
$this->_now = $now;
|
| 61 |
+
|
| 62 |
+
return $this;
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
/**
|
| 66 |
+
* @return Array All Deals for the buyer that are unrevoked and unexpired.
|
| 67 |
+
*/
|
| 68 |
+
public function getActiveDeals()
|
| 69 |
+
{
|
| 70 |
+
if ($this->_activeDeals !== null) {
|
| 71 |
+
return $this->_activeDeals;
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
$buyerId = $this->getBuyerId();
|
| 75 |
+
|
| 76 |
+
if (!$buyerId) {
|
| 77 |
+
return array();
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
$collection = Mage::getModel('nypwidget/deal')
|
| 81 |
+
->getCollection()
|
| 82 |
+
// Only get Deals for the current buyer...
|
| 83 |
+
->addFieldToFilter('pricewaiter_buyer_id', $buyerId)
|
| 84 |
+
|
| 85 |
+
// ...that haven't been revoked...
|
| 86 |
+
->addFieldToFilter('revoked', 0)
|
| 87 |
+
|
| 88 |
+
// ...and havent' already been used to check out...
|
| 89 |
+
->addFieldToFilter('order_id', array('null' => true))
|
| 90 |
+
|
| 91 |
+
// ...and either don't have an expiry or have an expiry in the future
|
| 92 |
+
->addFieldToFilter(
|
| 93 |
+
'expires_at',
|
| 94 |
+
array(
|
| 95 |
+
array('gt' => date('Y-m-d H:i:s', $this->getNow())),
|
| 96 |
+
array('null' => true),
|
| 97 |
+
)
|
| 98 |
+
);
|
| 99 |
+
|
| 100 |
+
$this->_activeDeals = $collection->getItems();
|
| 101 |
+
|
| 102 |
+
return $this->_activeDeals;
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
/**
|
| 106 |
+
* @return $this
|
| 107 |
+
*/
|
| 108 |
+
public function reset()
|
| 109 |
+
{
|
| 110 |
+
return $this->setBuyerId(null);
|
| 111 |
+
}
|
| 112 |
+
}
|
app/code/community/PriceWaiter/NYPWidget/Model/Total/Quote.php
ADDED
|
@@ -0,0 +1,452 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
/**
|
| 4 |
+
* Total model used to apply PriceWaiter discounts at the quote level.
|
| 5 |
+
*/
|
| 6 |
+
class PriceWaiter_NYPWidget_Model_Total_Quote
|
| 7 |
+
extends Mage_Sales_Model_Quote_Address_Total_Abstract
|
| 8 |
+
{
|
| 9 |
+
private $_deals = null;
|
| 10 |
+
private $_session = null;
|
| 11 |
+
|
| 12 |
+
/**
|
| 13 |
+
* @internal
|
| 14 |
+
*/
|
| 15 |
+
private static $_dealsForTesting = null;
|
| 16 |
+
|
| 17 |
+
/**
|
| 18 |
+
* @internal Supports fetch() hack.
|
| 19 |
+
* @var array
|
| 20 |
+
*/
|
| 21 |
+
private static $_fetchingAddresses = array();
|
| 22 |
+
|
| 23 |
+
/**
|
| 24 |
+
* Calculates PriceWaiter discounts and applies them to the quote and
|
| 25 |
+
* relevant quote items.
|
| 26 |
+
*
|
| 27 |
+
* @param Mage_Sales_Model_Quote_Address $address
|
| 28 |
+
* @return PriceWaiter_NYPWidget_Model_Total_Quote $this
|
| 29 |
+
*/
|
| 30 |
+
public function collect(Mage_Sales_Model_Quote_Address $address)
|
| 31 |
+
{
|
| 32 |
+
parent::collect($address);
|
| 33 |
+
|
| 34 |
+
if (!$this->shouldCollectAddress($address)) {
|
| 35 |
+
return $this;
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
$deals = $this->getPriceWaiterDeals();
|
| 39 |
+
|
| 40 |
+
// In case of collision, favor more recent deals over less recent.
|
| 41 |
+
usort($deals, array(__CLASS__, 'sortDealsRecentFirst'));
|
| 42 |
+
|
| 43 |
+
$appliedDeals = array();
|
| 44 |
+
$appliedDealIds = array();
|
| 45 |
+
|
| 46 |
+
$discountedQuoteItems = array();
|
| 47 |
+
$pwDiscount = 0;
|
| 48 |
+
|
| 49 |
+
/** @var PriceWaiter_NYPWidget_Model_Deal $deal */
|
| 50 |
+
foreach ($deals as $deal) {
|
| 51 |
+
$applied = $this->collectDeal($deal, $address, $discountedQuoteItems);
|
| 52 |
+
|
| 53 |
+
if (!$applied) {
|
| 54 |
+
continue;
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
$pwDiscount += $applied;
|
| 58 |
+
|
| 59 |
+
$appliedDeals[] = $deal;
|
| 60 |
+
$appliedDealIds[] = $deal->getId();
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
// Note that we have used one or more deals for this quote.
|
| 64 |
+
// This connection will get picked up later when quote is
|
| 65 |
+
// converted into an order.
|
| 66 |
+
// TODO: Link Deal -> Order on order creation without requiring an
|
| 67 |
+
// intermediate link btw. Deal -> Quote.
|
| 68 |
+
|
| 69 |
+
$res = Mage::getResourceModel('nypwidget/deal_usage');
|
| 70 |
+
$res->recordDealUsageForQuote(
|
| 71 |
+
$address->getQuote(),
|
| 72 |
+
$appliedDeals
|
| 73 |
+
);
|
| 74 |
+
|
| 75 |
+
// Track deal usage and discount amounts. fetch(), below, will pick them up.
|
| 76 |
+
// Note that these fields are *not* perisisted directly to the DB.
|
| 77 |
+
$address->setPriceWaiterDiscount($pwDiscount);
|
| 78 |
+
$address->setPriceWaiterDealIds($appliedDealIds);
|
| 79 |
+
|
| 80 |
+
return $this;
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
/**
|
| 84 |
+
* Adds the "PriceWaiter Savings" totals row for the address.
|
| 85 |
+
*
|
| 86 |
+
* @param Mage_Sales_Model_Quote_Address $address
|
| 87 |
+
* @return PriceWaiter_NYPWidget_Model_Total_Quote $this
|
| 88 |
+
*/
|
| 89 |
+
public function fetch(Mage_Sales_Model_Quote_Address $address)
|
| 90 |
+
{
|
| 91 |
+
$discount = $address->getPriceWaiterDiscount();
|
| 92 |
+
$deals = $address->getPriceWaiterDealIds();
|
| 93 |
+
|
| 94 |
+
if (!$deals || !$discount) {
|
| 95 |
+
return $this;
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
if ($this->fetchOverridingDiscount($address)) {
|
| 99 |
+
// We sneakily overrode the existing discount total
|
| 100 |
+
return $this;
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
// If Discount not loaded, then we can't replace it so we just add ours.
|
| 104 |
+
$address->addTotal($this->buildPriceWaiterTotal(-$discount));
|
| 105 |
+
|
| 106 |
+
return $this;
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
protected function buildPriceWaiterTotal($discount)
|
| 110 |
+
{
|
| 111 |
+
$helper = Mage::helper('nypwidget');
|
| 112 |
+
|
| 113 |
+
return array(
|
| 114 |
+
'code' => 'pricewaiter',
|
| 115 |
+
'title' => $helper->__('PriceWaiter Savings'),
|
| 116 |
+
'value' => $discount,
|
| 117 |
+
);
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
protected function fetchOverridingDiscount(Mage_Sales_Model_Quote_Address $address)
|
| 121 |
+
{
|
| 122 |
+
$addrId = $address->getId() ? $address->getId() : spl_object_hash($address);
|
| 123 |
+
$alreadyFetching = !empty(self::$_fetchingAddresses[$addrId]);
|
| 124 |
+
|
| 125 |
+
if ($alreadyFetching) {
|
| 126 |
+
// Avoid stack overflow.
|
| 127 |
+
return true;
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
// Note that we are fetching for this address so that when we call
|
| 131 |
+
// $addr->getTotals() below we don't end up recursing infinitely.
|
| 132 |
+
self::$_fetchingAddresses[$addrId] = true;
|
| 133 |
+
|
| 134 |
+
try
|
| 135 |
+
{
|
| 136 |
+
$discountTotal = null;
|
| 137 |
+
foreach ($address->getTotals() as $total) {
|
| 138 |
+
if ($total->getCode() === 'discount') {
|
| 139 |
+
$discountTotal = $total;
|
| 140 |
+
break;
|
| 141 |
+
}
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
unset(self::$_fetchingAddresses[$addrId]);
|
| 145 |
+
|
| 146 |
+
if ($discountTotal) {
|
| 147 |
+
$this->hackilyOverwriteDiscountTotal($discountTotal);
|
| 148 |
+
return true;
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
return false; // "We did not overwrite discount"
|
| 152 |
+
}
|
| 153 |
+
catch (Exception $ex)
|
| 154 |
+
{
|
| 155 |
+
unset(self::$_fetchingAddresses[$addrId]);
|
| 156 |
+
throw $ex;
|
| 157 |
+
}
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
protected function hackilyOverwriteDiscountTotal(Mage_Sales_Model_Quote_Address_Total $total)
|
| 161 |
+
{
|
| 162 |
+
$pwTotal = $this->buildPriceWaiterTotal($total->getValue());
|
| 163 |
+
$total->setCode($pwTotal['code']);
|
| 164 |
+
$total->setTitle($pwTotal['title']);
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
/**
|
| 168 |
+
* @return array Deal models to be applied for discounting.
|
| 169 |
+
*/
|
| 170 |
+
public function getPriceWaiterDeals()
|
| 171 |
+
{
|
| 172 |
+
// HACK: Allow injecting deals for tests, since we don't control
|
| 173 |
+
// instantiation of this class during Quote::collectTotals().
|
| 174 |
+
if (self::$_dealsForTesting !== null) {
|
| 175 |
+
return self::$_dealsForTesting;
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
if ($this->_deals === null) {
|
| 179 |
+
$this->initDealsFromSession();
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
return $this->_deals;
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
/**
|
| 186 |
+
* @internal Setter for deals array.
|
| 187 |
+
* @param Array $deals Deals
|
| 188 |
+
* @return PriceWaiter_NYPWidget_Model_Total_Quote $this
|
| 189 |
+
*/
|
| 190 |
+
public function setPriceWaiterDeals(Array $deals)
|
| 191 |
+
{
|
| 192 |
+
$this->_deals = $deals;
|
| 193 |
+
return $this;
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
/**
|
| 197 |
+
* @return PriceWaiter_NYPWidget_Model_Session
|
| 198 |
+
*/
|
| 199 |
+
public function getSession()
|
| 200 |
+
{
|
| 201 |
+
return $this->_session ?
|
| 202 |
+
$this->_session :
|
| 203 |
+
Mage::getSingleton('nypwidget/session');
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
/**
|
| 207 |
+
* Sets the session instance to use (instead of the default one).
|
| 208 |
+
* @param PriceWaiter_NYPWidget_Model_Session $session
|
| 209 |
+
*/
|
| 210 |
+
public function setSession(PriceWaiter_NYPWidget_Model_Session $session)
|
| 211 |
+
{
|
| 212 |
+
$this->_session = $session;
|
| 213 |
+
return $this;
|
| 214 |
+
}
|
| 215 |
+
|
| 216 |
+
/**
|
| 217 |
+
* @internal Sets the deals to be returnd by getPriceWaiterDeals().
|
| 218 |
+
* @param array $deals
|
| 219 |
+
*/
|
| 220 |
+
public static function hackilySetDealsForTesting($deals)
|
| 221 |
+
{
|
| 222 |
+
self::$_dealsForTesting = $deals;
|
| 223 |
+
}
|
| 224 |
+
|
| 225 |
+
/**
|
| 226 |
+
* @internal
|
| 227 |
+
* @param PriceWaiter_NYPWidget_Model_Deal $a
|
| 228 |
+
* @param PriceWaiter_NYPWidget_Model_Deal $b
|
| 229 |
+
* @return Integer
|
| 230 |
+
*/
|
| 231 |
+
public static function sortDealsRecentFirst(
|
| 232 |
+
PriceWaiter_NYPWidget_Model_Deal $a,
|
| 233 |
+
PriceWaiter_NYPWidget_Model_Deal $b
|
| 234 |
+
)
|
| 235 |
+
{
|
| 236 |
+
$aCreated = @strtotime($a->getCreatedAt());
|
| 237 |
+
$bCreated = @strtotime($b->getCreatedAt());
|
| 238 |
+
|
| 239 |
+
// If a is more recent than b, sort it before
|
| 240 |
+
return $bCreated - $aCreated;
|
| 241 |
+
}
|
| 242 |
+
|
| 243 |
+
/**
|
| 244 |
+
* Attempts to apply a Deal to the given Quote_Address.
|
| 245 |
+
* @param PriceWaiter_NYPWidget_Model_Deal $deal
|
| 246 |
+
* @param Mage_Sales_Model_Quote_Address $address
|
| 247 |
+
* @param array $discountedQuoteItems Tracks quote items that receive discounts.
|
| 248 |
+
* @return array|false
|
| 249 |
+
*/
|
| 250 |
+
protected function collectDeal(
|
| 251 |
+
PriceWaiter_NYPWidget_Model_Deal $deal,
|
| 252 |
+
Mage_Sales_Model_Quote_Address $address,
|
| 253 |
+
array &$discountedQuoteItems
|
| 254 |
+
)
|
| 255 |
+
{
|
| 256 |
+
$offerItems = $deal->getOfferItems();
|
| 257 |
+
|
| 258 |
+
$quote = $address->getQuote();
|
| 259 |
+
$quoteItems = $address->getAllItems();
|
| 260 |
+
|
| 261 |
+
$quoteItemsForOfferItems = array();
|
| 262 |
+
|
| 263 |
+
try
|
| 264 |
+
{
|
| 265 |
+
// Check that we can apply *all* offer items.
|
| 266 |
+
foreach($offerItems as $offerItem) {
|
| 267 |
+
$quoteItem = $offerItem->findQuoteItem($quoteItems);
|
| 268 |
+
|
| 269 |
+
if (!$quoteItem) {
|
| 270 |
+
// No candidate quote item found -- cannot apply this deal.
|
| 271 |
+
return false;
|
| 272 |
+
}
|
| 273 |
+
|
| 274 |
+
if (in_array($quoteItem, $discountedQuoteItems)) {
|
| 275 |
+
// This quote item has already had a PriceWaiter deal applied.
|
| 276 |
+
// PW Deals *should not* stack on top of each other.
|
| 277 |
+
return false;
|
| 278 |
+
}
|
| 279 |
+
|
| 280 |
+
$quoteItemsForOfferItems[] = $quoteItem;
|
| 281 |
+
}
|
| 282 |
+
}
|
| 283 |
+
catch (Exception $ex)
|
| 284 |
+
{
|
| 285 |
+
// Prevent malformed / invalid deals from killing the cart.
|
| 286 |
+
Mage::logException($ex);
|
| 287 |
+
return false;
|
| 288 |
+
}
|
| 289 |
+
|
| 290 |
+
// TODO: Existing discount conflict resolution
|
| 291 |
+
|
| 292 |
+
// If we've gotten this far, it means all offer items are good to apply.
|
| 293 |
+
|
| 294 |
+
$pwDiscount = 0;
|
| 295 |
+
|
| 296 |
+
foreach($offerItems as $index => $offerItem) {
|
| 297 |
+
|
| 298 |
+
$quoteItem = $quoteItemsForOfferItems[$index];
|
| 299 |
+
|
| 300 |
+
// Things we need to do here:
|
| 301 |
+
// 1. Get the *price* of the product in question.
|
| 302 |
+
// 2. Calculate how much of a discount to apply
|
| 303 |
+
// 3. Apply the discount to the appropriate quote item
|
| 304 |
+
// 4. Apply the discount to the address.
|
| 305 |
+
|
| 306 |
+
$discounter = $this->createDiscountCalculator(
|
| 307 |
+
$quote,
|
| 308 |
+
$quoteItem,
|
| 309 |
+
$offerItem
|
| 310 |
+
);
|
| 311 |
+
|
| 312 |
+
list($discount, $baseDiscount, $originalDiscount, $baseOriginalDiscount) = array(
|
| 313 |
+
$discounter->getDiscount(),
|
| 314 |
+
$discounter->getBaseDiscount(),
|
| 315 |
+
$discounter->getOriginalDiscount(),
|
| 316 |
+
$discounter->getBaseOriginalDiscount(),
|
| 317 |
+
);
|
| 318 |
+
|
| 319 |
+
if ($discount <= 0) {
|
| 320 |
+
continue;
|
| 321 |
+
}
|
| 322 |
+
|
| 323 |
+
// Note that we're altering this quote item so we don't
|
| 324 |
+
// try to alter it again.
|
| 325 |
+
$discountedQuoteItems[] = $quoteItem;
|
| 326 |
+
|
| 327 |
+
$quoteItem->setDiscountAmount($quoteItem->getDiscountAmount() + $discount);
|
| 328 |
+
$quoteItem->setBaseDiscountAmount($quoteItem->getBaseDiscountAmount() + $baseDiscount);
|
| 329 |
+
|
| 330 |
+
// These fields are not persisted, but may be used in tax calculation
|
| 331 |
+
$quoteItem->setOriginalDiscountAmount($quoteItem->getOriginalDiscount + $originalDiscount);
|
| 332 |
+
$quoteItem->setBaseOriginalDiscountAmount($quoteItem->getBaseOriginalDiscountAmount() + $baseOriginalDiscount);
|
| 333 |
+
|
| 334 |
+
$this->_addAmount(-$discount);
|
| 335 |
+
$this->_addBaseAmount(-$baseDiscount);
|
| 336 |
+
|
| 337 |
+
// NOTE: Setting Discount Amount on the *address* here will make the
|
| 338 |
+
// totals row for the built-in discounter show up.
|
| 339 |
+
// We should probably be storing our discount in different places.
|
| 340 |
+
|
| 341 |
+
$address->setDiscountAmount(
|
| 342 |
+
$address->getDiscountAmount() - $discount
|
| 343 |
+
);
|
| 344 |
+
|
| 345 |
+
$address->setBaseDiscountAmount(
|
| 346 |
+
$address->getBaseDiscountAmount() - $baseDiscount
|
| 347 |
+
);
|
| 348 |
+
|
| 349 |
+
|
| 350 |
+
$pwDiscount += $discount;
|
| 351 |
+
}
|
| 352 |
+
|
| 353 |
+
return $pwDiscount;
|
| 354 |
+
}
|
| 355 |
+
|
| 356 |
+
/**
|
| 357 |
+
* Configures a discounter to actually calculate how big a discount to apply.
|
| 358 |
+
*/
|
| 359 |
+
protected function createDiscountCalculator(
|
| 360 |
+
Mage_Sales_Model_Quote $quote,
|
| 361 |
+
Mage_Sales_Model_Quote_Item $quoteItem,
|
| 362 |
+
PriceWaiter_NYPWidget_Model_Offer_Item $offerItem
|
| 363 |
+
)
|
| 364 |
+
{
|
| 365 |
+
|
| 366 |
+
return Mage::getModel('nypwidget/discounter')
|
| 367 |
+
->setProductPrice($this->getItemPrice($quoteItem))
|
| 368 |
+
->setProductOriginalPrice($this->getItemOriginalPrice($quoteItem))
|
| 369 |
+
|
| 370 |
+
->setQuoteBaseCurrency($this->getCurrencyByCode($quote->getBaseCurrency()))
|
| 371 |
+
->setQuoteCurrency($this->getCurrencyByCode($quote->getCurrency()))
|
| 372 |
+
->setQuoteItemQty($quoteItem->getQty())
|
| 373 |
+
|
| 374 |
+
->setOfferCurrency($this->getCurrencyByCode($offerItem->getCurrencyCode()))
|
| 375 |
+
->setOfferAmountPerItem($offerItem->getAmountPerItem())
|
| 376 |
+
->setOfferMinQty($offerItem->getMinimumQuantity())
|
| 377 |
+
->setOfferMaxQty($offerItem->getMaximumQuantity())
|
| 378 |
+
;
|
| 379 |
+
}
|
| 380 |
+
|
| 381 |
+
protected function getCurrencyByCode($code)
|
| 382 |
+
{
|
| 383 |
+
return Mage::getModel('directory/currency')->load($code);
|
| 384 |
+
}
|
| 385 |
+
|
| 386 |
+
/**
|
| 387 |
+
* @internal Cribbed from SalesRule/Model/Validator.php
|
| 388 |
+
*/
|
| 389 |
+
protected function getItemPrice(Mage_Sales_Model_Quote_Item_Abstract $item)
|
| 390 |
+
{
|
| 391 |
+
$price = $item->getDiscountCalculationPrice();
|
| 392 |
+
$calcPrice = $item->getCalculationPrice();
|
| 393 |
+
return ($price !== null) ? $price : $calcPrice;
|
| 394 |
+
}
|
| 395 |
+
|
| 396 |
+
/**
|
| 397 |
+
* @internal Cribbed from SalesRule/Model/Validator.php
|
| 398 |
+
*/
|
| 399 |
+
protected function getItemOriginalPrice(Mage_Sales_Model_Quote_Item_Abstract $item)
|
| 400 |
+
{
|
| 401 |
+
return Mage::helper('tax')->getPrice($item, $item->getOriginalPrice(), true);
|
| 402 |
+
}
|
| 403 |
+
|
| 404 |
+
/**
|
| 405 |
+
* Examines the current user's session and loads their current PriceWaiter deals.
|
| 406 |
+
* @return void
|
| 407 |
+
*/
|
| 408 |
+
protected function initDealsFromSession()
|
| 409 |
+
{
|
| 410 |
+
$session = $this->getSession();
|
| 411 |
+
$this->setPriceWaiterDeals($session->getActiveDeals());
|
| 412 |
+
}
|
| 413 |
+
|
| 414 |
+
/**
|
| 415 |
+
* Hook to prevent collect() and fetch() on a given address.
|
| 416 |
+
* @param Mage_Sales_Model_Quote_Address $addr
|
| 417 |
+
* @return Boolean
|
| 418 |
+
*/
|
| 419 |
+
protected function shouldCollectAddress(Mage_Sales_Model_Quote_Address $addr)
|
| 420 |
+
{
|
| 421 |
+
// HACK: We only support collecting for items on the shipping address for now.
|
| 422 |
+
// To properly support all address types we need a minor db migration +
|
| 423 |
+
// reworking of recordDealUsageForQuote() to accept address id as well as
|
| 424 |
+
// quote id.
|
| 425 |
+
if ($addr->getAddressType() !== Mage_Sales_Model_Quote_Address::TYPE_SHIPPING) {
|
| 426 |
+
return false;
|
| 427 |
+
}
|
| 428 |
+
|
| 429 |
+
// Things that stop us from performing collection:
|
| 430 |
+
// 1. Quote has salesrules applied
|
| 431 |
+
// 2. (Probably redundant) quote has coupon_code
|
| 432 |
+
|
| 433 |
+
// TODO: More nuance here.
|
| 434 |
+
|
| 435 |
+
$quote = $addr->getQuote();
|
| 436 |
+
|
| 437 |
+
$hasSalesRules = !empty($quote->getAppliedRuleIds());
|
| 438 |
+
if ($hasSalesRules) {
|
| 439 |
+
return false;
|
| 440 |
+
}
|
| 441 |
+
|
| 442 |
+
// This is probably redundant, a coupon code *should* result in an
|
| 443 |
+
// applied sales rule id (above), but just in case...
|
| 444 |
+
$hasCouponCode = !empty($quote->getCouponCode());
|
| 445 |
+
if ($hasCouponCode) {
|
| 446 |
+
return false;
|
| 447 |
+
}
|
| 448 |
+
|
| 449 |
+
return true;
|
| 450 |
+
}
|
| 451 |
+
|
| 452 |
+
}
|
app/code/community/PriceWaiter/NYPWidget/controllers/Adminhtml/PricewaiterController.php
CHANGED
|
@@ -91,18 +91,4 @@ class PriceWaiter_NYPWidget_Adminhtml_PriceWaiterController extends Mage_Adminht
|
|
| 91 |
'secret' => $secret
|
| 92 |
)));
|
| 93 |
}
|
| 94 |
-
|
| 95 |
-
/**
|
| 96 |
-
* @internal
|
| 97 |
-
*/
|
| 98 |
-
protected function _isAllowed()
|
| 99 |
-
{
|
| 100 |
-
// We have to override this method for Magento Technical Validation.
|
| 101 |
-
// The idea is that versions of Magento < 1.9.2 (or missing
|
| 102 |
-
// SUPEE-6285 patch) just return `true` here by default, which can
|
| 103 |
-
// inadvertently enable access to admin users w/o permissions.
|
| 104 |
-
// We don't really use ACLs, so this shouldn't matter much, but
|
| 105 |
-
// here we fill in what 1.9.2 does:
|
| 106 |
-
return Mage::getSingleton('admin/session')->isAllowed('admin');
|
| 107 |
-
}
|
| 108 |
}
|
| 91 |
'secret' => $secret
|
| 92 |
)));
|
| 93 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
}
|
app/code/community/PriceWaiter/NYPWidget/controllers/CallbackController.php
CHANGED
|
@@ -29,7 +29,7 @@ class PriceWaiter_NYPWidget_CallbackController extends Mage_Core_Controller_Fron
|
|
| 29 |
}
|
| 30 |
|
| 31 |
// Add debugging headers
|
| 32 |
-
Mage::helper('nypwidget')->
|
| 33 |
$pricewaiterId = '';
|
| 34 |
|
| 35 |
try
|
| 29 |
}
|
| 30 |
|
| 31 |
// Add debugging headers
|
| 32 |
+
Mage::helper('nypwidget/about')->setResponseHeaders($httpResponse);
|
| 33 |
$pricewaiterId = '';
|
| 34 |
|
| 35 |
try
|
app/code/community/PriceWaiter/NYPWidget/controllers/CheckoutController.php
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
/**
|
| 4 |
+
* Controller that serves the checkout_url endpoint used for deals.
|
| 5 |
+
*/
|
| 6 |
+
class PriceWaiter_NYPWidget_CheckoutController
|
| 7 |
+
extends Mage_Core_Controller_Front_Action
|
| 8 |
+
{
|
| 9 |
+
/**
|
| 10 |
+
* Header used to provide feedback about errors.
|
| 11 |
+
* This is non-spec behavior added as a convenience.
|
| 12 |
+
*/
|
| 13 |
+
const ERROR_HEADER = 'X-PriceWaiter-Error';
|
| 14 |
+
|
| 15 |
+
/**
|
| 16 |
+
* @internal
|
| 17 |
+
*/
|
| 18 |
+
public function indexAction()
|
| 19 |
+
{
|
| 20 |
+
$httpRequest = $this->getRequest();
|
| 21 |
+
$httpResponse = $this->getResponse();
|
| 22 |
+
|
| 23 |
+
try
|
| 24 |
+
{
|
| 25 |
+
|
| 26 |
+
$id = $httpRequest->getQuery(
|
| 27 |
+
PriceWaiter_NYPWidget_Model_Deal::CHECKOUT_URL_DEAL_ID_ARG,
|
| 28 |
+
null
|
| 29 |
+
);
|
| 30 |
+
|
| 31 |
+
$deal = $this->getDealById($id);
|
| 32 |
+
|
| 33 |
+
if (!$deal) {
|
| 34 |
+
$this->redirectToHomepage('deal_not_found');
|
| 35 |
+
return;
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
$this->prepareCart($deal);
|
| 39 |
+
|
| 40 |
+
// Attempt to include some debugging info with redirect
|
| 41 |
+
$errorCode = null;
|
| 42 |
+
|
| 43 |
+
if ($deal->isRevoked()) {
|
| 44 |
+
$errorCode = 'deal_revoked';
|
| 45 |
+
} else if ($deal->isExpired()) {
|
| 46 |
+
$errorCode = 'deal_expired';
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
$this->redirectToCart($errorCode);
|
| 50 |
+
}
|
| 51 |
+
catch (PriceWaiter_NYPWidget_Exception_Product_OutOfStock $ex)
|
| 52 |
+
{
|
| 53 |
+
// The product is not currently in stock, so our add-to-cart
|
| 54 |
+
// failed. Forward the user to the cart page and show an
|
| 55 |
+
// error message (this matches built-in A2C error behavior)
|
| 56 |
+
$this->redirectToProductPage(
|
| 57 |
+
$deal,
|
| 58 |
+
$ex->errorCode,
|
| 59 |
+
$ex->getMessage()
|
| 60 |
+
);
|
| 61 |
+
}
|
| 62 |
+
catch (PriceWaiter_NYPWidget_Exception_Abstract $ex)
|
| 63 |
+
{
|
| 64 |
+
Mage::logException($ex);
|
| 65 |
+
$this->redirectToHomepage($ex->errorCode);
|
| 66 |
+
}
|
| 67 |
+
catch (Exception $ex)
|
| 68 |
+
{
|
| 69 |
+
Mage::logException($ex);
|
| 70 |
+
$this->redirectToHomepage();
|
| 71 |
+
}
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
/**
|
| 75 |
+
* @internal
|
| 76 |
+
*/
|
| 77 |
+
protected function addMessageToSession($message)
|
| 78 |
+
{
|
| 79 |
+
// This is adapted from CartController's A2C handling.
|
| 80 |
+
// The idea is that if there was an error adding to cart, we should
|
| 81 |
+
// report that error to the user in some way.
|
| 82 |
+
$session = Mage::getSingleton('checkout/session');
|
| 83 |
+
$shouldUseNotice = $session->getUseNotice(true);
|
| 84 |
+
|
| 85 |
+
if ($shouldUseNotice) {
|
| 86 |
+
$message = Mage::helper('core')->escapeHtml($message);
|
| 87 |
+
$session->addNotice($message);
|
| 88 |
+
} else {
|
| 89 |
+
$messages = array_unique(explode("\n", $message));
|
| 90 |
+
foreach ($messages as $message) {
|
| 91 |
+
$message = Mage::helper('core')->escapeHtml($message);
|
| 92 |
+
$session->addError($message);
|
| 93 |
+
}
|
| 94 |
+
}
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
/**
|
| 98 |
+
* @param String $id
|
| 99 |
+
* @return PriceWaiter_NYPWidget_Model_Deal|false
|
| 100 |
+
*/
|
| 101 |
+
protected function getDealById($id)
|
| 102 |
+
{
|
| 103 |
+
if (!$id) {
|
| 104 |
+
return false;
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
$deal = Mage::getModel('nypwidget/deal');
|
| 108 |
+
$deal->load($id);
|
| 109 |
+
|
| 110 |
+
return $deal->getId() ? $deal : false;
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
/**
|
| 114 |
+
* Ensures that the buyer's cart contains the contents of the given deal.
|
| 115 |
+
* @param PriceWaiter_NYPWidget_Model_Deal $deal
|
| 116 |
+
*/
|
| 117 |
+
protected function prepareCart(PriceWaiter_NYPWidget_Model_Deal $deal)
|
| 118 |
+
{
|
| 119 |
+
$cart = Mage::getSingleton('checkout/cart');
|
| 120 |
+
$quote = $cart->getQuote();
|
| 121 |
+
$deal->ensurePresentInQuote($quote);
|
| 122 |
+
$cart->save();
|
| 123 |
+
|
| 124 |
+
// Track the current PW buyer ID so we can automatically discover
|
| 125 |
+
// other deals the buyer has made.
|
| 126 |
+
Mage::getSingleton('nypwidget/session')
|
| 127 |
+
->setBuyerId($deal->getPricewaiterBuyerId());
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
/**
|
| 131 |
+
* Redirects the buyer back to the cart page.
|
| 132 |
+
* @param string $errorCode
|
| 133 |
+
*/
|
| 134 |
+
protected function redirectToCart($errorCode = null, $errorMessage = null)
|
| 135 |
+
{
|
| 136 |
+
$url = Mage::getUrl('checkout/cart');
|
| 137 |
+
$this->_doRedirectWithError($url, $errorCode, $errorMessage);
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
/**
|
| 141 |
+
* Redirects the buyer back to the store's homepage.
|
| 142 |
+
* Used when deal is invalid in some way.
|
| 143 |
+
*/
|
| 144 |
+
protected function redirectToHomepage($errorCode = null)
|
| 145 |
+
{
|
| 146 |
+
$url = Mage::getUrl('/');
|
| 147 |
+
$this->_doRedirectWithError($url, $errorCode);
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
/**
|
| 151 |
+
* Sends the user to the product page, optionally setting an error
|
| 152 |
+
* code header and displaying an error message.
|
| 153 |
+
*
|
| 154 |
+
* If something goes wrong, the user is redirected to the homepage.
|
| 155 |
+
*
|
| 156 |
+
* @param PriceWaiter_NYPWidget_Model_Deal $deal
|
| 157 |
+
* @param string $errorCode
|
| 158 |
+
* @param string $errorMessage
|
| 159 |
+
*/
|
| 160 |
+
protected function redirectToProductPage(
|
| 161 |
+
PriceWaiter_NYPWidget_Model_Deal $deal,
|
| 162 |
+
$errorCode = null,
|
| 163 |
+
$errorMessage = null
|
| 164 |
+
)
|
| 165 |
+
{
|
| 166 |
+
$offerItems = $deal->getOfferItems();
|
| 167 |
+
if (count($offerItems) > 0) {
|
| 168 |
+
try
|
| 169 |
+
{
|
| 170 |
+
$url = $offerItems[0]->getMagentoProductUrl();
|
| 171 |
+
return $this->_doRedirectWithError($url, $errorCode, $errorMessage);
|
| 172 |
+
}
|
| 173 |
+
catch (Exception $ex)
|
| 174 |
+
{
|
| 175 |
+
// A malformed deal could result in getMagentoProductUrl() throwing
|
| 176 |
+
Mage::logException($ex);
|
| 177 |
+
}
|
| 178 |
+
}
|
| 179 |
+
|
| 180 |
+
// Fall back to homepage when something horrible happens
|
| 181 |
+
return $this->redirectToHomepage($errorCode);
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
/**
|
| 185 |
+
* @internal
|
| 186 |
+
* 302 Redirects the user, optionally setting an error code header
|
| 187 |
+
* and session-based error message.
|
| 188 |
+
*/
|
| 189 |
+
private function _doRedirectWithError($url, $errorCode = null, $errorMessage = null)
|
| 190 |
+
{
|
| 191 |
+
$httpResponse = $this->getResponse();
|
| 192 |
+
|
| 193 |
+
if ($errorCode !== null) {
|
| 194 |
+
$httpResponse->setHeader(self::ERROR_HEADER, $errorCode, true);
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
$httpResponse->setRedirect($url);
|
| 198 |
+
|
| 199 |
+
if ($errorMessage !== null) {
|
| 200 |
+
$this->addMessageToSession($errorMessage);
|
| 201 |
+
}
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
}
|
app/code/community/PriceWaiter/NYPWidget/controllers/CreatedealController.php
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
/*
|
| 4 |
+
* Copyright 2013-2016 Price Waiter, LLC
|
| 5 |
+
*
|
| 6 |
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
| 7 |
+
* you may not use this file except in compliance with the License.
|
| 8 |
+
* You may obtain a copy of the License at
|
| 9 |
+
*
|
| 10 |
+
* http://www.apache.org/licenses/LICENSE-2.0
|
| 11 |
+
*
|
| 12 |
+
* Unless required by applicable law or agreed to in writing, software
|
| 13 |
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
| 14 |
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 15 |
+
* See the License for the specific language governing permissions and
|
| 16 |
+
* limitations under the License.
|
| 17 |
+
*
|
| 18 |
+
*/
|
| 19 |
+
|
| 20 |
+
/**
|
| 21 |
+
* Controller that handles /pricewaiter/createdeal
|
| 22 |
+
*/
|
| 23 |
+
class PriceWaiter_NYPWidget_CreatedealController extends PriceWaiter_NYPWidget_Controller_Endpoint
|
| 24 |
+
{
|
| 25 |
+
/**
|
| 26 |
+
* Versions of request data this controller supports.
|
| 27 |
+
* @var Array
|
| 28 |
+
*/
|
| 29 |
+
protected $supportedVersions = [
|
| 30 |
+
'2016-03-01',
|
| 31 |
+
];
|
| 32 |
+
|
| 33 |
+
public function processRequest(PriceWaiter_NYPWidget_Controller_Endpoint_Request $request)
|
| 34 |
+
{
|
| 35 |
+
$deal = Mage::getModel('nypwidget/deal');
|
| 36 |
+
|
| 37 |
+
$deal->processCreateRequest($request);
|
| 38 |
+
|
| 39 |
+
$response = new PriceWaiter_NYPWidget_Controller_Endpoint_Response(200, array(
|
| 40 |
+
'checkout_url' => $deal->getCheckoutUrl(),
|
| 41 |
+
));
|
| 42 |
+
|
| 43 |
+
return $response;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
}
|
app/code/community/PriceWaiter/NYPWidget/controllers/DebugController.php
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
/*
|
| 4 |
+
* Copyright 2013-2016 Price Waiter, LLC
|
| 5 |
+
*
|
| 6 |
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
| 7 |
+
* you may not use this file except in compliance with the License.
|
| 8 |
+
* You may obtain a copy of the License at
|
| 9 |
+
*
|
| 10 |
+
* http://www.apache.org/licenses/LICENSE-2.0
|
| 11 |
+
*
|
| 12 |
+
* Unless required by applicable law or agreed to in writing, software
|
| 13 |
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
| 14 |
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 15 |
+
* See the License for the specific language governing permissions and
|
| 16 |
+
* limitations under the License.
|
| 17 |
+
*
|
| 18 |
+
*/
|
| 19 |
+
|
| 20 |
+
class PriceWaiter_NYPWidget_DebugController extends PriceWaiter_NYPWidget_Controller_Endpoint
|
| 21 |
+
{
|
| 22 |
+
/**
|
| 23 |
+
* @var Array
|
| 24 |
+
*/
|
| 25 |
+
protected $supportedVersions = [
|
| 26 |
+
'2016-03-01',
|
| 27 |
+
];
|
| 28 |
+
|
| 29 |
+
public function processRequest(PriceWaiter_NYPWidget_Controller_Endpoint_Request $request)
|
| 30 |
+
{
|
| 31 |
+
$resp = array(
|
| 32 |
+
'store' => $this->summarizeStore(),
|
| 33 |
+
'total_models' => $this->summarizeTotalModels(),
|
| 34 |
+
);
|
| 35 |
+
|
| 36 |
+
return new PriceWaiter_NYPWidget_Controller_Endpoint_Response(200, $resp);
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
/**
|
| 40 |
+
* Returns a summary of the store config.
|
| 41 |
+
* @return Array
|
| 42 |
+
*/
|
| 43 |
+
protected function summarizeStore()
|
| 44 |
+
{
|
| 45 |
+
$store = Mage::app()->getStore();
|
| 46 |
+
$helper = Mage::helper('nypwidget');
|
| 47 |
+
|
| 48 |
+
return array(
|
| 49 |
+
'name' => $store->getName(),
|
| 50 |
+
'code' => $store->getCode(),
|
| 51 |
+
'pricewaiter_api_key' => $helper->getPriceWaiterApiKey($store),
|
| 52 |
+
'pricewaiter_secret_set' => !!trim($helper->getSecret($store)),
|
| 53 |
+
);
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
/**
|
| 57 |
+
* Returns a summary of the available total models and their configuration.
|
| 58 |
+
* @return Array
|
| 59 |
+
*/
|
| 60 |
+
protected function summarizeTotalModels()
|
| 61 |
+
{
|
| 62 |
+
$collector = Mage::getSingleton(
|
| 63 |
+
'sales/quote_address_total_collector',
|
| 64 |
+
array('store' => Mage::app()->getStore())
|
| 65 |
+
);
|
| 66 |
+
|
| 67 |
+
$result = array();
|
| 68 |
+
foreach($collector->getCollectors() as $code => $collector) {
|
| 69 |
+
$result[] = array(
|
| 70 |
+
'code' => $code,
|
| 71 |
+
'class' => get_class($collector),
|
| 72 |
+
);
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
return $result;
|
| 76 |
+
}
|
| 77 |
+
}
|
app/code/community/PriceWaiter/NYPWidget/controllers/ListordersController.php
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
/**
|
| 3 |
+
* Controller that handles /pricewaiter/listorders
|
| 4 |
+
*/
|
| 5 |
+
class PriceWaiter_NYPWidget_ListordersController extends PriceWaiter_NYPWidget_Controller_Endpoint
|
| 6 |
+
{
|
| 7 |
+
/**
|
| 8 |
+
* Versions of request data this controller supports.
|
| 9 |
+
* @var Array
|
| 10 |
+
*/
|
| 11 |
+
protected $supportedVersions = [
|
| 12 |
+
'2016-03-01',
|
| 13 |
+
];
|
| 14 |
+
|
| 15 |
+
public function processRequest(PriceWaiter_NYPWidget_Controller_Endpoint_Request $request)
|
| 16 |
+
{
|
| 17 |
+
$body = $request->getBody();
|
| 18 |
+
$res = Mage::getResourceModel('nypwidget/deal_usage');
|
| 19 |
+
|
| 20 |
+
// Resolve deal ids into a set of order -> deal id links
|
| 21 |
+
$usage = $res->getOrdersAndDealUsageForDealIds($body->pricewaiter_deals);
|
| 22 |
+
|
| 23 |
+
// Format and return the resulting array
|
| 24 |
+
$orders = $this->formatUsage($usage);
|
| 25 |
+
|
| 26 |
+
$response = new PriceWaiter_NYPWidget_Controller_Endpoint_Response(200, $orders);
|
| 27 |
+
|
| 28 |
+
return $response;
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
/**
|
| 32 |
+
* @internal
|
| 33 |
+
* @param array $usage
|
| 34 |
+
* @return array
|
| 35 |
+
*/
|
| 36 |
+
public function formatUsage(array $usage)
|
| 37 |
+
{
|
| 38 |
+
$result = array();
|
| 39 |
+
|
| 40 |
+
foreach($usage as $u) {
|
| 41 |
+
$order = $u['order'];
|
| 42 |
+
$dealIds = $u['dealIds'];
|
| 43 |
+
$formatted = $this->formatOrder($order, $dealIds);
|
| 44 |
+
if ($formatted) {
|
| 45 |
+
$result[] = $formatted;
|
| 46 |
+
}
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
return $result;
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
/**
|
| 53 |
+
* Attempts to format
|
| 54 |
+
* @param Mage_Sales_Model_Order $order
|
| 55 |
+
* @param array $dealIds
|
| 56 |
+
* @return Object|false
|
| 57 |
+
*/
|
| 58 |
+
public function formatOrder(Mage_Sales_Model_Order $order, array $dealIds)
|
| 59 |
+
{
|
| 60 |
+
$state = $this->translateMagentoOrderState($order->getState());
|
| 61 |
+
if (!$state) {
|
| 62 |
+
return false;
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
return (object)array(
|
| 66 |
+
'id' => strval($order->getIncrementId()),
|
| 67 |
+
'state' => $state,
|
| 68 |
+
'currency' => $order->getOrderCurrency()->getCode(),
|
| 69 |
+
'subtotal' => (object)array(
|
| 70 |
+
'value' => strval(
|
| 71 |
+
// "subtotal" here means "order total minus shipping and tax"
|
| 72 |
+
$order->getGrandTotal() -
|
| 73 |
+
$order->getShippingAmount() -
|
| 74 |
+
$order->getTaxAmount()
|
| 75 |
+
),
|
| 76 |
+
),
|
| 77 |
+
'pricewaiter_deals' => $dealIds,
|
| 78 |
+
);
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
/**
|
| 82 |
+
* @internal Translates a Magento order state into one of the states
|
| 83 |
+
* PriceWaiter uses.
|
| 84 |
+
* @param string $state
|
| 85 |
+
* @return string|false A PW state, or false if no translation is possible.
|
| 86 |
+
*/
|
| 87 |
+
public static function translateMagentoOrderState($state)
|
| 88 |
+
{
|
| 89 |
+
$state = strtolower(strval($state));
|
| 90 |
+
|
| 91 |
+
switch($state) {
|
| 92 |
+
case Mage_Sales_Model_Order::STATE_CANCELED:
|
| 93 |
+
return false;
|
| 94 |
+
|
| 95 |
+
case Mage_Sales_Model_Order::STATE_PAYMENT_REVIEW:
|
| 96 |
+
case Mage_Sales_Model_Order::STATE_PENDING_PAYMENT:
|
| 97 |
+
return 'pending';
|
| 98 |
+
|
| 99 |
+
default:
|
| 100 |
+
return 'paid';
|
| 101 |
+
}
|
| 102 |
+
}
|
| 103 |
+
}
|
app/code/community/PriceWaiter/NYPWidget/controllers/PingController.php
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
/**
|
| 4 |
+
* Controller that handles /pricewaiter/ping
|
| 5 |
+
*/
|
| 6 |
+
class PriceWaiter_NYPWidget_PingController extends PriceWaiter_NYPWidget_Controller_Endpoint
|
| 7 |
+
{
|
| 8 |
+
/**
|
| 9 |
+
* Versions of request data this controller supports.
|
| 10 |
+
* @var Array
|
| 11 |
+
*/
|
| 12 |
+
protected $supportedVersions = [
|
| 13 |
+
'2016-03-01',
|
| 14 |
+
];
|
| 15 |
+
|
| 16 |
+
public function processRequest(PriceWaiter_NYPWidget_Controller_Endpoint_Request $request)
|
| 17 |
+
{
|
| 18 |
+
$response = new PriceWaiter_NYPWidget_Controller_Endpoint_Response(200, $request->getBody());
|
| 19 |
+
return $response;
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
}
|
app/code/community/PriceWaiter/NYPWidget/controllers/ProductinfoController.php
CHANGED
|
@@ -1,61 +1,161 @@
|
|
| 1 |
<?php
|
| 2 |
|
| 3 |
-
class PriceWaiter_NYPWidget_ProductinfoController
|
|
|
|
| 4 |
{
|
| 5 |
public function indexAction()
|
| 6 |
{
|
| 7 |
-
|
| 8 |
-
$
|
| 9 |
-
$postFields = Mage::app()->getRequest()->getPost();
|
| 10 |
-
Mage::helper('nypwidget')->setHeaders();
|
| 11 |
|
| 12 |
-
|
| 13 |
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
}
|
| 18 |
|
| 19 |
// Validate the request
|
| 20 |
// - return 400 if signature cannot be verified
|
| 21 |
-
$signature =
|
| 22 |
-
if (
|
| 23 |
-
|
| 24 |
return false;
|
| 25 |
}
|
| 26 |
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
$productConfiguration['qty'] = 1;
|
| 36 |
}
|
|
|
|
|
|
|
|
|
|
| 37 |
|
| 38 |
-
|
| 39 |
-
// This is necessary to make Magento calculate the cost of the item in the correct context.
|
| 40 |
-
try {
|
| 41 |
-
$productInformation = $productHelper->lookupData($productConfiguration);
|
| 42 |
|
| 43 |
-
if
|
| 44 |
-
|
| 45 |
-
$
|
| 46 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
|
| 48 |
-
|
| 49 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
} else {
|
| 51 |
-
|
| 52 |
-
return;
|
| 53 |
}
|
| 54 |
-
} catch (Exception $e) {
|
| 55 |
-
Mage::log("Unable to fulfill PriceWaiter Product Information request for product ID: " . $productConfiguration['product']);
|
| 56 |
-
Mage::log($e->getMessage());
|
| 57 |
-
Mage::app()->getResponse()->setHeader('HTTP/1.0 404 Not Found', 404, true);
|
| 58 |
-
return;
|
| 59 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
}
|
| 61 |
}
|
| 1 |
<?php
|
| 2 |
|
| 3 |
+
class PriceWaiter_NYPWidget_ProductinfoController
|
| 4 |
+
extends Mage_Core_Controller_Front_Action
|
| 5 |
{
|
| 6 |
public function indexAction()
|
| 7 |
{
|
| 8 |
+
$httpRequest = $this->getRequest();
|
| 9 |
+
$httpResponse = $this->getResponse();
|
|
|
|
|
|
|
| 10 |
|
| 11 |
+
Mage::helper('nypwidget/about')->setResponseHeaders($httpResponse);
|
| 12 |
|
| 13 |
+
// Ensure that we have received POST data
|
| 14 |
+
$requestBody = $httpRequest->getRawBody();
|
| 15 |
+
$postFields = $httpRequest->getPost();
|
|
|
|
| 16 |
|
| 17 |
// Validate the request
|
| 18 |
// - return 400 if signature cannot be verified
|
| 19 |
+
$signature = $httpRequest->getHeader('X-PriceWaiter-Signature');
|
| 20 |
+
if (!$this->isPriceWaiterRequestValid($signature, $requestBody)) {
|
| 21 |
+
$httpResponse->setHttpResponseCode(400);
|
| 22 |
return false;
|
| 23 |
}
|
| 24 |
|
| 25 |
+
try
|
| 26 |
+
{
|
| 27 |
+
$store = Mage::app()->getStore();
|
| 28 |
+
|
| 29 |
+
// Turn an array of POST data into a thing that can actually
|
| 30 |
+
// tell us something about the product(s).
|
| 31 |
+
$offerItem = $this->getOfferItem($httpRequest->getPost(), $store);
|
| 32 |
+
|
| 33 |
+
// Format the result we're going to return.
|
| 34 |
+
$result = $this->buildResponse($offerItem, $store);
|
| 35 |
+
|
| 36 |
+
// And finally, return it.
|
| 37 |
+
$json = json_encode($result);
|
| 38 |
|
| 39 |
+
$httpResponse->setHeader('X-PriceWaiter-Signature', $this->getResponseSignature($json));
|
| 40 |
+
$httpResponse->setHeader('Content-Type', 'application/json');
|
| 41 |
+
$httpResponse->setBody($json);
|
|
|
|
| 42 |
}
|
| 43 |
+
catch (Exception $ex)
|
| 44 |
+
{
|
| 45 |
+
Mage::logException($ex);
|
| 46 |
|
| 47 |
+
$httpResponse->setHttpResponseCode(404);
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
+
// Extra: Include an error code if we have one.
|
| 50 |
+
if ($ex instanceof PriceWaiter_NYPWidget_Exception_Abstract) {
|
| 51 |
+
$httpResponse->setHeader('X-PriceWaiter-Error', $ex->errorCode);
|
| 52 |
+
}
|
| 53 |
+
}
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
public function buildResponse(
|
| 57 |
+
PriceWaiter_NYPWidget_Model_Offer_Item $item,
|
| 58 |
+
Mage_Core_Model_Store $store
|
| 59 |
+
)
|
| 60 |
+
{
|
| 61 |
+
$result = array(
|
| 62 |
+
'allow_pricewaiter' => true, // See pricewaiter/magento-dev#115
|
| 63 |
+
);
|
| 64 |
|
| 65 |
+
// 1. Add pricing information
|
| 66 |
+
$pricing = $item->getPricing();
|
| 67 |
+
|
| 68 |
+
$retail = $pricing->getRetailPrice();
|
| 69 |
+
if ($retail !== false) {
|
| 70 |
+
$result['retail_price'] = strval($retail);
|
| 71 |
+
$result['retail_price_currency'] = $pricing->getCurrencyCode();
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
$cost = $pricing->getCost();
|
| 75 |
+
if ($cost !== false) {
|
| 76 |
+
$result['cost'] = strval($cost);
|
| 77 |
+
$result['cost_currency'] = $pricing->getCurrencyCode();
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
$regular = $pricing->getRegularPrice();
|
| 81 |
+
if ($regular !== false) {
|
| 82 |
+
$result['regular_price'] = strval($regular);
|
| 83 |
+
$result['regular_price_currency'] = $pricing->getCurrencyCode();
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
// 2. Add inventory
|
| 87 |
+
$inventory = $item->getInventory();
|
| 88 |
+
$stock = $inventory->getStock();
|
| 89 |
+
if ($stock !== false) {
|
| 90 |
+
$result['inventory'] = $stock;
|
| 91 |
+
$result['can_backorder'] = $inventory->canBackorder();
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
return $result;
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
/**
|
| 98 |
+
* @return PriceWaiter_NYPWidget_Model_Offer_Item
|
| 99 |
+
*/
|
| 100 |
+
public function getOfferItem(array $post, Mage_Core_Model_Store $store)
|
| 101 |
+
{
|
| 102 |
+
$data = array(
|
| 103 |
+
'product' => array(),
|
| 104 |
+
'metadata' => array(),
|
| 105 |
+
);
|
| 106 |
+
|
| 107 |
+
// Per spec, $post will contain product_sku, and any other field
|
| 108 |
+
// should be interpreted as metadata.
|
| 109 |
+
foreach($post as $key => $value) {
|
| 110 |
+
if ($key === 'product_sku') {
|
| 111 |
+
$data['product']['sku'] = $value;
|
| 112 |
} else {
|
| 113 |
+
$data['metadata'][$key] = $value;
|
|
|
|
| 114 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
}
|
| 116 |
+
|
| 117 |
+
return Mage::getModel('nypwidget/offer_item', $data, $store);
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
/**
|
| 121 |
+
* Returns a signature that can be added to the head of a PriceWaiter API response.
|
| 122 |
+
* @param {String} $responseBody The full body of the request to sign.
|
| 123 |
+
* @return {String} Signature that should be set as the X-PriceWaiter-Signature header.
|
| 124 |
+
*/
|
| 125 |
+
public function getResponseSignature($responseBody)
|
| 126 |
+
{
|
| 127 |
+
$secret = Mage::helper('nypwidget')->getSecret();
|
| 128 |
+
$signature = 'sha256=' . hash_hmac('sha256', $responseBody, $secret, false);
|
| 129 |
+
return $signature;
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
/**
|
| 133 |
+
* Validates that the current request came from PriceWaiter.
|
| 134 |
+
* @param {String} $signatureHeader Full value of the X-PriceWaiter-Signature header.
|
| 135 |
+
* @param {String} $requestBody Complete body of incoming request.
|
| 136 |
+
* @return {Boolean} Wehther the request actually came from PriceWaiter.
|
| 137 |
+
*/
|
| 138 |
+
public function isPriceWaiterRequestValid($signatureHeader = null, $requestBody = null)
|
| 139 |
+
{
|
| 140 |
+
if ($signatureHeader === null || $requestBody === null) {
|
| 141 |
+
return false;
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
$secret = Mage::helper('nypwidget')->getSecret();
|
| 145 |
+
|
| 146 |
+
if (trim($secret) === '') {
|
| 147 |
+
// Don't allow a blank secret to validate.
|
| 148 |
+
return false;
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
$detected = 'sha256=' . hash_hmac('sha256', $requestBody, $secret, false);
|
| 152 |
+
|
| 153 |
+
if (function_exists('hash_equals')) {
|
| 154 |
+
// Favor PHP's secure hash comparison function in 5.6 and up.
|
| 155 |
+
// For a robust drop-in compatibility shim, see: https://github.com/indigophp/hash-compat
|
| 156 |
+
return hash_equals($detected, $signatureHeader);
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
return $detected === $signatureHeader;
|
| 160 |
}
|
| 161 |
}
|
app/code/community/PriceWaiter/NYPWidget/controllers/RevokedealController.php
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
/*
|
| 4 |
+
* Copyright 2013-2016 Price Waiter, LLC
|
| 5 |
+
*
|
| 6 |
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
| 7 |
+
* you may not use this file except in compliance with the License.
|
| 8 |
+
* You may obtain a copy of the License at
|
| 9 |
+
*
|
| 10 |
+
* http://www.apache.org/licenses/LICENSE-2.0
|
| 11 |
+
*
|
| 12 |
+
* Unless required by applicable law or agreed to in writing, software
|
| 13 |
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
| 14 |
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 15 |
+
* See the License for the specific language governing permissions and
|
| 16 |
+
* limitations under the License.
|
| 17 |
+
*
|
| 18 |
+
*/
|
| 19 |
+
|
| 20 |
+
/**
|
| 21 |
+
* Controller that handles /pricewaiter/revokedeal
|
| 22 |
+
*/
|
| 23 |
+
class PriceWaiter_NYPWidget_RevokedealController extends PriceWaiter_NYPWidget_Controller_Endpoint
|
| 24 |
+
{
|
| 25 |
+
/**
|
| 26 |
+
* Versions of request data this controller supports.
|
| 27 |
+
* @var Array
|
| 28 |
+
*/
|
| 29 |
+
protected $supportedVersions = [
|
| 30 |
+
'2016-03-01',
|
| 31 |
+
];
|
| 32 |
+
|
| 33 |
+
public function processRequest(PriceWaiter_NYPWidget_Controller_Endpoint_Request $request)
|
| 34 |
+
{
|
| 35 |
+
$body = $request->getBody();
|
| 36 |
+
$id = isset($body->id) ? $body->id : null;
|
| 37 |
+
|
| 38 |
+
// Find the Deal
|
| 39 |
+
$deal = Mage::getModel('nypwidget/deal')->load($id);
|
| 40 |
+
|
| 41 |
+
if (!$deal->getId()) {
|
| 42 |
+
throw new PriceWaiter_NYPWidget_Exception_DealNotFound();
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
$deal->processRevokeRequest($request);
|
| 46 |
+
|
| 47 |
+
return PriceWaiter_NYPWidget_Controller_Endpoint_Response::ok();
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
}
|
app/code/community/PriceWaiter/NYPWidget/etc/config.xml
CHANGED
|
@@ -3,7 +3,7 @@
|
|
| 3 |
|
| 4 |
<modules>
|
| 5 |
<PriceWaiter_NYPWidget>
|
| 6 |
-
<version>
|
| 7 |
</PriceWaiter_NYPWidget>
|
| 8 |
</modules>
|
| 9 |
|
|
@@ -22,6 +22,12 @@
|
|
| 22 |
<order>
|
| 23 |
<table>nypwidget_orders</table>
|
| 24 |
</order>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
</entities>
|
| 26 |
</nypwidget_mysql4>
|
| 27 |
<nypwidget_category>
|
|
@@ -84,7 +90,38 @@
|
|
| 84 |
</catalog_category_prepare_save_pricewaiter>
|
| 85 |
</observers>
|
| 86 |
</catalog_category_prepare_save>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
</events>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
</global>
|
| 89 |
|
| 90 |
<frontend>
|
|
@@ -103,6 +140,13 @@
|
|
| 103 |
<frontName>pricewaiter</frontName>
|
| 104 |
</args>
|
| 105 |
</nypwidget>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
</routers>
|
| 107 |
</frontend>
|
| 108 |
|
| 3 |
|
| 4 |
<modules>
|
| 5 |
<PriceWaiter_NYPWidget>
|
| 6 |
+
<version>3.0.0</version>
|
| 7 |
</PriceWaiter_NYPWidget>
|
| 8 |
</modules>
|
| 9 |
|
| 22 |
<order>
|
| 23 |
<table>nypwidget_orders</table>
|
| 24 |
</order>
|
| 25 |
+
<deal>
|
| 26 |
+
<table>nypwidget_deal</table>
|
| 27 |
+
</deal>
|
| 28 |
+
<deal_usage>
|
| 29 |
+
<table>nypwidget_deal_usage</table>
|
| 30 |
+
</deal_usage>
|
| 31 |
</entities>
|
| 32 |
</nypwidget_mysql4>
|
| 33 |
<nypwidget_category>
|
| 90 |
</catalog_category_prepare_save_pricewaiter>
|
| 91 |
</observers>
|
| 92 |
</catalog_category_prepare_save>
|
| 93 |
+
<customer_logout>
|
| 94 |
+
<observers>
|
| 95 |
+
<customer_logout_pricewaiter>
|
| 96 |
+
<type>model</type>
|
| 97 |
+
<class>nypwidget/observer</class>
|
| 98 |
+
<method>handleCustomerLogout</method>
|
| 99 |
+
<args></args>
|
| 100 |
+
</customer_logout_pricewaiter>
|
| 101 |
+
</observers>
|
| 102 |
+
</customer_logout>
|
| 103 |
+
<sales_model_service_quote_submit_success>
|
| 104 |
+
<observers>
|
| 105 |
+
<sales_model_service_quote_submit_success_pricewaiter>
|
| 106 |
+
<type>model</type>
|
| 107 |
+
<class>nypwidget/observer</class>
|
| 108 |
+
<method>tieOrderToPriceWaiterDeals</method>
|
| 109 |
+
<args></args>
|
| 110 |
+
</sales_model_service_quote_submit_success_pricewaiter>
|
| 111 |
+
</observers>
|
| 112 |
+
</sales_model_service_quote_submit_success>
|
| 113 |
</events>
|
| 114 |
+
<sales>
|
| 115 |
+
<quote>
|
| 116 |
+
<totals>
|
| 117 |
+
<pricewaiter>
|
| 118 |
+
<class>nypwidget/total_quote</class>
|
| 119 |
+
<after>discount,subtotal</after>
|
| 120 |
+
<before>grand_total</before>
|
| 121 |
+
</pricewaiter>
|
| 122 |
+
</totals>
|
| 123 |
+
</quote>
|
| 124 |
+
</sales>
|
| 125 |
</global>
|
| 126 |
|
| 127 |
<frontend>
|
| 140 |
<frontName>pricewaiter</frontName>
|
| 141 |
</args>
|
| 142 |
</nypwidget>
|
| 143 |
+
<pricewaiter_deal>
|
| 144 |
+
<use>standard</use>
|
| 145 |
+
<args>
|
| 146 |
+
<module>PriceWaiter_NYPWidget</module>
|
| 147 |
+
<frontName>_dealpw</frontName>
|
| 148 |
+
</args>
|
| 149 |
+
</pricewaiter_deal>
|
| 150 |
</routers>
|
| 151 |
</frontend>
|
| 152 |
|
app/code/community/PriceWaiter/NYPWidget/sql/nypwidget_setup/mysql4-install-1.0.0.php
CHANGED
|
@@ -49,3 +49,4 @@ CREATE TABLE {$this->getTable('nypwidget_category')} (
|
|
| 49 |
// ->updateAttributes(array($product->getId()), array('nypwidget_enabled' => 1), 0);
|
| 50 |
// }
|
| 51 |
|
|
|
| 49 |
// ->updateAttributes(array($product->getId()), array('nypwidget_enabled' => 1), 0);
|
| 50 |
// }
|
| 51 |
|
| 52 |
+
?>
|
app/code/community/PriceWaiter/NYPWidget/sql/nypwidget_setup/mysql4-upgrade-2.5.4-3.0.0.php
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
$installer = $this;
|
| 4 |
+
$installer->startSetup();
|
| 5 |
+
|
| 6 |
+
$connection = $installer->getConnection();
|
| 7 |
+
|
| 8 |
+
$dealsTable = $connection
|
| 9 |
+
->newTable($installer->getTable('nypwidget/deal'))
|
| 10 |
+
->addColumn(
|
| 11 |
+
'deal_id',
|
| 12 |
+
Varien_Db_Ddl_Table::TYPE_VARCHAR,
|
| 13 |
+
36,
|
| 14 |
+
array(
|
| 15 |
+
'nullable' => false,
|
| 16 |
+
'primary' => true,
|
| 17 |
+
),
|
| 18 |
+
'Unique Deal ID'
|
| 19 |
+
)
|
| 20 |
+
->addColumn(
|
| 21 |
+
'revoked',
|
| 22 |
+
Varien_Db_Ddl_Table::TYPE_BOOLEAN,
|
| 23 |
+
null,
|
| 24 |
+
array(
|
| 25 |
+
'default' => 0,
|
| 26 |
+
'nullable' => false,
|
| 27 |
+
),
|
| 28 |
+
'Whether this deal has been revoked'
|
| 29 |
+
)
|
| 30 |
+
->addColumn(
|
| 31 |
+
'store_id',
|
| 32 |
+
Varien_Db_Ddl_Table::TYPE_INTEGER,
|
| 33 |
+
10,
|
| 34 |
+
array(
|
| 35 |
+
'nullable' => false,
|
| 36 |
+
'unsigned' => true,
|
| 37 |
+
),
|
| 38 |
+
'Store this deal was for'
|
| 39 |
+
)
|
| 40 |
+
->addColumn(
|
| 41 |
+
'create_request_id',
|
| 42 |
+
Varien_Db_Ddl_Table::TYPE_VARCHAR,
|
| 43 |
+
36,
|
| 44 |
+
array(
|
| 45 |
+
'nullable' => false,
|
| 46 |
+
),
|
| 47 |
+
'X-PriceWaiter-Request-Id header from create call'
|
| 48 |
+
)
|
| 49 |
+
->addColumn(
|
| 50 |
+
'created_at',
|
| 51 |
+
Varien_Db_Ddl_Table::TYPE_DATETIME,
|
| 52 |
+
null,
|
| 53 |
+
array(
|
| 54 |
+
'nullable' => false,
|
| 55 |
+
),
|
| 56 |
+
'Create date (UTC)'
|
| 57 |
+
)
|
| 58 |
+
->addColumn(
|
| 59 |
+
'revoke_request_id',
|
| 60 |
+
Varien_Db_Ddl_Table::TYPE_VARCHAR,
|
| 61 |
+
36,
|
| 62 |
+
array(
|
| 63 |
+
'nullable' => true,
|
| 64 |
+
),
|
| 65 |
+
'X-PriceWaiter-Request-Id header from revoke call'
|
| 66 |
+
)
|
| 67 |
+
->addColumn(
|
| 68 |
+
'revoked_at',
|
| 69 |
+
Varien_Db_Ddl_Table::TYPE_DATETIME,
|
| 70 |
+
null,
|
| 71 |
+
array(
|
| 72 |
+
'nullable' => true,
|
| 73 |
+
),
|
| 74 |
+
'Revoke date (UTC)'
|
| 75 |
+
)
|
| 76 |
+
->addColumn(
|
| 77 |
+
'create_request_body_json',
|
| 78 |
+
Varien_Db_Ddl_Table::TYPE_TEXT,
|
| 79 |
+
null,
|
| 80 |
+
array(
|
| 81 |
+
'nullable' => false,
|
| 82 |
+
),
|
| 83 |
+
'Full JSON for the create deal request.'
|
| 84 |
+
)
|
| 85 |
+
->addColumn(
|
| 86 |
+
'expires_at',
|
| 87 |
+
Varien_Db_Ddl_Table::TYPE_DATETIME,
|
| 88 |
+
null,
|
| 89 |
+
array(
|
| 90 |
+
'nullable' => true,
|
| 91 |
+
),
|
| 92 |
+
'Expiry date (UTC)'
|
| 93 |
+
)
|
| 94 |
+
->addColumn(
|
| 95 |
+
'pricewaiter_buyer_id',
|
| 96 |
+
Varien_Db_Ddl_Table::TYPE_VARCHAR,
|
| 97 |
+
36,
|
| 98 |
+
array(
|
| 99 |
+
'nullable' => false,
|
| 100 |
+
),
|
| 101 |
+
'PriceWaiter buyer id'
|
| 102 |
+
)
|
| 103 |
+
->addColumn(
|
| 104 |
+
'order_id',
|
| 105 |
+
Varien_Db_Ddl_Table::TYPE_INTEGER,
|
| 106 |
+
10,
|
| 107 |
+
array(
|
| 108 |
+
'nullable' => true,
|
| 109 |
+
'unsigned' => true,
|
| 110 |
+
),
|
| 111 |
+
'Magento order id deal was used on (if any)'
|
| 112 |
+
)
|
| 113 |
+
// Add an index for quick lookup when applying deals
|
| 114 |
+
->addIndex(
|
| 115 |
+
$installer->getIdxName(array(
|
| 116 |
+
'pricewaiter_buyer_id',
|
| 117 |
+
'revoked',
|
| 118 |
+
'order_id',
|
| 119 |
+
'expires_at',
|
| 120 |
+
)),
|
| 121 |
+
array(
|
| 122 |
+
'pricewaiter_buyer_id',
|
| 123 |
+
'revoked',
|
| 124 |
+
'order_id',
|
| 125 |
+
'expires_at',
|
| 126 |
+
),
|
| 127 |
+
array(
|
| 128 |
+
'type' => Varien_Db_Adapter_Interface::INDEX_TYPE_INDEX,
|
| 129 |
+
)
|
| 130 |
+
)
|
| 131 |
+
// Index on just order_id for quicker order listing
|
| 132 |
+
->addIndex(
|
| 133 |
+
$installer->getIdxName(array('order_id')),
|
| 134 |
+
array('order_id'),
|
| 135 |
+
array(
|
| 136 |
+
'type' => Varien_Db_Adapter_Interface::INDEX_TYPE_INDEX,
|
| 137 |
+
)
|
| 138 |
+
)
|
| 139 |
+
->setComment('PriceWaiter Deals');
|
| 140 |
+
|
| 141 |
+
$dealUsageTable = $connection
|
| 142 |
+
->newTable($installer->getTable('nypwidget/deal_usage'))
|
| 143 |
+
->setComment('Tracks PriceWaiter Deal usage on quotes')
|
| 144 |
+
->addColumn(
|
| 145 |
+
'deal_id',
|
| 146 |
+
Varien_Db_Ddl_Table::TYPE_VARCHAR,
|
| 147 |
+
36,
|
| 148 |
+
array(
|
| 149 |
+
'nullable' => false,
|
| 150 |
+
),
|
| 151 |
+
'PriceWaiter deal uuid'
|
| 152 |
+
)
|
| 153 |
+
->addColumn(
|
| 154 |
+
'quote_id',
|
| 155 |
+
Varien_Db_Ddl_Table::TYPE_INTEGER,
|
| 156 |
+
10,
|
| 157 |
+
array(
|
| 158 |
+
'nullable' => false,
|
| 159 |
+
'unsigned' => true,
|
| 160 |
+
),
|
| 161 |
+
'Quote deal was used on'
|
| 162 |
+
)
|
| 163 |
+
->addForeignKey(
|
| 164 |
+
$installer->getFkName(
|
| 165 |
+
'nypwidget/deal_usage',
|
| 166 |
+
'quote_id',
|
| 167 |
+
'sales/quote',
|
| 168 |
+
'entity_id'
|
| 169 |
+
),
|
| 170 |
+
'quote_id',
|
| 171 |
+
$installer->getTable('sales/quote'),
|
| 172 |
+
'entity_id',
|
| 173 |
+
// Cascade deletes when quote table is cleaned up
|
| 174 |
+
Varien_Db_Ddl_Table::ACTION_CASCADE
|
| 175 |
+
)
|
| 176 |
+
->addIndex(
|
| 177 |
+
$installer->getIdxName(array(
|
| 178 |
+
'deal_id',
|
| 179 |
+
'quote_id',
|
| 180 |
+
)),
|
| 181 |
+
array(
|
| 182 |
+
'deal_id',
|
| 183 |
+
'quote_id',
|
| 184 |
+
),
|
| 185 |
+
array(
|
| 186 |
+
'type' => Varien_Db_Adapter_Interface::INDEX_TYPE_PRIMARY,
|
| 187 |
+
)
|
| 188 |
+
);
|
| 189 |
+
|
| 190 |
+
$connection->createTable($dealsTable);
|
| 191 |
+
$connection->createTable($dealUsageTable);
|
| 192 |
+
|
| 193 |
+
$installer->endSetup();
|
app/design/frontend/base/default/template/pricewaiter/widget.phtml
CHANGED
|
@@ -1,84 +1,15 @@
|
|
| 1 |
-
<?php
|
| 2 |
-
<?php if ($_product->isSaleable()): ?>
|
| 3 |
-
<?php
|
| 4 |
-
$customCss = Mage::getStoreConfig('pricewaiter/appearance/display_custom_css');
|
| 5 |
-
$helper = $this->_getHelper();
|
| 6 |
|
| 7 |
-
|
| 8 |
-
$extensionVersion = Mage::getConfig()->getNode()->modules->PriceWaiter_NYPWidget->version;
|
| 9 |
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
$groupedInformation = $helper->getGroupedProductInfo();
|
| 16 |
-
} else {
|
| 17 |
-
$groupedInformation = '';
|
| 18 |
-
}
|
| 19 |
-
if ($helper->isEnabledForStore()):
|
| 20 |
-
?>
|
| 21 |
-
<script type="text/javascript">
|
| 22 |
-
//<![CDATA[
|
| 23 |
-
<?php echo $groupedInformation; ?>
|
| 24 |
-
var PriceWaiterOptions = {
|
| 25 |
-
<?php if ($helper->isButtonEnabled()): ?>
|
| 26 |
-
enableButton: true,
|
| 27 |
-
<?php else: ?>
|
| 28 |
-
enableButton: false,
|
| 29 |
-
<?php endif; ?>
|
| 30 |
-
currency: '<?php echo $currency; ?>',
|
| 31 |
-
<?php if ($helper->isConversionToolsEnabled()): ?>
|
| 32 |
-
enableConversionTools: true,
|
| 33 |
-
<?php else: ?>
|
| 34 |
-
enableConversionTools: false,
|
| 35 |
-
<?php endif; ?>
|
| 36 |
-
|
| 37 |
-
product: {
|
| 38 |
-
<?php if ($brand): ?>
|
| 39 |
-
brand: <?php echo json_encode($brand); ?>,
|
| 40 |
-
<?php endif; ?>
|
| 41 |
-
sku: <?php echo json_encode($_product->getSku()); ?>,
|
| 42 |
-
name: <?php echo json_encode($_product->getName()); ?>,
|
| 43 |
-
price: <?php echo json_encode($helper->getProductPrice($_product)); ?>,
|
| 44 |
-
<?php if ($image != ''): ?>
|
| 45 |
-
image: <?php echo json_encode($image); ?>
|
| 46 |
-
<?php endif; ?>
|
| 47 |
-
},
|
| 48 |
-
metadata: {
|
| 49 |
-
_magento_version: "<?php echo $magentoVersion; ?>",
|
| 50 |
-
_magento_extention_version: "<?php echo $extensionVersion; ?>"
|
| 51 |
-
}
|
| 52 |
-
};
|
| 53 |
-
|
| 54 |
-
var PriceWaiterCategories = <?php echo $helper->getCategoriesAsJSON($_product); ?>;
|
| 55 |
-
|
| 56 |
-
var PriceWaiterRegularPrice = '<?php echo (float) $_product->getPrice(); ?>';
|
| 57 |
-
var PriceWaiterProductType = '<?php echo $_product->getTypeId(); ?>';
|
| 58 |
-
//]]>
|
| 59 |
-
</script>
|
| 60 |
-
<div class="name-your-price-widget"
|
| 61 |
-
style='display: block; clear: both; padding-top: 10px; <?php echo $customCss; ?>'>
|
| 62 |
<span id="pricewaiter"></span>
|
| 63 |
</div>
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
<?php
|
| 67 |
-
$simples = Mage::getModel('catalog/product_type_configurable')->setProduct($_product)
|
| 68 |
-
->getUsedProductCollection()
|
| 69 |
-
->addAttributeToSelect('sku');
|
| 70 |
-
|
| 71 |
-
$idToSku = array();
|
| 72 |
-
foreach ($simples as $simple) {
|
| 73 |
-
$idToSku[$simple->getId()] = $simple->getSku();
|
| 74 |
-
} ?>
|
| 75 |
-
<?php if (count($idToSku) > 0): ?>
|
| 76 |
-
<script type="text/javascript">
|
| 77 |
-
var PriceWaiterIdToSkus = <?php echo json_encode($idToSku); ?>;
|
| 78 |
-
</script>
|
| 79 |
-
<?php endif; ?>
|
| 80 |
-
<?php endif; ?>
|
| 81 |
-
|
| 82 |
-
<script src="<?php echo $helper->getWidgetUrl(); ?>" async></script>
|
| 83 |
|
| 84 |
-
|
| 1 |
+
<?php
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
+
$embed = $this->getEmbed();
|
|
|
|
| 4 |
|
| 5 |
+
if ($embed->shouldRenderButtonPlaceholder()) {
|
| 6 |
+
echo '
|
| 7 |
+
<div
|
| 8 |
+
class="name-your-price-widget"
|
| 9 |
+
style="display: block; clear: both; padding-top: 10px;">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
<span id="pricewaiter"></span>
|
| 11 |
</div>
|
| 12 |
+
';
|
| 13 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
+
echo implode(PHP_EOL, $embed->getScriptTags());
|
package.xml
CHANGED
|
@@ -1,14 +1,14 @@
|
|
| 1 |
<?xml version="1.0"?>
|
| 2 |
<package>
|
| 3 |
<name>nypwidget</name>
|
| 4 |
-
<version>
|
| 5 |
<stability>stable</stability>
|
| 6 |
<license uri="http://www.apache.org/licenses/LICENSE-2.0.html">Apache License, Version 2.0</license>
|
| 7 |
<channel>community</channel>
|
| 8 |
<extends/>
|
| 9 |
<summary>PriceWaiter lets buyers make offers on your products that you can easily accept, counter offer or reject.</summary>
|
| 10 |
-
<description>Sell more, immediately, with
|
| 11 |
-
<notes>
|
| 12 |
<authors>
|
| 13 |
<author>
|
| 14 |
<name>PriceWaiter</name>
|
|
@@ -16,9 +16,9 @@
|
|
| 16 |
<email>extensions@pricewaiter.com</email>
|
| 17 |
</author>
|
| 18 |
</authors>
|
| 19 |
-
<date>2016-
|
| 20 |
-
<time>
|
| 21 |
-
<contents><target name="mageweb"><dir name="app"><dir name="code"><dir name="community"><dir name="PriceWaiter"><file name=".DS_Store" hash="2a209fa87a026f2f0ca9c35ac878a3c6"/><dir name="NYPWidget"><file name=".DS_Store" hash="4b6f2307f11e3f2f15c48e055cb7c96a"/><dir name="Block"><dir name="Adminhtml"><file name="Link.php" hash="f17d6461e82c76a3a49266aa6a6b23e4"/><file name="Signup.php" hash="f3464994c972a3f0dcc7747ed011bbff"/><file name="Widget.php" hash="a5a15261dae201aca09a8249fe7e8a81"/></dir><file name="Category.php" hash="1047b4af8b7e438964dff8d82e7d8cf6"/><file name="Widget.php" hash="
|
| 22 |
<compatible/>
|
| 23 |
<dependencies>
|
| 24 |
<required>
|
| 1 |
<?xml version="1.0"?>
|
| 2 |
<package>
|
| 3 |
<name>nypwidget</name>
|
| 4 |
+
<version>3.0.0</version>
|
| 5 |
<stability>stable</stability>
|
| 6 |
<license uri="http://www.apache.org/licenses/LICENSE-2.0.html">Apache License, Version 2.0</license>
|
| 7 |
<channel>community</channel>
|
| 8 |
<extends/>
|
| 9 |
<summary>PriceWaiter lets buyers make offers on your products that you can easily accept, counter offer or reject.</summary>
|
| 10 |
+
<description>Sell more, immediately, with PriceWaiter. Convert comparison shoppers you might have lost, increase sales and conversions, and stop "minimum advertised price" from preventing sales before they even start.

 PriceWaiter lets customers make offers on products you sell-- offers you can accept, reject or counter. The widget embeds a simple Name Your Price button on any product page or category that you choose. Simply install the extension and you'll have full control over PriceWaiter in your Magento admin control panel.</description>
|
| 11 |
+
<notes>Adds support for checkout via Magento.</notes>
|
| 12 |
<authors>
|
| 13 |
<author>
|
| 14 |
<name>PriceWaiter</name>
|
| 16 |
<email>extensions@pricewaiter.com</email>
|
| 17 |
</author>
|
| 18 |
</authors>
|
| 19 |
+
<date>2016-11-21</date>
|
| 20 |
+
<time>20:55:50</time>
|
| 21 |
+
<contents><target name="mageweb"><dir name="app"><dir name="code"><dir name="community"><dir name="PriceWaiter"><file name=".DS_Store" hash="2a209fa87a026f2f0ca9c35ac878a3c6"/><dir name="NYPWidget"><file name=".DS_Store" hash="4b6f2307f11e3f2f15c48e055cb7c96a"/><dir name="Block"><dir name="Adminhtml"><file name="Link.php" hash="f17d6461e82c76a3a49266aa6a6b23e4"/><file name="Signup.php" hash="f3464994c972a3f0dcc7747ed011bbff"/><file name="Widget.php" hash="a5a15261dae201aca09a8249fe7e8a81"/></dir><file name="Category.php" hash="1047b4af8b7e438964dff8d82e7d8cf6"/><file name="Widget.php" hash="abeee723164cb607c5110aee2b12bc0a"/><dir name="Payment"><dir name="Info"><file name="Pricewaiter.php" hash="ea9b94211a536552689d95c1664894e7"/></dir></dir></dir><dir name="Controller"><dir name="Endpoint"><file name="Request.php" hash="8e706ce0dd68d2d45aa9fcfa0fd79d17"/><file name="Response.php" hash="f205fca4133184d6dc1c220f6da7946e"/></dir><file name="Endpoint.php" hash="2a462bffd94b57aa1955bac03b8433c7"/></dir><dir name="controllers"><dir name="Adminhtml"><file name="PricewaiterController.php" hash="29d6f14dc97fde5c5d1c5bb969692b9a"/></dir><file name="CallbackController.php" hash="25c5ab8b737df6e24bba57c8b46b74d5"/><file name="CheckoutController.php" hash="1050baec45534d63f824e9a931b3f824"/><file name="CreatedealController.php" hash="38edc9dc1c7935e1d0c1bd76d626bef8"/><file name="DebugController.php" hash="c9fa18395b1e649ccc795ad9f7cd5321"/><file name="ListordersController.php" hash="61d01b554778cd0e2f84ecf6e0af6f72"/><file name="PingController.php" hash="4103dd3758146f333b0c7e667c169ff3"/><file name="ProductinfoController.php" hash="160369f5d9c037f4ca1f13b3c579bc32"/><file name="RevokedealController.php" hash="da35b7ce683753d009164fea3ce564b2"/></dir><dir name="etc"><file name="adminhtml.xml" hash="57af207c7a6966d365ed41ebe6d88ecb"/><file name="config.xml" hash="f0afa04e0438dc4bbfd81e48323f9559"/><file name="system.xml" hash="3d83e0edf292b6e653d30f05382f1d9b"/></dir><dir name="Exception"><file name="Abstract.php" hash="6a86e327c5f5110bfa5249d92573a871"/><file name="ApiKey.php" hash="0cb0715d3242092d5e1fbecb8d978f59"/><file name="DealAlreadyCreated.php" hash="0fa91d8e9a2ef47d9039b589c2c8cc81"/><file name="DealAlreadyRevoked.php" hash="6de1fbb0df19f224923c615f13edb49b"/><file name="DealNotFound.php" hash="76e8da946df39482f2768d1eae36f790"/><file name="DuplicateOrder.php" hash="91eb14444c1bb6e01ddf2a78b92419c9"/><file name="InvalidOrderData.php" hash="6bbb8a438f1464acae9077b7d00f197c"/><file name="InvalidRegion.php" hash="e27b75878d290011b541b6cab032f116"/><file name="NoTestDeals.php" hash="f663a330d268cf43f14c41ca5c0a77bc"/><file name="OrderNotFound.php" hash="57eb66ac79ae7bd771a60ac7ae5e9da6"/><file name="Signature.php" hash="55e22f7459e76f4341e45ae3d8bb3d97"/><file name="SingleItemOnly.php" hash="12f83b8017cef267a595f5aabcbcea87"/><file name="Version.php" hash="7f2c0e0a398e7cde26c9d9649fbc09c3"/><dir name="Product"><file name="Abstract.php" hash="859a53ed0e6d3f9914044ec14aef6e47"/><file name="Invalid.php" hash="050fc42ad68af5ddeadd050df31c6028"/><file name="NotFound.php" hash="91e63e96104b9ecb79bdede2df23b4c1"/><file name="OutOfStock.php" hash="430c5d119a77cdda6323561aec065c22"/></dir></dir><dir name="Helper"><file name="About.php" hash="90a78f52efc937586cd370735d9672a5"/><file name="Data.php" hash="57caf1984bfb86ac0eaef4846dbcdebc"/></dir><dir name="Model"><dir name="Callback"><file name="Inventory.php" hash="6a69e883cc1403cfacfc40d7069490ac"/></dir><file name="Callback.php" hash="fb2b22cc32710efd35cacadb9ed941ca"/><file name="Category.php" hash="18826308eba00faaeffea8b473198ca4"/><file name="Deal.php" hash="7f1406b5d13d40634f6f917cab61e4b7"/><file name="Discounter.php" hash="5b6f0ea67a70917a963cf25736f957bb"/><file name="Embed.php" hash="e43299138e67613d3c7a4c220f47edde"/><file name="Observer.php" hash="a266be2b690e2914a213d916fef3ed9d"/><file name="Order.php" hash="86206e5678929e57b833f7ea4bc6bf96"/><file name="PaymentMethod.php" hash="0fa5e9a67512f578226e916b319052b7"/><file name="Session.php" hash="6c8c9fba62ba926d9af7b3c40a074d5c"/><dir name="Carrier"><file name="ShippingMethod.php" hash="93baf3d128481be22154b103cdf5d627"/></dir><dir name="Display"><file name="Phrase.php" hash="54339c37d5d88f0b2e68dfbe814af2e2"/><file name="Size.php" hash="2a70f37e54202f4a8de5a3f0b376f090"/></dir><dir name="Mysql4"><dir name="Category"><file name="Collection.php" hash="a5113a9db28d82cedcc8eb2c9b609a88"/></dir><file name="Category.php" hash="656c556879e61bbb20733ffc1dbe38d0"/><file name="Deal.php" hash="6d94a010e9e213057deb4677e5c166e0"/><file name="Order.php" hash="ebecab2ef597171207ae787b0ddb68df"/><dir name="Deal"><file name="Collection.php" hash="4bdd3c43fee16b80c9188af232c5e9ef"/><file name="Usage.php" hash="29193d08ab3054c0e61ab38da288d452"/></dir><dir name="Order"><file name="Collection.php" hash="07773d6ca5bdf74d14e2d3290466b28f"/></dir></dir><dir name="Offer"><dir name="Item"><file name="Handler.php" hash="1704e8065802116706a3fd2f1842fb94"/><file name="Inventory.php" hash="b518984933c386f7dba06305bb3652ee"/><file name="Pricing.php" hash="ca342c391bd18bf199afef6def2902af"/></dir><file name="Item.php" hash="bc4306df349ecac42b88e510a69c184a"/></dir><dir name="Resource"><dir name="Eav"><dir name="Mysql4"><file name="Setup.php" hash="c23a03f23524957235bb3cbd1363551b"/></dir></dir></dir><dir name="Total"><file name="Quote.php" hash="7354457003e0cb0cfc4f7bc129e6f4a3"/></dir></dir><dir name="sql"><dir name="nypwidget_setup"><file name="mysql4-install-1.0.0.php" hash="b977c3a62af93441d115650b543ff858"/><file name="mysql4-upgrade-1.1.2-1.1.3.php" hash="df453265b32071329de809aa750dd31f"/><file name="mysql4-upgrade-1.1.7-1.1.8.php" hash="9b18e12698cbdc6896666b26362a7ddb"/><file name="mysql4-upgrade-1.2.4-1.2.5.php" hash="5e501f27bf223c34e19443923e998021"/><file name="mysql4-upgrade-1.3.0-1.3.1.php" hash="4cd7633a027696aa74668c61c58a83e2"/><file name="mysql4-upgrade-2.1.5-2.2.0.php" hash="be3f748105ff6bf61d4329b967c09c94"/><file name="mysql4-upgrade-2.2.0-2.5.0.php" hash="c02dacf91e070d7274daeda881e68f2a"/><file name="mysql4-upgrade-2.5.4-3.0.0.php" hash="c2366841c23fb9242b5332fad1615522"/></dir></dir></dir></dir></dir></dir><dir name="design"><dir name="adminhtml"><dir name="default"><dir name="default"><dir name="layout"><file name="pricewaiter.xml" hash="646560535c94bf42ed75438ec9cc6b9f"/></dir><dir name="template"><dir name="pricewaiter"><file name="categorytab.phtml" hash="703fcf0daa0fb71bef23987a9896bd29"/><file name="signup.phtml" hash="fc18c669c18c55cccdf2c66c96199dec"/></dir></dir></dir></dir></dir><dir name="frontend"><dir name="base"><dir name="default"><dir name="layout"><file name="pricewaiter.xml" hash="d578d936a726cca4837d5f2e58c5ca45"/></dir><dir name="template"><dir name="pricewaiter"><file name="widget.phtml" hash="018ad07f442de5837317ecfac18ab01d"/></dir></dir></dir></dir></dir></dir><dir name="etc"><dir name="modules"><file name="PriceWaiter_NYPWidget.xml" hash="7649918eb71009656f956a077f0cf6a8"/></dir></dir></dir><dir name="js"><dir name="pricewaiter"><file name="product-pages.js" hash="775a2c04db1f58c50f204ed7e15aef76"/><file name="token.js" hash="79ce2bdf4d9ae33fb4ef38c298f2dffe"/></dir></dir><dir name="skin"><dir name="adminhtml"><dir name="default"><dir name="default"><dir name="images"><file name="pricewaiter_logo.png" hash="becb9713a561131ff69eabb7503d3e74"/><file name="pricewaiter_tab.png" hash="1bab71be6b1a93aee2ae7aeae3807484"/><file name="pricewaiter_logo.png" hash="becb9713a561131ff69eabb7503d3e74"/><file name="pricewaiter_tab.png" hash="1bab71be6b1a93aee2ae7aeae3807484"/></dir><file name="pricewaiter.css" hash="cbda20d51ec4c1505962c1e79007d7f7"/></dir></dir></dir></dir></target></contents>
|
| 22 |
<compatible/>
|
| 23 |
<dependencies>
|
| 24 |
<required>
|
