Passed
Push — master ( 29d5f6...586595 )
by Aimeos
04:48
created

src/MShop/Plugin/Provider/Order/ProductPrice.php (1 issue)

Labels
Severity
1
<?php
2
3
/**
4
 * @license LGPLv3, http://opensource.org/licenses/LGPL-3.0
5
 * @copyright Metaways Infosystems GmbH, 2012
6
 * @copyright Aimeos (aimeos.org), 2015-2018
7
 * @package MShop
8
 * @subpackage Plugin
9
 */
10
11
12
namespace Aimeos\MShop\Plugin\Provider\Order;
13
14
15
/**
16
 * Checks the products in a basket for changed prices
17
 *
18
 * Notifies the customers if a price of a product in the basket has changed in
19
 * the meantime. This plugin can handle the change from net to gross prices and
20
 * backwards if prices are recalculated for B2B or B2C customers. In these cases
21
 * the customer won't be notified.
22
 *
23
 * To trace the execution and interaction of the plugins, set the log level to DEBUG:
24
 *	madmin/log/manager/standard/loglevel = 7
25
 *
26
 * @package MShop
27
 * @subpackage Plugin
28
 */
29
class ProductPrice
30
	extends \Aimeos\MShop\Plugin\Provider\Factory\Base
31
	implements \Aimeos\MShop\Plugin\Provider\Iface, \Aimeos\MShop\Plugin\Provider\Factory\Iface
32
{
33
	/**
34
	 * Subscribes itself to a publisher
35
	 *
36
	 * @param \Aimeos\MW\Observer\Publisher\Iface $p Object implementing publisher interface
37
	 */
38
	public function register( \Aimeos\MW\Observer\Publisher\Iface $p )
39
	{
40
		$p->addListener( $this->getObject(), 'check.after' );
41
	}
42
43
44
	/**
45
	 * Receives a notification from a publisher object
46
	 *
47
	 * @param \Aimeos\MW\Observer\Publisher\Iface $order Shop basket instance implementing publisher interface
48
	 * @param string $action Name of the action to listen for
49
	 * @param mixed $value Object or value changed in publisher
50
	 * @throws \Aimeos\MShop\Plugin\Provider\Exception if checks fail
51
	 * @return bool true if checks succeed
52
	 */
53
	public function update( \Aimeos\MW\Observer\Publisher\Iface $order, $action, $value = null )
54
	{
55
		if( ( $value & \Aimeos\MShop\Order\Item\Base\Base::PARTS_PRODUCT ) === 0 ) {
56
			return true;
57
		}
58
59
		\Aimeos\MW\Common\Base::checkClass( '\\Aimeos\\MShop\\Order\\Item\\Base\\Iface', $order );
60
61
		$attrIds = $prodCodes = $changedProducts = [];
62
		$orderProducts = $order->getProducts();
63
64
		foreach( $orderProducts as $pos => $item )
65
		{
66
			if( $item->getFlags() & \Aimeos\MShop\Order\Item\Base\Product\Base::FLAG_IMMUTABLE ) {
67
				unset( $orderProducts[$pos] );
68
			}
69
70
			$prodCodes[] = $item->getProductCode();
71
72
			foreach( $item->getAttributeItems() as $ordAttrItem )
73
			{
74
				if( ( $id = $ordAttrItem->getAttributeId() ) != '' ) {
75
					$attrIds[$id] = null;
76
				}
77
			}
78
		}
79
80
81
		$attributes = $this->getAttributes( array_keys( $attrIds ) );
82
		$prodMap = $this->getProducts( $prodCodes );
83
84
85
		foreach( $orderProducts as $pos => $orderProduct )
86
		{
87
			if( !isset( $prodMap[$orderProduct->getProductCode()] ) ) {
88
				continue; // Product isn't available or excluded
89
			}
90
91
			// fetch prices of articles/sub-products
92
			$refPrices = $prodMap[$orderProduct->getProductCode()]->getRefItems( 'price', 'default', 'default' );
93
94
			$orderPosPrice = $orderProduct->getPrice();
95
			$price = $this->getPrice( $orderProduct, $refPrices, $attributes, $pos );
96
97
			if( $orderPosPrice->getTaxFlag() === $price->getTaxFlag() && $orderPosPrice->compare( $price ) === false )
98
			{
99
				$orderProduct->setPrice( $price );
100
101
				$order->deleteProduct( $pos );
102
				$order->addProduct( $orderProduct, $pos );
103
104
				$changedProducts[$pos] = 'price.changed';
105
			}
106
		}
107
108
		if( count( $changedProducts ) > 0 )
109
		{
110
			$code = array( 'product' => $changedProducts );
111
			$msg = $this->getContext()->getI18n()->dt( 'mshop', 'Please have a look at the prices of the products in your basket' );
112
			throw new \Aimeos\MShop\Plugin\Provider\Exception( $msg, -1, null, $code );
113
		}
114
115
		return true;
116
	}
117
118
119
	/**
120
	 * Returns the attribute items for the given IDs.
121
	 *
122
	 * @param array $ids List of attribute IDs
123
	 * @return \Aimeos\MShop\Attribute\Item\Iface[] List of attribute items
124
	 */
125
	protected function getAttributes( array $ids )
126
	{
127
		if( empty( $ids ) ) {
128
			return [];
129
		}
130
131
		$attrManager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'attribute' );
132
133
		$search = $attrManager->createSearch( true );
134
		$expr = array(
135
			$search->compare( '==', 'attribute.id', $ids ),
136
			$search->getConditions(),
137
		);
138
		$search->setConditions( $search->combine( '&&', $expr ) );
139
140
		return $attrManager->searchItems( $search, array( 'price' ) );
141
	}
142
143
144
	/**
145
	 * Returns the product items for the given product codes.
146
	 *
147
	 * @param string[] $prodCodes Product codes
148
	 */
149
	protected function getProducts( array $prodCodes )
150
	{
151
		if( empty( $prodCodes ) ) {
152
			return [];
153
		}
154
155
		$attrManager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'attribute' );
156
		$productManager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'product' );
157
158
		$attrId = $attrManager->findItem( 'custom', [], 'product', 'price' )->getId();
159
160
		$search = $productManager->createSearch( true );
161
		$func = $search->createFunction( 'product:has', ['attribute', 'custom', $attrId] );
162
		$expr = array(
163
			$search->compare( '==', 'product.code', $prodCodes ),
164
			$search->compare( '==', $func, null ),
165
			$search->getConditions(),
166
		);
167
		$search->setConditions( $search->combine( '&&', $expr ) );
168
169
		$products = $productManager->searchItems( $search, array( 'price' ) );
170
171
		$prodMap = [];
172
173
		foreach( $products as $item ) {
174
			$prodMap[$item->getCode()] = $item;
175
		}
176
177
		return $prodMap;
178
	}
179
180
181
	/**
182
	 * Returns the actual price for the given order product.
183
	 *
184
	 * @param \Aimeos\MShop\Order\Item\Base\Product\Iface $orderProduct Ordered product
185
	 * @param array $refPrices Prices associated to the original product
186
	 * @param \Aimeos\MShop\Attribute\Item\Iface[] $attributes Attribute items with prices
187
	 * @param integer $pos Position of the product in the basket
188
	 * @return \Aimeos\MShop\Price\Item\Iface Price item including the calculated price
189
	 */
190
	private function getPrice( \Aimeos\MShop\Order\Item\Base\Product\Iface $orderProduct, array $refPrices, array $attributes, $pos )
191
	{
192
		$context = $this->getContext();
193
194
		// fetch prices of selection/parent products
195
		if( empty( $refPrices ) )
196
		{
197
			$productManager = \Aimeos\MShop\Factory::createManager( $context, 'product' );
198
			$product = $productManager->getItem( $orderProduct->getProductId(), array( 'price' ) );
199
			$refPrices = $product->getRefItems( 'price', 'default', 'default' );
1 ignored issue
show
The method getRefItems() does not exist on Aimeos\MShop\Common\Item\Iface. It seems like you code against a sub-type of Aimeos\MShop\Common\Item\Iface such as Aimeos\MShop\Product\Item\Iface or Aimeos\MShop\Service\Item\Iface or Aimeos\MShop\Customer\Item\Iface or Aimeos\MShop\Text\Item\Iface or Aimeos\MShop\Media\Item\Iface or Aimeos\MShop\Price\Item\Iface or Aimeos\MShop\Attribute\Item\Iface or Aimeos\MShop\Catalog\Item\Iface or Aimeos\MShop\Supplier\Item\Iface or Aimeos\MShop\Attribute\Item\Standard or Aimeos\MShop\Catalog\Item\Standard or Aimeos\MShop\Customer\Item\Base or Aimeos\MShop\Media\Item\Standard or Aimeos\MShop\Text\Item\Standard or Aimeos\MShop\Service\Item\Standard or Aimeos\MShop\Common\Item\ListRef\Base or Aimeos\MShop\Price\Item\Base or Aimeos\MShop\Supplier\Item\Standard or Aimeos\MShop\Product\Item\Standard or Aimeos\MShop\Product\Item\Iface or Aimeos\MShop\Service\Item\Iface or Aimeos\MShop\Customer\Item\Iface or Aimeos\MShop\Text\Item\Iface or Aimeos\MShop\Media\Item\Iface or Aimeos\MShop\Price\Item\Iface or Aimeos\MShop\Attribute\Item\Iface or Aimeos\MShop\Supplier\Item\Iface or Aimeos\MShop\Catalog\Item\Iface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

199
			/** @scrutinizer ignore-call */ 
200
   $refPrices = $product->getRefItems( 'price', 'default', 'default' );
Loading history...
200
		}
201
202
		if( empty( $refPrices ) )
203
		{
204
			$pid = $orderProduct->getProductId();
205
			$pcode = $orderProduct->getProductCode();
206
			$codes = array( 'product' => array( $pos => 'product.price' ) );
207
			$msg = $this->getContext()->getI18n()->dt( 'mshop', 'No price for product ID "%1$s" or product code "%2$s" available' );
208
209
			throw new \Aimeos\MShop\Plugin\Provider\Exception( sprintf( $msg, $pid, $pcode ), -1, null, $codes );
210
		}
211
212
		$priceManager = \Aimeos\MShop\Factory::createManager( $context, 'price' );
213
		$price = clone $priceManager->getLowestPrice( $refPrices, $orderProduct->getQuantity() );
214
215
		// add prices of product attributes to compute the end price for comparison
216
		foreach( $orderProduct->getAttributeItems() as $orderAttribute )
217
		{
218
			$attrPrices = [];
219
			$attrId = $orderAttribute->getAttributeId();
220
221
			if( isset( $attributes[$attrId] ) ) {
222
				$attrPrices = $attributes[$attrId]->getRefItems( 'price', 'default', 'default' );
223
			}
224
225
			if( !empty( $attrPrices ) ) {
226
				$price->addItem( $priceManager->getLowestPrice( $attrPrices, $orderAttribute->getQuantity() ), $orderAttribute->getQuantity() );
227
			}
228
		}
229
230
		// reset product rebates like in the basket controller
231
		$price->setRebate( '0.00' );
232
233
		return $price;
234
	}
235
}
236