Completed
Pull Request — 21.x (#39)
by Marcus
01:10
created

ProductMediaObserver::getDefaultImagePosition()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 4
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 2
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 default image position.
54
     *
55
     * @var string
56
     */
57
    const DEFAULT_IMAGE_POSITION = 0;
58
59
    /**
60
     * The array with the image information of on row before they'll be converted into artefacts.
61
     *
62
     * @var array
63
     */
64
    protected $images = array();
65
66
    /**
67
     * The image artefacts that has to be exported.
68
     *
69
     * @var array
70
     */
71
    protected $artefacts = array();
72
73
    /**
74
     * The array with names of the images that should be hidden on the product detail page.
75
     *
76
     * @var array
77
     */
78
    protected $imagesToHide = array();
79
80
    /**
81
     * Holds the image values of the main row.
82
     *
83
     * @var array
84
     */
85
    protected $mainRow = array();
86
87
    /**
88
     * Process the observer's business logic.
89
     *
90
     * @return array The processed row
91
     */
92
    protected function process()
93
    {
94
95
        // reset the values of the parent row, if the SKU changes
96
        if ($this->isLastSku($this->getValue(ColumnKeys::SKU)) === false) {
97
            $this->mainRow = array();
98
        }
99
100
        // initialize the array for the artefacts and the hidden images
101
        $this->images = array();
102
        $this->artefacts = array();
103
        $this->imagesToHide = array();
104
105
        // load the images that has to be hidden on product detail page
106
        $this->loadImagesToHide();
107
108
        // process the images/additional images
109
        $this->processImages();
110
        $this->processAdditionalImages();
111
112
        // append the artefacts that has to be exported to the subject
113
        $this->addArtefacts($this->artefacts);
114
    }
115
116
    /**
117
     * Resolve's the value with the passed colum name from the actual row. If a callback will
118
     * be passed, the callback will be invoked with the found value as parameter. If
119
     * the value is NULL or empty, the default value will be returned.
120
     *
121
     * @param string        $name     The name of the column to return the value for
122
     * @param mixed|null    $default  The default value, that has to be returned, if the row's value is empty
123
     * @param callable|null $callback The callback that has to be invoked on the value, e. g. to format it
124
     *
125
     * @return mixed|null The, almost formatted, value
126
     * @see \TechDivision\Import\Observers\AbstractObserver::getValue()
127
     */
128
    protected function getImageValue($name, $default = null, callable $callback = null)
129
    {
130
131
        // query whether or not the a image value is available, return it if yes
132
        if ($this->hasValue($name) && $this->isLastSku($this->getValue(ColumnKeys::SKU)) === false) {
133
            return $this->mainRow[$name] = $this->getValue($name, $default, $callback);
134
        }
135
136
        // try to load it from the parent rows
137
        if (isset($this->mainRow[$name])) {
138
            return $this->mainRow[$name];
139
        }
140
    }
141
142
    /**
143
     * Parses the column and exports the image data to a separate file.
144
     *
145
     * @return void
146
     */
147
    protected function processImages()
148
    {
149
150
        // load the store view code
151
        $storeViewCode = $this->getValue(ColumnKeys::STORE_VIEW_CODE);
152
        $attributeSetCode = $this->getValue(ColumnKeys::ATTRIBUTE_SET_CODE);
153
154
        // load the parent SKU from the row
155
        $parentSku = $this->getValue(ColumnKeys::SKU);
156
157
        // load the media roles
158
        $mediaRoles = $this->getMediaRoles();
159
160
        // iterate over the available image fields
161
        foreach ($mediaRoles as $mediaAttrColumnNames) {
162
            // query whether or not the column contains an image name
163
            if ($image = $this->getImageValue($imageColumnName = $mediaAttrColumnNames[ColumnKeys::IMAGE_PATH])) {
164
                // load the original image path and query whether or not an image with the name already exists
165
                if (isset($this->artefacts[$imagePath = $this->getInversedImageMapping($image)])) {
166
                    continue;
167
                }
168
169
                // initialize the label text
170
                $labelText = $this->getDefaultImageLabel();
171
172
                // query whether or not a custom label text has been passed
173
                if ($this->hasValue($labelColumnName = $mediaAttrColumnNames[ColumnKeys::IMAGE_LABEL])) {
174
                    $labelText = $this->getValue($mediaAttrColumnNames[ColumnKeys::IMAGE_LABEL]);
175
                }
176
177
                // initialize the default image position
178
                $position = $this->getDefaultImagePosition();
179
180
                // query and retrieve optional image position
181
                if ($this->hasValue($mediaAttrColumnNames[ColumnKeys::IMAGE_POSITION])) {
182
                    $position = $this->getValue($mediaAttrColumnNames[ColumnKeys::IMAGE_POSITION]);
183
                }
184
185
                // prepare the new base image
186
                $artefact = $this->newArtefact(
187
                    array(
188
                        ColumnKeys::STORE_VIEW_CODE        => $storeViewCode,
189
                        ColumnKeys::ATTRIBUTE_SET_CODE     => $attributeSetCode,
190
                        ColumnKeys::IMAGE_PARENT_SKU       => $parentSku,
191
                        ColumnKeys::IMAGE_PATH             => $imagePath,
192
                        ColumnKeys::IMAGE_PATH_NEW         => $image,
193
                        ColumnKeys::HIDE_FROM_PRODUCT_PAGE => in_array($image, $this->imagesToHide) ? 1 : 0,
194
                        ColumnKeys::MEDIA_TYPE             => 'image',
195
                        ColumnKeys::IMAGE_LABEL            => $labelText,
196
                        ColumnKeys::IMAGE_POSITION         => $position
197
                    ),
198
                    array(
199
                        ColumnKeys::STORE_VIEW_CODE        => ColumnKeys::STORE_VIEW_CODE,
200
                        ColumnKeys::ATTRIBUTE_SET_CODE     => ColumnKeys::ATTRIBUTE_SET_CODE,
201
                        ColumnKeys::IMAGE_PARENT_SKU       => ColumnKeys::SKU,
202
                        ColumnKeys::IMAGE_PATH             => $imageColumnName,
203
                        ColumnKeys::IMAGE_PATH_NEW         => $imageColumnName,
204
                        ColumnKeys::HIDE_FROM_PRODUCT_PAGE => ColumnKeys::HIDE_FROM_PRODUCT_PAGE,
205
                        ColumnKeys::MEDIA_TYPE             => null,
206
                        ColumnKeys::IMAGE_LABEL            => $labelColumnName,
207
                        ColumnKeys::IMAGE_POSITION         => $position
208
                    )
209
                );
210
211
                // append the base image to the artefacts
212
                $this->artefacts[$imagePath] = $artefact;
213
            }
214
        }
215
    }
216
217
    /**
218
     * Parses the column and exports the additional image data to a separate file.
219
     *
220
     * @return void
221
     */
222
    protected function processAdditionalImages()
223
    {
224
225
        // load the store view code
226
        $storeViewCode = $this->getValue(ColumnKeys::STORE_VIEW_CODE);
227
        $attributeSetCode = $this->getValue(ColumnKeys::ATTRIBUTE_SET_CODE);
228
229
        // load the parent SKU from the row
230
        $parentSku = $this->getValue(ColumnKeys::SKU);
231
232
        // query whether or not, we've additional images
233
        if ($additionalImages = $this->getImageValue(ColumnKeys::ADDITIONAL_IMAGES, null, array($this, 'explode'))) {
234
            // expand the additional image labels, if available
235
            $additionalImageLabels = $this->getValue(ColumnKeys::ADDITIONAL_IMAGE_LABELS, array(), array($this, 'explode'));
236
            // retrieve the additional image positions
237
            $additionalImagePositions = $this->getValue(ColumnKeys::ADDITIONAL_IMAGE_POSITIONS, array(), array($this, 'explode'));
238
239
            // initialize the images with the found values
240
            foreach ($additionalImages as $key => $additionalImage) {
241
                // load the original image path and query whether or not an image with the name already exists
242
                if (isset($this->artefacts[$imagePath = $this->getInversedImageMapping($additionalImage)])) {
243
                    continue;
244
                }
245
246
                // prepare the additional image
247
                $artefact = $this->newArtefact(
248
                    array(
249
                        ColumnKeys::STORE_VIEW_CODE        => $storeViewCode,
250
                        ColumnKeys::ATTRIBUTE_SET_CODE     => $attributeSetCode,
251
                        ColumnKeys::IMAGE_PARENT_SKU       => $parentSku,
252
                        ColumnKeys::IMAGE_PATH             => $imagePath,
253
                        ColumnKeys::IMAGE_PATH_NEW         => $additionalImage,
254
                        ColumnKeys::HIDE_FROM_PRODUCT_PAGE => in_array($additionalImage, $this->imagesToHide) ? 1 : 0,
255
                        ColumnKeys::MEDIA_TYPE             => 'image',
256
                        ColumnKeys::IMAGE_LABEL            => isset($additionalImageLabels[$key]) ?
257
                                                              $additionalImageLabels[$key] :
258
                                                              $this->getDefaultImageLabel(),
259
                        ColumnKeys::IMAGE_POSITION         => isset($additionalImagePositions[$key]) ?
260
                                                              $additionalImagePositions[$key] :
261
                                                              $this->getDefaultImagePosition()
262
                    ),
263
                    array(
264
                        ColumnKeys::STORE_VIEW_CODE        => ColumnKeys::STORE_VIEW_CODE,
265
                        ColumnKeys::ATTRIBUTE_SET_CODE     => ColumnKeys::ATTRIBUTE_SET_CODE,
266
                        ColumnKeys::IMAGE_PARENT_SKU       => ColumnKeys::SKU,
267
                        ColumnKeys::IMAGE_PATH             => ColumnKeys::ADDITIONAL_IMAGES,
268
                        ColumnKeys::IMAGE_PATH_NEW         => ColumnKeys::ADDITIONAL_IMAGES,
269
                        ColumnKeys::HIDE_FROM_PRODUCT_PAGE => ColumnKeys::HIDE_FROM_PRODUCT_PAGE,
270
                        ColumnKeys::MEDIA_TYPE             => null,
271
                        ColumnKeys::IMAGE_LABEL            => ColumnKeys::ADDITIONAL_IMAGE_LABELS,
272
                        ColumnKeys::IMAGE_POSITION         => ColumnKeys::ADDITIONAL_IMAGE_POSITIONS
273
                    )
274
                );
275
276
                // append the additional image to the artefacts
277
                $this->artefacts[$imagePath] = $artefact;
278
            }
279
        }
280
    }
281
282
    /**
283
     * Load the images that has to be hidden on the product detail page.
284
     *
285
     * @return void
286
     */
287
    protected function loadImagesToHide()
288
    {
289
290
        // load the array with the images that has to be hidden
291
        $hideFromProductPage = $this->getValue(ColumnKeys::HIDE_FROM_PRODUCT_PAGE, array(), array($this, 'explode'));
292
293
        // map the image names, because probably they have been renamed by the upload functionality
294
        foreach ($hideFromProductPage as $filename) {
295
            $this->imagesToHide[] = $this->getImageMapping($filename);
296
        }
297
    }
298
299
    /**
300
     * Return's the array with the available image types and their label columns.
301
     *
302
     * @return array The array with the available image types
303
     */
304
    protected function getImageTypes()
305
    {
306
        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...
307
    }
308
309
    /**
310
     * Return's the default image label.
311
     *
312
     * @return string|null The default image label
313
     */
314
    protected function getDefaultImageLabel()
315
    {
316
        return ProductMediaObserver::DEFAULT_IMAGE_LABEL;
317
    }
318
319
    /**
320
     * Returns the default image position.
321
     *
322
     * @return int|string
323
     */
324
    protected function getDefaultImagePosition()
325
    {
326
        return ProductMediaObserver::DEFAULT_IMAGE_POSITION;
327
    }
328
329
    /**
330
     * Returns the mapped filename (which is the new filename).
331
     *
332
     * @param string $filename The filename to map
333
     *
334
     * @return string The mapped filename
335
     */
336
    protected function getImageMapping($filename)
337
    {
338
        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...
339
    }
340
341
    /**
342
     * Returns the original filename for passed one (which is the new filename).
343
     *
344
     * @param string $newFilename The new filename to return the original one for
345
     *
346
     * @return string The original filename
347
     */
348
    protected function getInversedImageMapping($newFilename)
349
    {
350
        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...
351
    }
352
353
    /**
354
     * Returns the media roles of the subject.
355
     *
356
     * @return mixed
357
     */
358
    protected function getMediaRoles()
359
    {
360
        return $this->getSubject()->getMediaRoles();
0 ignored issues
show
Bug introduced by
The method getMediaRoles() does not seem to exist on object<TechDivision\Impo...jects\SubjectInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
361
    }
362
363
    /**
364
     * Create's and return's a new empty artefact entity.
365
     *
366
     * @param array $columns             The array with the column data
367
     * @param array $originalColumnNames The array with a mapping from the old to the new column names
368
     *
369
     * @return array The new artefact entity
370
     */
371
    protected function newArtefact(array $columns, array $originalColumnNames)
372
    {
373
        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...
374
    }
375
376
    /**
377
     * Add the passed product type artefacts to the product with the
378
     * last entity ID.
379
     *
380
     * @param array $artefacts The product type artefacts
381
     *
382
     * @return void
383
     * @uses \TechDivision\Import\Product\Media\Subjects\MediaSubject::getLastEntityId()
384
     */
385
    protected function addArtefacts(array $artefacts)
386
    {
387
        $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...
388
    }
389
}
390