• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

aimeos / aimeos-core / 4f8b7e8d-5595-43b2-ba5c-df9921830178

17 May 2026 07:32AM UTC coverage: 92.581%. Remained the same
4f8b7e8d-5595-43b2-ba5c-df9921830178

push

circleci

aimeos
Fixed PHPStan issues

896 of 980 new or added lines in 165 files covered. (91.43%)

35 existing lines in 29 files now uncovered.

9734 of 10514 relevant lines covered (92.58%)

80.53 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.54
/src/MShop/Plugin/Provider/Order/ProductFreeOptions.php
1
<?php
2

3
/**
4
 * @license LGPLv3, https://opensource.org/licenses/LGPL-3.0
5
 * @copyright Aimeos (aimeos.org), 2017-2026
6
 * @package MShop
7
 * @subpackage Plugin
8
 */
9

10

11
namespace Aimeos\MShop\Plugin\Provider\Order;
12

13

14
/**
15
 * Defines the number of the free "config" attributes for products with configurable options
16
 *
17
 * If customers can configure products using several options they can add and each option must be paid additionally,
18
 * this plugin recalculates the total product price based on the added options.
19
 *
20
 * Example:
21
 *  <attribute type> : <number of free options>
22
 *
23
 * @package MShop
24
 * @subpackage Plugin
25
 */
26
class ProductFreeOptions
27
        extends \Aimeos\MShop\Plugin\Provider\Factory\Base
28
        implements \Aimeos\MShop\Plugin\Provider\Iface, \Aimeos\MShop\Plugin\Provider\Factory\Iface
