Passed
Pull Request — master (#251)
by Dev
03:02
created

Stack   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 215
Duplicated Lines 0 %

Test Coverage

Coverage 59.34%

Importance

Changes 16
Bugs 2 Features 0
Metric Value
eloc 104
c 16
b 2
f 0
dl 0
loc 215
ccs 54
cts 91
cp 0.5934
rs 10
wmc 26

6 Methods

Rating   Name   Duplication   Size   Complexity  
A createOptions() 0 10 1
A checkOperationality() 0 5 2
A getConverterUsed() 0 3 1
A getUnsupportedDefaultOptions() 0 17 1
A getAvailableConverters() 0 4 1
F doActualConvert() 0 141 20
1
<?php
2
3
namespace WebPConvert\Convert\Converters;
4
5
use WebPConvert\Convert\ConverterFactory;
6
use WebPConvert\Convert\Converters\AbstractConverter;
7
use WebPConvert\Convert\Exceptions\ConversionFailedException;
8
use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperationalException;
9
use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperational\SystemRequirementsNotMetException;
10
use WebPConvert\Convert\Exceptions\ConversionFailed\ConversionSkippedException;
11
use WebPConvert\Options\BooleanOption;
12
use WebPConvert\Options\ArrayOption;
13
use WebPConvert\Options\GhostOption;
14
use WebPConvert\Options\SensitiveArrayOption;
15
16
//use WebPConvert\Convert\Exceptions\ConversionFailed\InvalidInput\TargetNotFoundException;
17
18
/**
19
 * Convert images to webp by trying a stack of converters until success.
20
 *
21
 * @package    WebPConvert
22
 * @author     Bjørn Rosell <[email protected]>
23
 * @since      Class available since Release 2.0.0
24
 */
25
class Stack extends AbstractConverter
26
{
27
28
    protected $converterUsed;
29
30
    protected function getUnsupportedDefaultOptions()
31
    {
32
        return [
33
            'alpha-quality',
34
            'auto-filter',
35
            'encoding',
36
            'low-memory',
37
            'metadata',
38
            'method',
39
            'near-lossless',
40
            'preset',
41
            'size-in-percentage',
42
            'use-nice',
43
            'skip',
44
            'default-quality',
45
            'quality',
46
            'max-quality',
47
        ];
48
    }
49
50 4
    protected function createOptions()
51
    {
52 4
        parent::createOptions();
53
54 4
        $this->options2->addOptions(
55 4
            new SensitiveArrayOption('converters', self::getAvailableConverters()),
56 4
            new SensitiveArrayOption('converter-options', []),
57 4
            new BooleanOption('shuffle', false),
58 4
            new ArrayOption('preferred-converters', []),
59 4
            new SensitiveArrayOption('extra-converters', [])
60
        );
61 4
    }
62
63
    /**
64
     * Get available converters (ids) - ordered by awesomeness.
65
     *
66
     * @return  array  An array of ids of converters that comes with this library
67
     */
68 4
    public static function getAvailableConverters()
69
    {
70
        return [
71 4
            'cwebp', 'vips', 'imagick', 'gmagick', 'imagemagick', 'graphicsmagick', 'wpc', 'ewww', 'gd'
72
        ];
73
    }
74
75
    /**
76
     * Check (general) operationality of imagack converter executable
77
     *
78
     * @throws SystemRequirementsNotMetException  if system requirements are not met
79
     */
80 3
    public function checkOperationality()
81
    {
82 3
        if (count($this->options['converters']) == 0) {
83 1
            throw new ConverterNotOperationalException(
84 1
                'Converter stack is empty! - no converters to try, no conversion can be made!'
85
            );
86
        }
87
88
        // TODO: We should test if all converters are found in order to detect problems early
89
90
        //$this->logLn('Stack converter ignited');
91 2
    }
92
93 2
    protected function doActualConvert()
94
    {
95 2
        $options = $this->options;
96
97 2
        $beginTimeStack = microtime(true);
98
99 2
        $anyRuntimeErrors = false;
100
101 2
        $converters = $options['converters'];
102 2
        if (count($options['extra-converters']) > 0) {
103
            $converters = array_merge($converters, $options['extra-converters']);
104
            /*foreach ($options['extra-converters'] as $extra) {
105
                $converters[] = $extra;
106
            }*/
107
        }
108
109
        // preferred-converters
110 2
        if (count($options['preferred-converters']) > 0) {
111
            foreach (array_reverse($options['preferred-converters']) as $prioritizedConverter) {
112
                foreach ($converters as $i => $converter) {
113
                    if (is_array($converter)) {
114
                        $converterId = $converter['converter'];
115
                    } else {
116
                        $converterId = $converter;
117
                    }
118
                    if ($converterId == $prioritizedConverter) {
119
                        unset($converters[$i]);
120
                        array_unshift($converters, $converter);
121
                        break;
122
                    }
123
                }
124
            }
125
            // perhaps write the order to the log? (without options) - but this requires some effort
126
        }
127
128
        // shuffle
129 2
        if ($options['shuffle']) {
130
            shuffle($converters);
131
        }
132
133
        //$this->logLn(print_r($converters));
134
        //$options['converters'] = $converters;
135
        //$defaultConverterOptions = $options;
136 2
        $defaultConverterOptions = [];
137
138 2
        foreach ($this->options2->getOptionsMap() as $id => $option) {
139 2
            if ($option->isValueExplicitlySet() && !($option instanceof GhostOption)) {
140
                //$this->logLn('hi' . $id);
141 2
                $defaultConverterOptions[$id] = $option->getValue();
142
            }
143
        }
144
145
        //unset($defaultConverterOptions['converters']);
146
        //unset($defaultConverterOptions['converter-options']);
147 2
        $defaultConverterOptions['_skip_input_check'] = true;
148 2
        $defaultConverterOptions['_suppress_success_message'] = true;
149 2
        unset($defaultConverterOptions['converters']);
150 2
        unset($defaultConverterOptions['extra-converters']);
151 2
        unset($defaultConverterOptions['converter-options']);
152 2
        unset($defaultConverterOptions['preferred-converters']);
153 2
        unset($defaultConverterOptions['shuffle']);
154
155
//        $this->logLn('converters: ' . print_r($converters, true));
156
157
        //return;
158 2
        foreach ($converters as $converter) {
159 2
            if (is_array($converter)) {
160
                $converterId = $converter['converter'];
161
                $converterOptions = isset($converter['options']) ? $converter['options'] : [];
162
            } else {
163 2
                $converterId = $converter;
164 2
                $converterOptions = [];
165 2
                if (isset($options['converter-options'][$converterId])) {
166
                    // Note: right now, converter-options are not meant to be used,
167
                    //       when you have several converters of the same type
168
                    $converterOptions = $options['converter-options'][$converterId];
169
                }
170
            }
171 2
            $converterOptions = array_merge($defaultConverterOptions, $converterOptions);
172
            /*
173
            if ($converterId != 'stack') {
174
                //unset($converterOptions['converters']);
175
                //unset($converterOptions['converter-options']);
176
            } else {
177
                //$converterOptions['converter-options'] =
178
                $this->logLn('STACK');
179
                $this->logLn('converterOptions: ' . print_r($converterOptions, true));
180
            }*/
181
182 2
            $beginTime = microtime(true);
183
184 2
            $this->ln();
185 2
            $this->logLn('Trying: ' . $converterId, 'italic');
186
187 2
            $converter = ConverterFactory::makeConverter(
188 2
                $converterId,
189 2
                $this->source,
190 2
                $this->destination,
191 2
                $converterOptions,
192 2
                $this->logger
193
            );
194
195
            try {
196 1
                $converter->doConvert();
197
198
                //self::runConverterWithTiming($converterId, $source, $destination, $converterOptions, false, $logger);
199
200 1
                $this->logLn($converterId . ' succeeded :)');
201 1
                $this->converterUsed = $converterId;
202
                //throw new ConverterNotOperationalException('...');
203 1
                return;
204
            } catch (ConverterNotOperationalException $e) {
205
                $this->logLn($e->getMessage());
206
            } catch (ConversionSkippedException $e) {
207
                $this->logLn($e->getMessage());
208
            } catch (ConversionFailedException $e) {
209
                $this->logLn($e->getMessage(), 'italic');
210
                $prev = $e->getPrevious();
211
                if (!is_null($prev)) {
212
                    $this->logLn($prev->getMessage(), 'italic');
213
                    $this->logLn(' in ' . $prev->getFile() . ', line ' . $prev->getLine(), 'italic');
214
                    $this->ln();
215
                }
216
                //$this->logLn($e->getTraceAsString());
217
                $anyRuntimeErrors = true;
218
            }
219
            $this->logLn($converterId . ' failed in ' . round((microtime(true) - $beginTime) * 1000) . ' ms');
220
        }
221
222
        $this->ln();
223
        $this->logLn('Stack failed in ' . round((microtime(true) - $beginTimeStack) * 1000) . ' ms');
224
225
        // Hm, Scrutinizer complains that $anyRuntimeErrors is always false. But that is not true!
226
        if ($anyRuntimeErrors) {
0 ignored issues
show
introduced by
The condition $anyRuntimeErrors is always false.
Loading history...
227
            // At least one converter failed
228
            throw new ConversionFailedException(
229
                'None of the converters in the stack could convert the image.'
230
            );
231
        } else {
232
            // All converters threw a SystemRequirementsNotMetException
233
            throw new ConverterNotOperationalException('None of the converters in the stack are operational');
234
        }
235
    }
236
237
    public function getConverterUsed()
238
    {
239
        return $this->converterUsed;
240
    }
241
}
242