Completed
Push — 13.x ( 6ef6a1 )
by Tim
06:12
created

ProductMediaObserver   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 308
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 26
lcom 1
cbo 1
dl 0
loc 308
ccs 0
cts 129
cp 0
rs 10
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A process() 0 23 2
A getImageValue() 0 13 4
B processImages() 0 57 6
B processAdditionalImages() 0 51 6
A loadImagesToHide() 0 11 2
A getImageTypes() 0 4 1
A getDefaultImageLabel() 0 4 1
A getImageMapping() 0 4 1
A getInversedImageMapping() 0 4 1
A newArtefact() 0 4 1
A addArtefacts() 0 4 1
1
<?php
2
3
/**
4
 * TechDivision\Import\Product\Media\Observers\ProductMediaObserver
5
 *
6
 * NOTICE OF LICENSE
7
 *
8
 * This source file is subject to the Open Software License (OSL 3.0)
9
 * that is available through the world-wide-web at this URL:
10
 * http://opensource.org/licenses/osl-3.0.php
11
 *
12
 * PHP version 5
13
 *
14
 * @author    Tim Wagner <[email protected]>
15
 * @copyright 2016 TechDivision GmbH <[email protected]>
16
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
17
 * @link      https://github.com/techdivision/import-product-media
18
 * @link      http://www.techdivision.com
19
 */
20
21
namespace TechDivision\Import\Product\Media\Observers;
22
23
use TechDivision\Import\Product\Media\Utils\ColumnKeys;
24
use TechDivision\Import\Product\Observers\AbstractProductImportObserver;
25
26
/**
27
 * Observer that extracts theproduct's media data from a CSV file to be added to media specifi CSV file.
28
 *
29
 * @author    Tim Wagner <[email protected]>
30
 * @copyright 2016 TechDivision GmbH <[email protected]>
31
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
32
 * @link      https://github.com/techdivision/import-product-media
33
 * @link      http://www.techdivision.com
34
 */
35
class ProductMediaObserver extends AbstractProductImportObserver
36
{
37
38
    /**
39
     * The artefact type.
40
     *
41
     * @var string
42
     */
43
    const ARTEFACT_TYPE = 'media';
44
45
    /**
46
     * The the default image label.
47
     *
48
     * @var string
49
     */
50
    const DEFAULT_IMAGE_LABEL = 'Image';
51
52
    /**
53
     * The array with the image information of on row before they'll be converted into artefacts.
54
     *
55
     * @var array
56
     */
57
    protected $images = array();
58
59
    /**
60
     * The image artefacts that has to be exported.
61
     *
62
     * @var array
63
     */
64
    protected $artefacts = array();
65
66
    /**
67
     * The array with names of the images that should be hidden on the product detail page.
68
     *
69
     * @var array
70
     */
71
    protected $imagesToHide = array();
72
73
    /**
74
     * Holds the image values of the main row.
75
     *
76
     * @var array
77
     */
78
    protected $mainRow = array();
79
80
    /**
81
     * Process the observer's business logic.
82
     *
83
     * @return array The processed row
84
     */
85
    protected function process()
86
    {
87
88
        // reset the values of the parent row, if the SKU changes
89
        if ($this->isLastSku($this->getValue(ColumnKeys::SKU)) === false) {
90
            $this->mainRow = array();
91
        }
92
93
        // initialize the array for the artefacts and the hidden images
94
        $this->images = array();
95
        $this->artefacts = array();
96
        $this->imagesToHide = array();
97
98
        // load the images that has to be hidden on product detail page
99
        $this->loadImagesToHide();
100
101
        // process the images/additional images
102
        $this->processImages();
103
        $this->processAdditionalImages();
104
105
        // append the artefacts that has to be exported to the subject
106
        $this->addArtefacts($this->artefacts);
107
    }
108
109
    /**
110
     * Resolve's the value with the passed colum name from the actual row. If a callback will
111
     * be passed, the callback will be invoked with the found value as parameter. If
112
     * the value is NULL or empty, the default value will be returned.
113
     *
114
     * @param string        $name     The name of the column to return the value for
115
     * @param mixed|null    $default  The default value, that has to be returned, if the row's value is empty
116
     * @param callable|null $callback The callback that has to be invoked on the value, e. g. to format it
117
     *
118
     * @return mixed|null The, almost formatted, value
119
     * @see \TechDivision\Import\Observers\AbstractObserver::getValue()
120
     */
121
    protected function getImageValue($name, $default = null, callable $callback = null)
122
    {
123
124
        // query whether or not the a image value is available, return it if yes
125
        if ($this->hasValue($name) && $this->isLastSku($this->getValue(ColumnKeys::SKU)) === false) {
126
            return $this->mainRow[$name] = $this->getValue($name, $default, $callback);
127
        }
128
129
        // try to load it from the parent rows
130
        if (isset($this->mainRow[$name])) {
131
            return $this->mainRow[$name];
132
        }
133
    }
134
135
    /**
136
     * Parses the column and exports the image data to a separate file.
137
     *
138
     * @return void
139
     */
140
    protected function processImages()
141
    {
142
143
        // load the store view code
144
        $storeViewCode = $this->getValue(ColumnKeys::STORE_VIEW_CODE);
145
        $attributeSetCode = $this->getValue(ColumnKeys::ATTRIBUTE_SET_CODE);
146
147
        // load the parent SKU from the row
148
        $parentSku = $this->getValue(ColumnKeys::SKU);
149
150
        // load the image types
151
        $imageTypes = $this->getImageTypes();
152
153
        // iterate over the available image fields
154
        foreach ($imageTypes as $imageColumnName => $labelColumnName) {
155
            // query whether or not the column contains an image name
156
            if ($image = $this->getImageValue($imageColumnName)) {
157
                // load the original image path and query whether or not an image with the name already exists
158
                if (isset($this->artefacts[$imagePath = $this->getInversedImageMapping($image)])) {
159
                    continue;
160
                }
161
162
                // initialize the label text
163
                $labelText = $this->getDefaultImageLabel();
164
165
                // query whether or not a custom label text has been passed
166
                if ($this->hasValue($labelColumnName)) {
167
                    $labelText = $this->getValue($labelColumnName);
168
                }
169
170
                // prepare the new base image
171
                $artefact = $this->newArtefact(
172
                    array(
173
                        ColumnKeys::STORE_VIEW_CODE        => $storeViewCode,
174
                        ColumnKeys::ATTRIBUTE_SET_CODE     => $attributeSetCode,
175
                        ColumnKeys::IMAGE_PARENT_SKU       => $parentSku,
176
                        ColumnKeys::IMAGE_PATH             => $imagePath,
177
                        ColumnKeys::IMAGE_PATH_NEW         => $image,
178
                        ColumnKeys::HIDE_FROM_PRODUCT_PAGE => in_array($image, $this->imagesToHide) ? 1 : 0,
179
                        ColumnKeys::IMAGE_LABEL            => $labelText
180
                    ),
181
                    array(
182
                        ColumnKeys::STORE_VIEW_CODE        => ColumnKeys::STORE_VIEW_CODE,
183
                        ColumnKeys::ATTRIBUTE_SET_CODE     => ColumnKeys::ATTRIBUTE_SET_CODE,
184
                        ColumnKeys::IMAGE_PARENT_SKU       => ColumnKeys::SKU,
185
                        ColumnKeys::IMAGE_PATH             => $imageColumnName,
186
                        ColumnKeys::IMAGE_PATH_NEW         => $imageColumnName,
187
                        ColumnKeys::HIDE_FROM_PRODUCT_PAGE => ColumnKeys::HIDE_FROM_PRODUCT_PAGE,
188
                        ColumnKeys::IMAGE_LABEL            => $labelColumnName
189
                    )
190
                );
191
192
                // append the base image to the artefacts
193
                $this->artefacts[$imagePath] = $artefact;
194
            }
195
        }
196
    }
197
198
    /**
199
     * Parses the column and exports the additional image data to a separate file.
200
     *
201
     * @return void
202
     */
203
    protected function processAdditionalImages()
204
    {
205
206
        // load the store view code
207
        $storeViewCode = $this->getValue(ColumnKeys::STORE_VIEW_CODE);
208
        $attributeSetCode = $this->getValue(ColumnKeys::ATTRIBUTE_SET_CODE);
209
210
        // load the parent SKU from the row
211
        $parentSku = $this->getValue(ColumnKeys::SKU);
212
213
        // query whether or not, we've additional images
214
        if ($additionalImages = $this->getImageValue(ColumnKeys::ADDITIONAL_IMAGES, null, array($this, 'explode'))) {
215
            // expand the additional image labels, if available
216
            $additionalImageLabels = $this->getValue(ColumnKeys::ADDITIONAL_IMAGE_LABELS, array(), array($this, 'explode'));
217
218
            // initialize the images with the found values
219
            foreach ($additionalImages as $key => $additionalImage) {
220
                // load the original image path and query whether or not an image with the name already exists
221
                if (isset($this->artefacts[$imagePath = $this->getInversedImageMapping($additionalImage)])) {
222
                    continue;
223
                }
224
225
                // prepare the additional image
226
                $artefact = $this->newArtefact(
227
                    array(
228
                        ColumnKeys::STORE_VIEW_CODE        => $storeViewCode,
229
                        ColumnKeys::ATTRIBUTE_SET_CODE     => $attributeSetCode,
230
                        ColumnKeys::IMAGE_PARENT_SKU       => $parentSku,
231
                        ColumnKeys::IMAGE_PATH             => $imagePath,
232
                        ColumnKeys::IMAGE_PATH_NEW         => $additionalImage,
233
                        ColumnKeys::HIDE_FROM_PRODUCT_PAGE => in_array($additionalImage, $this->imagesToHide) ? 1 : 0,
234
                        ColumnKeys::IMAGE_LABEL            => isset($additionalImageLabels[$key]) ?
235
                                                              $additionalImageLabels[$key] :
236
                                                              $this->getDefaultImageLabel()
237
                    ),
238
                    array(
239
                        ColumnKeys::STORE_VIEW_CODE        => ColumnKeys::STORE_VIEW_CODE,
240
                        ColumnKeys::ATTRIBUTE_SET_CODE     => ColumnKeys::ATTRIBUTE_SET_CODE,
241
                        ColumnKeys::IMAGE_PARENT_SKU       => ColumnKeys::SKU,
242
                        ColumnKeys::IMAGE_PATH             => ColumnKeys::ADDITIONAL_IMAGES,
243
                        ColumnKeys::IMAGE_PATH_NEW         => ColumnKeys::ADDITIONAL_IMAGES,
244
                        ColumnKeys::HIDE_FROM_PRODUCT_PAGE => ColumnKeys::HIDE_FROM_PRODUCT_PAGE,
245
                        ColumnKeys::IMAGE_LABEL            => ColumnKeys::ADDITIONAL_IMAGE_LABELS
246
                    )
247
                );
248
249
                // append the additional image to the artefacts
250
                $this->artefacts[$imagePath] = $artefact;
251
            }
252
        }
253
    }
254
255
    /**
256
     * Load the images that has to be hidden on the product detail page.
257
     *
258
     * @return void
259
     */
260
    protected function loadImagesToHide()
261
    {
262
263
        // load the array with the images that has to be hidden
264
        $hideFromProductPage = $this->getValue(ColumnKeys::HIDE_FROM_PRODUCT_PAGE, array(), array($this, 'explode'));
265
266
        // map the image names, because probably they have been renamed by the upload functionlity
267
        foreach ($hideFromProductPage as $filename) {
268
            $this->imagesToHide[] = $this->getImageMapping($filename);
269
        }
270
    }
271
272
    /**
273
     * Return's the array with the available image types and their label columns.
274
     *
275
     * @return array The array with the available image types
276
     */
277
    protected function getImageTypes()
278
    {
279
        return $this->getSubject()->getImageTypes();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TechDivision\Import\Subjects\SubjectInterface as the method getImageTypes() does only exist in the following implementations of said interface: TechDivision\Import\Prod...a\Subjects\MediaSubject, TechDivision\Import\Prod...\AbstractProductSubject, TechDivision\Import\Product\Subjects\BunchSubject.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
280
    }
281
282
    /**
283
     * Return's the default image label.
284
     *
285
     * @return string|null The default image label
286
     */
287
    protected function getDefaultImageLabel()
288
    {
289
        return ProductMediaObserver::DEFAULT_IMAGE_LABEL;
290
    }
291
292
    /**
293
     * Returns the mapped filename (which is the new filename).
294
     *
295
     * @param string $filename The filename to map
296
     *
297
     * @return string The mapped filename
298
     */
299
    protected function getImageMapping($filename)
300
    {
301
        return $this->getSubject()->getImageMapping($filename);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TechDivision\Import\Subjects\SubjectInterface as the method getImageMapping() does only exist in the following implementations of said interface: TechDivision\Import\Obse...s\FileUploadSubjectImpl, TechDivision\Import\Prod...a\Subjects\MediaSubject, TechDivision\Import\Product\Subjects\BunchSubject.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
302
    }
303
304
    /**
305
     * Returns the original filename for passed one (which is the new filename).
306
     *
307
     * @param string $newFilename The new filename to return the original one for
308
     *
309
     * @return string The original filename
310
     */
311
    protected function getInversedImageMapping($newFilename)
312
    {
313
        return $this->getSubject()->getInversedImageMapping($newFilename);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TechDivision\Import\Subjects\SubjectInterface as the method getInversedImageMapping() does only exist in the following implementations of said interface: TechDivision\Import\Obse...s\FileUploadSubjectImpl, TechDivision\Import\Prod...a\Subjects\MediaSubject, TechDivision\Import\Product\Subjects\BunchSubject.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
314
    }
315
316
    /**
317
     * Create's and return's a new empty artefact entity.
318
     *
319
     * @param array $columns             The array with the column data
320
     * @param array $originalColumnNames The array with a mapping from the old to the new column names
321
     *
322
     * @return array The new artefact entity
323
     */
324
    protected function newArtefact(array $columns, array $originalColumnNames)
325
    {
326
        return $this->getSubject()->newArtefact($columns, $originalColumnNames);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TechDivision\Import\Subjects\SubjectInterface as the method newArtefact() does only exist in the following implementations of said interface: TechDivision\Import\Plugins\ExportableSubjectImpl, TechDivision\Import\Product\Subjects\BunchSubject, TechDivision\Import\Subjects\ExportableTraitImpl.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
327
    }
328
329
    /**
330
     * Add the passed product type artefacts to the product with the
331
     * last entity ID.
332
     *
333
     * @param array $artefacts The product type artefacts
334
     *
335
     * @return void
336
     * @uses \TechDivision\Import\Product\Media\Subjects\MediaSubject::getLastEntityId()
337
     */
338
    protected function addArtefacts(array $artefacts)
339
    {
340
        $this->getSubject()->addArtefacts(ProductMediaObserver::ARTEFACT_TYPE, $artefacts, false);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TechDivision\Import\Subjects\SubjectInterface as the method addArtefacts() does only exist in the following implementations of said interface: TechDivision\Import\Plugins\ExportableSubjectImpl, TechDivision\Import\Product\Subjects\BunchSubject, TechDivision\Import\Subjects\ExportableTraitImpl.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
341
    }
342
}
343