29
{
30
        /**
31
         * Subscribes itself to a publisher
32
         *
33
         * @param \Aimeos\MShop\Order\Item\Iface $p Object implementing publisher interface
34
         * @return static Plugin object for method chaining
35
         */
36
        public function register( \Aimeos\MShop\Order\Item\Iface $p ) : static
37
        {
38
                $plugin = $this->object();
1✔
39

40
                $p->attach( $plugin, 'addProduct.after' );
1✔
41
                $p->attach( $plugin, 'setProducts.after' );
1✔
42

43
                return $this;
1✔
44
        }
45

46

47
        /**
48
         * Receives a notification from a publisher object
49
         *
50
         * @param \Aimeos\MShop\Order\Item\Iface $order Shop basket instance implementing publisher interface
51
         * @param string $action Name of the action to listen for
52
         * @param mixed $value Object or value changed in publisher
53
         * @return mixed Modified value parameter
54
         */
55
        public function update( \Aimeos\MShop\Order\Item\Iface $order, string $action, $value = null )
56
        {
57
                if( is_array( $value ) )
1✔
58
                {
59
                        foreach( $value as $key => $product ) {
1✔
60
                                // @phpstan-ignore argument.type
61
                                $value[$key] = $this->updatePrice( $product );
1✔
62
                        }
63
                }
64
                else
65
                {
66
                        // @phpstan-ignore argument.type
67
                        $value = $this->updatePrice( $value );
1✔
68
                }
69

70
                return $value;
1✔
71
        }
72

73

74
        /**
75
         * Adds the prices of the attribute items without the given amount of free items
76
         *
77
         * @param \Aimeos\MShop\Price\Item\Iface $price Product price item
78
         * @param array $attrItems Associative list of attribute IDs as keys and items with prices as values
79
         * @param array $quantities Associative list of attribute IDs as keys and their quantities as values
80
         * @param int $free Number of free items
81
         * @return \Aimeos\MShop\Price\Item\Iface Price item with attribute prices added
82
         */
83
        protected function addPrices( \Aimeos\MShop\Price\Item\Iface $price, array $attrItems, array $quantities,
84
                int $free ) : \Aimeos\MShop\Price\Item\Iface
85
        {
86
                $priceManager = \Aimeos\MShop::create( $this->context(), 'price' );
2✔
87

88
                foreach( $attrItems as $attrId => $attrItem )
2✔
89
                {
90
                        $prices = $attrItem->getRefItems( 'price', 'default', 'default' );
2✔
91

92
                        if( !$prices->isEmpty() )
2✔
93
                        {
94
                                $qty = ( isset( $quantities[$attrId] ) ? $quantities[$attrId] : 0 );
2✔
95

96
                                $quantity = ( $qty >= $free ? $qty - $free : 0 );
2✔
97
                                $free = ( $free >= $qty ? $free - $qty : 0 );
2✔
98

99
                                if( $quantity > 0 )
2✔
100
                                {
101
                                        $priceItem = $priceManager->getLowestPrice( $prices, $quantity );
2✔
102
                                        // @phpstan-ignore argument.type
103
                                        $price = $price->addItem( $priceItem, $quantity );
2✔
104
                                }
105
                        }
106
                }
107

108
                return $price;
2✔
109
        }
110

111

112
        /**
113
         * Returns the attribute items including the prices for the given IDs
114
         *
115
         * @param array $ids List of attribute IDs
116
         * @return array Associative List of attribute type and ID as keys and \Aimeos\MShop\Attribute\Item\Iface as values
117
         */
118
        protected function getAttributeMap( array $ids ) : array
119
        {
120
                $attrMap = [];
1✔
121
                $attrManager = \Aimeos\MShop::create( $this->context(), 'attribute' );
1✔
122

123
                $search = $attrManager->filter()->slice( 0, count( $ids ) );
1✔
124
                $search->setConditions( $search->compare( '==', 'attribute.id', $ids ) );
1✔
125

126
                foreach( $attrManager->search( $search, ['price'] ) as $attrId => $attrItem ) {
1✔
127
                        $attrMap[$attrItem->getType()][$attrId] = $attrItem;
1✔
128
                }
129

130
                return $attrMap;
1✔
131
        }
132

133

134
        /**
135
         * Sorts the given attribute items by their price (lowest first)
136
         *
137
         * @param \Aimeos\MShop\Attribute\Item\Iface[] $attrItems Associative list of attribute IDs as keys and items as values
138
         * @param array $attrQtys Associative list of attribute IDs as keys and their quantities as values
139
         * @return \Aimeos\MShop\Attribute\Item\Iface[] Sorted associative list of attribute IDs as keys and items as values
140
         */
141
        protected function sortByPrice( array $attrItems, array $attrQtys ) : array
142
        {
143
                $priceManager = \Aimeos\MShop::create( $this->context(), 'price' );
4✔
144

145
                $sortFcn = function( $a, $b ) use( $priceManager, $attrQtys )
4✔
146
                {
4✔
147
                        if( ( $pricesA = $a->getRefItems( 'price', 'default', 'default' )->toArray() ) === [] ) {
2✔
148
                                return 1;
1✔
149
                        }
150

151
                        if( ( $pricesB = $b->getRefItems( 'price', 'default', 'default' )->toArray() ) === [] ) {
1✔
152
                                return -1;
1✔
153
                        }
154

155
                        $qty = ( isset( $attrQtys[$a->getId()] ) ? $attrQtys[$a->getId()] : 0 );
×
156
                        $p1 = $priceManager->getLowestPrice( $pricesA, $qty );
×
157

158
                        $qty = ( isset( $attrQtys[$b->getId()] ) ? $attrQtys[$b->getId()] : 0 );
×
159
                        $p2 = $priceManager->getLowestPrice( $pricesB, $qty );
×
160

161
                        if( $p1->getValue() < $p2->getValue() ) {
×
162
                                return -1;
×
163
                        } elseif( $p1->getValue() > $p2->getValue() ) {
×
164
                                return 1;
×
165
                        }
166

167
                        return 0;
×
168
                };
4✔
169

170
                uasort( $attrItems, $sortFcn );
4✔
171

172
                return $attrItems;
4✔
173
        }
174

175

176
        /** Updates the price of the product
177
         *
178
         * @param \Aimeos\MShop\Order\Item\Product\Iface $product Ordered product for updating the price
179
         * @return \Aimeos\MShop\Order\Item\Product\Iface Ordered product with updated price
180
         */
181
        protected function updatePrice( \Aimeos\MShop\Order\Item\Product\Iface $product ) : \Aimeos\MShop\Order\Item\Product\Iface
182
        {
183
                $attrQtys = $attrTypes = [];
1✔
184
                $context = $this->context();
1✔
185
                $prodItem = \Aimeos\MShop::create( $context, 'product' )->get( $product->getProductId(), ['price'] );
1✔
186
                $prodConf = $prodItem->getConfig();
1✔
187

188

189
                foreach( $product->getAttributeItems( 'config' ) as $attr )
1✔
190
                {
191
                        $attrQtys[$attr->getAttributeId()] = $attr->getQuantity();
1✔
192
                        $attrTypes[] = $attr->getCode();
1✔
193
                }
194

195
                // @phpstan-ignore argument.type, argument.type
196
                if( array_intersect( $attrTypes, array_keys( $prodConf ) ) === [] ) {
1✔
197
                        return $product;
×
198
                }
199

200

201
                $prices = $prodItem->getRefItems( 'price', 'default', 'default' );
1✔
202
                $priceItem = \Aimeos\MShop::create( $context, 'price' )->getLowestPrice( $prices, $product->getQuantity() );
1✔
203

204
                foreach( $this->getAttributeMap( array_keys( $attrQtys ) ) as $type => $list )
1✔
205
                {
206
                        if( isset( $prodConf[$type] ) )
1✔
207
                        {
208
                                // @phpstan-ignore argument.type
209
                                $list = $this->sortByPrice( $list, $attrQtys );
1✔
210
                                // @phpstan-ignore argument.type
211
                                $priceItem = $this->addPrices( $priceItem, $list, $attrQtys, (int) $prodConf[$type] );
1✔
212
                        }
213
                        else
214
                        {
215
                                // @phpstan-ignore argument.type, argument.type
UNCOV
216
                                $priceItem = $this->addPrices( $priceItem, $list, $attrQtys, 0 );
×
217
                        }
218
                }
219

220
                // @phpstan-ignore argument.type
221
                return $product->setPrice( $priceItem );
1✔
222
        }
223
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc