BothInteract_ConfigurableProductVariantsImageAssignment - Version 1.0.0

Version Notes

Initial public release.

Download this release

Release Info

Developer Matthias Kerstner
Extension BothInteract_ConfigurableProductVariantsImageAssignment
Version 1.0.0
Comparing to
See all releases


Version 1.0.0

app/code/community/BothInteract/ConfigurableProductVariantsImageAssignment/Model/Observer.php ADDED
@@ -0,0 +1,330 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Observer class that handles automatic image assignment for child products of
5
+ * configurable products (i.e. associated products) once a configurable product
6
+ * is being save()'d.
7
+ *
8
+ * It is possible to set the list of required image types for child products:
9
+ * - @see self::$IMAGE_TYPE_BASE_IMAGE
10
+ * - @see self::$IMAGE_TYPE_SMALL_IMAGE
11
+ * - @see self::$IMAGE_TYPE_THUMBNAIL
12
+ *
13
+ * To set the list required image types please refer to
14
+ * @see $requiredChildProductImageTypes.
15
+ *
16
+ * All required image types set will be checked for each child product and will
17
+ * be set based on a copy of the parent's base image.
18
+ *
19
+ * This class writes log messages to a custom log file specified by
20
+ * @see self::$LOG_FILE.
21
+ *
22
+ * @author Matthias Kerstner <matthias@both-interact.com>
23
+ * @version 1.0.0
24
+ * @copyright (c) 2014, Both Interact GmbH
25
+ */
26
+ class BothInteract_ConfigurableProductVariantsImageAssignment_Model_Observer {
27
+
28
+ /** @var string name of logfile */
29
+ private static $LOG_FILE = 'bothinteract_configurableproductvariantsimageassignment.log';
30
+
31
+ /** @var string placeholder text if no image is set */
32
+ private static $IMAGE_NO_SELECTION = 'no_selection';
33
+
34
+ /** @var string base image type used by Magento */
35
+ private static $IMAGE_TYPE_BASE_IMAGE = 'image';
36
+
37
+ /** @var string small_image type used by Magento */
38
+ private static $IMAGE_TYPE_SMALL_IMAGE = 'small_image';
39
+
40
+ /** @var string thumbnail image type used by Magento */
41
+ private static $IMAGE_TYPE_THUMBNAIL = 'thumbnail';
42
+
43
+ /**
44
+ * Logs $msg to logfile.
45
+ * @param string $msg
46
+ */
47
+ private function logToFile($msg) {
48
+ Mage::log($msg, null, self::$LOG_FILE);
49
+ }
50
+
51
+ /**
52
+ * Returns absolute path the product's based image if set, otherwise NULL.
53
+ * @param Mage_Catalog_Model_Product $product
54
+ * @return string|NULL
55
+ */
56
+ private function getProductBaseImagePath(Mage_Catalog_Model_Product $product) {
57
+
58
+ $this->logToFile('Product Base image: ' . $product->getImage());
59
+ $this->logToFile('Product Small image: ' . $product->getSmallImage());
60
+ $this->logToFile('Product Thumbnail: ' . $product->getThumbnail());
61
+
62
+ if ($product->getImage() == '' ||
63
+ $product->getImage() == self::$IMAGE_NO_SELECTION) {
64
+ $this->logToFile('WARNING: product ' . $product->getId()
65
+ . ' does not have a base image set');
66
+ return null;
67
+ }
68
+
69
+ $productBaseImagePath = $this->getImagePath($product, self::$IMAGE_TYPE_BASE_IMAGE);
70
+
71
+ if (!is_file($productBaseImagePath)) {
72
+ $this->logToFile('WARNING: parent product ' . $product->getId()
73
+ . ' base image not readable ' . $productBaseImagePath);
74
+ return null;
75
+ }
76
+
77
+ return $productBaseImagePath;
78
+ }
79
+
80
+ /**
81
+ * Sets required image types specified by $requiredChildProductImageTypes
82
+ * child product of parent product based on parent product's base image
83
+ * specified by $productBaseImagePath.
84
+ *
85
+ * @param Mage_Catalog_Model_Product $parentProduct
86
+ * @param Mage_Catalog_Model_Product $childProduct
87
+ * @param array $requiredChildProductImageTypes
88
+ */
89
+ private function setChildProductRequiredImageTypesFromParent(
90
+ Mage_Catalog_Model_Product $parentProduct, Mage_Catalog_Model_Product $childProduct, $requiredChildProductImageTypes) {
91
+
92
+ $this->logToFile('---------------------------------------');
93
+ $this->logToFile('Checking '
94
+ . mb_strtoupper($childProduct->getTypeId())
95
+ . ' product ' . $childProduct->getId());
96
+
97
+ $parentProductBaseImagePath = $this->getProductBaseImagePath($parentProduct);
98
+
99
+ if (!$parentProductBaseImagePath) {
100
+ $this->logToFile('WARNING: Failed to determine parent product '
101
+ . $parentProduct->getId() . ' base image - QUITTING!');
102
+ return;
103
+ }
104
+
105
+ $this->logToFile('Using parent product ' . $parentProduct->getId()
106
+ . ' base image:');
107
+ $this->logToFile($parentProductBaseImagePath);
108
+
109
+ $this->logToFile('Child Base image: ' . $childProduct->getImage());
110
+ $this->logToFile('Child Small image: ' . $childProduct->getSmallImage());
111
+ $this->logToFile('Child Thumbnail: ' . $childProduct->getThumbnail());
112
+
113
+ $this->logToFile('Required image type(s) '
114
+ . 'for child product ' . $childProduct->getId()
115
+ . ': [' . implode(',', $requiredChildProductImageTypes) . ']');
116
+
117
+ foreach ($requiredChildProductImageTypes as $k => $imageType) {
118
+ $image = null;
119
+
120
+ if ($imageType === self::$IMAGE_TYPE_BASE_IMAGE) {
121
+ $image = $childProduct->getImage();
122
+ } else if ($imageType === self::$IMAGE_TYPE_SMALL_IMAGE) {
123
+ $image = $childProduct->getSmallImage();
124
+ } else if ($imageType === self::$IMAGE_TYPE_THUMBNAIL) {
125
+ $image = $childProduct->getThumbnail();
126
+ } else {
127
+ $this->logToFile('Invalid image type specified: '
128
+ . $imageType);
129
+ continue;
130
+ }
131
+
132
+ if ($image == '' || $image == self::$IMAGE_NO_SELECTION) {
133
+ $this->logToFile(mb_strtoupper($imageType)
134
+ . ' not set - using parent base image '
135
+ . $parentProduct->getImage()
136
+ . ' as ' . $imageType . ' for child product '
137
+ . $childProduct->getId());
138
+ } else {
139
+ $this->logToFile(mb_strtoupper($imageType)
140
+ . ' already set for child product '
141
+ . $childProduct->getId());
142
+ unset($requiredChildProductImageTypes[$k]);
143
+ }
144
+ }
145
+
146
+ if (!count($requiredChildProductImageTypes)) {
147
+ $this->logToFile('No required image(s) to be set for child product '
148
+ . $childProduct->getId());
149
+ } else {
150
+ $this->logToFile('Setting required image(s) ['
151
+ . implode(',', $requiredChildProductImageTypes)
152
+ . '] for child product ' . $childProduct->getId());
153
+
154
+ /**
155
+ * Set required image types for child product based on base
156
+ * image from parent set through $requiredChildProductImageTypes.
157
+ *
158
+ * Will automatically create a copy of image file(s) in media
159
+ * folder.
160
+ */
161
+ $childProduct->addImageToMediaGallery($parentProductBaseImagePath, $requiredChildProductImageTypes, false, false);
162
+ $childProduct->save();
163
+
164
+ $this->logToFile('Successfully set required image type(s) ['
165
+ . implode(',', $requiredChildProductImageTypes)
166
+ . '] for child product ' . $childProduct->getId());
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Returns absolute path to base image set on $product. If no image of type
172
+ * $imageType is currently set will return NULL.
173
+ * @param Mage_Catalog_Model_Product $product
174
+ * @param string $imageType image type, i.e. image (base image),
175
+ * small_image, thumbnail
176
+ * @return string|NULL
177
+ */
178
+ private function getImagePath(Mage_Catalog_Model_Product $product, $imageType) {
179
+
180
+ if ($product->getImage() == '' ||
181
+ $product->getImage() == self::$IMAGE_NO_SELECTION) {
182
+ return null;
183
+ }
184
+
185
+ $image = null;
186
+
187
+ if ($imageType === self::$IMAGE_TYPE_BASE_IMAGE) {
188
+ $image = $product->getImage();
189
+ } else if ($imageType === self::$IMAGE_TYPE_SMALL_IMAGE) {
190
+ $image = $product->getSmallImage();
191
+ } else if ($imageType === self::$IMAGE_TYPE_THUMBNAIL) {
192
+ $image = $product->getThumbnail();
193
+ } else {
194
+ $this->logToFile('Invalid image type specified: ' . $imageType);
195
+ return null;
196
+ }
197
+
198
+ $imageUrl = Mage::getModel('catalog/product_media_config')
199
+ ->getMediaUrl($image);
200
+ $baseDir = Mage::getBaseDir();
201
+ $withoutIndex = str_replace('index.php/', '', Mage::getBaseUrl());
202
+ $imageWithoutBase = str_replace($withoutIndex, '', $imageUrl);
203
+ return ($baseDir . DIRECTORY_SEPARATOR . $imageWithoutBase);
204
+ }
205
+
206
+ /**
207
+ * Handles simple products.
208
+ * @param Mage_Catalog_Model_Product $product
209
+ * @var array $requiredChildProductImageTypes
210
+ */
211
+ private function handleSimpleProduct(Mage_Catalog_Model_Product $product, $requiredChildProductImageTypes) {
212
+ $this->logToFile('Handling '
213
+ . mb_strtoupper($product->getTypeId())
214
+ . ' product ' . $product->getId());
215
+
216
+ $this->logToFile('Checking if '
217
+ . Mage_Catalog_Model_Product_Type::TYPE_GROUPED
218
+ . ' product...');
219
+
220
+ $parentIds = Mage::getModel('catalog/product_type_grouped')
221
+ ->getParentIdsByChild($product->getId());
222
+
223
+ if (!$parentIds) {
224
+ $this->logToFile('Checking if '
225
+ . Mage_Catalog_Model_Product_Type::TYPE_CONFIGURABLE
226
+ . ' product...');
227
+
228
+ $parentIds = Mage::getModel('catalog/product_type_configurable')
229
+ ->getParentIdsByChild($product->getId());
230
+ }
231
+
232
+ $this->logToFile('Possible parent product(s): '
233
+ . count($parentIds));
234
+
235
+ if (!count($parentIds)) {
236
+ $this->logToFile('No parent product(s) found for product '
237
+ . $product->getId());
238
+ }
239
+
240
+ /**
241
+ * avoid recursion here if for instance base image was just removed
242
+ * from child product. Thus, do not call
243
+ * setChildProductRequiredImageTypesFromParent() here!
244
+ */
245
+ }
246
+
247
+ /**
248
+ * Handles configurable prod
249
+ * @param Mage_Catalog_Model_Product $product
250
+ * @param array $requiredChildProductImageTypes
251
+ */
252
+ private function handleConfigurableProduct(Mage_Catalog_Model_Product $product, $requiredChildProductImageTypes) {
253
+
254
+ $this->logToFile('Handling '
255
+ . mb_strtoupper($product->getTypeId())
256
+ . ' product ' . $product->getId());
257
+
258
+ $childProducts = Mage::getModel('catalog/product_type_configurable')
259
+ ->getUsedProducts(null, $product);
260
+
261
+ if (!count($childProducts)) {
262
+ $this->logToFile('No associated (child) product(s) to be processed for product '
263
+ . $product->getId() . ' - DONE!');
264
+ return;
265
+ }
266
+
267
+ $this->logToFile('Found ' . count($childProducts)
268
+ . ' associated (child) product(s)');
269
+
270
+ foreach ($childProducts as $childProduct) {
271
+ $this->setChildProductRequiredImageTypesFromParent(
272
+ $product, $childProduct, $requiredChildProductImageTypes);
273
+ }
274
+
275
+ $this->logToFile('Done handling associated products for '
276
+ . mb_strtoupper($product->getTypeId())
277
+ . ' product ' . $product->getId() . '!');
278
+ }
279
+
280
+ /**
281
+ * Handles product save event calls by checking product type and settings
282
+ * required image types.
283
+ * @param Varien_Event_Observer $observer
284
+ */
285
+ public function catalog_product_save_after(Varien_Event_Observer $observer) {
286
+ try {
287
+ /**
288
+ * Can be of any product type, e.g. configurable, grouped, simple,
289
+ * @var $order Mage_Catalog_Model_Product
290
+ */
291
+ $product = $observer->getEvent()->getProduct();
292
+
293
+ $this->logToFile('==================================================');
294
+ $this->logToFile('Checking product ' . $product->getId()
295
+ . ' of type ' . mb_strtoupper($product->getTypeId())
296
+ . '...');
297
+
298
+ /**
299
+ * @var array required image types to be set for each child product
300
+ * based on parent's base image.
301
+ * Possible values are
302
+ * - IMAGE_TYPE_BASE_IMAGE,
303
+ * - IMAGE_TYPE_SMALL_IMAGE
304
+ * - IMAGE_TYPE_THUMBNAIL.
305
+ *
306
+ * Make sure that this list always includes at least
307
+ * IMAGE_TYPE_BASE_IMAGE for e.g. Amazon Listing to work since it
308
+ * requires a base image.
309
+ *
310
+ * TODO: load these settings from admin backend
311
+ */
312
+ $requiredChildProductImageTypes = array(self::$IMAGE_TYPE_BASE_IMAGE);
313
+
314
+ if ($product->getTypeId() === Mage_Catalog_Model_Product_Type::TYPE_SIMPLE) {
315
+ $this->handleSimpleProduct($product, $requiredChildProductImageTypes);
316
+ } else if ($product->getTypeId() === Mage_Catalog_Model_Product_Type::TYPE_CONFIGURABLE) {
317
+ $this->handleConfigurableProduct($product, $requiredChildProductImageTypes);
318
+ } else {
319
+ $this->logToFile('Ignoring '
320
+ . mb_strtoupper($product->getTypeId())
321
+ . ' product');
322
+ }
323
+
324
+ $this->logToFile('Done processing ' . $product->getId() . '!');
325
+ } catch (Exception $e) {
326
+ $this->logToFile('ERROR: ' . $e->getMessage());
327
+ }
328
+ }
329
+
330
+ }
app/code/community/BothInteract/ConfigurableProductVariantsImageAssignment/etc/config.xml ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+
3
+ <config>
4
+ <modules>
5
+ <BothInteract_ConfigurableProductVariantsImageAssignment>
6
+ <version>1.0.0</version>
7
+ </BothInteract_ConfigurableProductVariantsImageAssignment>
8
+ </modules>
9
+ <global>
10
+ <!-- inform Magento of our new model class-->
11
+ <models>
12
+ <bothinteract_configurableproductvariantsimageassignment>
13
+ <class>BothInteract_ConfigurableProductVariantsImageAssignment_Model</class>
14
+ </bothinteract_configurableproductvariantsimageassignment>
15
+ </models>
16
+ <!-- attach our observer class to catalog_product_save_after event-->
17
+ <events>
18
+ <catalog_product_save_after>
19
+ <observers>
20
+ <bothinteract_configurableproductvariantsimageassignment_model_observer>
21
+ <type>singleton</type>
22
+ <class>BothInteract_ConfigurableProductVariantsImageAssignment_Model_Observer</class>
23
+ <method>catalog_product_save_after</method>
24
+ </bothinteract_configurableproductvariantsimageassignment_model_observer>
25
+ </observers>
26
+ </catalog_product_save_after>
27
+ </events>
28
+ </global>
29
+ </config>
package.xml ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <package>
3
+ <name>BothInteract_ConfigurableProductVariantsImageAssignment</name>
4
+ <version>1.0.0</version>
5
+ <stability>stable</stability>
6
+ <license uri="http://www.opensource.org/licenses/mit-license.php">MIT License (MITL)</license>
7
+ <channel>community</channel>
8
+ <extends/>
9
+ <summary>Automatically copies custom list of required image types from configurable products to variants on save.</summary>
10
+ <description>This extension automatically copies a custom list of required image types (i.e. base image, small image, thumbail) from parent configurable products to variants (i.e. associated child simple products) once parent is saved.&#xD;
11
+ &#xD;
12
+ Logs messages to custom log file configurableproductvariantsimageassignment.log in your log directory. This way you can easily track events handled by this extension and check for possible problems.</description>
13
+ <notes>Initial public release.</notes>
14
+ <authors><author><name>Matthias Kerstner</name><user>mkbothinteract</user><email>matthias@both-interact.com</email></author></authors>
15
+ <date>2015-01-21</date>
16
+ <time>11:32:49</time>
17
+ <contents><target name="magecommunity"><dir name="BothInteract"><dir name="ConfigurableProductVariantsImageAssignment"><dir name="Model"><file name="Observer.php" hash="738936e385908a5b730e7f366f388393"/></dir><dir name="etc"><file name="config.xml" hash="401d1569864aedd91fdd70a7d4be6148"/></dir></dir></dir></target><target name="mageetc"><dir name="modules"><dir name="BothInteract_BothInteract"><dir name="ConfigurableProductVariantsImageAssignment"><file name="BothInteract_ConfigurableProductVariantsImageAssignment.xml" hash=""/></dir></dir></dir></target></contents>
18
+ <compatible/>
19
+ <dependencies><required><php><min>5.3.0</min><max>5.6.3</max></php></required></dependencies>
20
+ </package>