Passed
Push — master ( 47232b...207a99 )
by Bjørn
01:36 queued 12s
created

AbstractConverter::logReduction()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2.0116

Importance

Changes 0
Metric Value
cc 2
eloc 7
c 0
b 0
f 0
nc 2
nop 2
dl 0
loc 9
ccs 6
cts 7
cp 0.8571
crap 2.0116
rs 10
1
<?php
2
3
// TODO:
4
// Read this: https://sourcemaking.com/design_patterns/strategy
5
6
namespace WebPConvert\Convert\Converters;
7
8
use WebPConvert\Helpers\InputValidator;
9
use WebPConvert\Helpers\MimeType;
10
use WebPConvert\Convert\Exceptions\ConversionFailedException;
11
use WebPConvert\Convert\Converters\BaseTraits\AutoQualityTrait;
12
use WebPConvert\Convert\Converters\BaseTraits\DestinationPreparationTrait;
13
use WebPConvert\Convert\Converters\BaseTraits\LoggerTrait;
14
use WebPConvert\Convert\Converters\BaseTraits\OptionsTrait;
15
use WebPConvert\Convert\Converters\BaseTraits\WarningLoggerTrait;
16
use WebPConvert\Exceptions\WebPConvertException;
17
use WebPConvert\Loggers\BaseLogger;
18
19
/**
20
 * Base for all converter classes.
21
 *
22
 * @package    WebPConvert
23
 * @author     Bjørn Rosell <[email protected]>
24
 * @since      Class available since Release 2.0.0
25
 */
26
abstract class AbstractConverter
27
{
28
    use AutoQualityTrait;
29
    use OptionsTrait;
30
    use WarningLoggerTrait;
31
    use DestinationPreparationTrait;
32
    use LoggerTrait;
33
34
    /**
35
     * The actual conversion is be done by a concrete converter extending this class.
36
     *
37
     * At the stage this method is called, the abstract converter has taken preparational steps.
38
     * - It has created the destination folder (if neccesary)
39
     * - It has checked the input (valid mime type)
40
     * - It has set up an error handler, mostly in order to catch and log warnings during the doConvert fase
41
     *
42
     * Note: This method is not meant to be called from the outside. Use the static *convert* method for converting
43
     *       or, if you wish, create an instance with ::createInstance() and then call ::doConvert()
44
     *
45
     * @throws ConversionFailedException in case conversion failed in an antipiciated way (or subclass)
46
     * @throws \Exception in case conversion failed in an unantipiciated way
47
     */
48
    abstract protected function doActualConvert();
49
50
    /**
51
     * Whether or not the converter supports lossless encoding (even for jpegs)
52
     *
53
     * PS: Converters that supports lossless encoding all use the EncodingAutoTrait, which
54
     * overrides this function.
55
     *
56
     * @return  boolean  Whether the converter supports lossless encoding (even for jpegs).
57
     */
58
    public function supportsLossless()
59
    {
60
        return false;
61
    }
62
63
    /** @var string  The filename of the image to convert (complete path) */
64
    protected $source;
65
66
    /** @var string  Where to save the webp (complete path) */
67
    protected $destination;
68
69
    /**
70
     * Check basis operationality
71
     *
72
     * Converters may override this method for the purpose of performing basic operationaly checks. It is for
73
     * running general operation checks for a conversion method.
74
     * If some requirement is not met, it should throw a ConverterNotOperationalException (or subtype)
75
     *
76
     * The method is called internally right before calling doActualConvert() method.
77
     * - It SHOULD take options into account when relevant. For example, a missing api key for a
78
     *   cloud converter should be detected here
79
     * - It should NOT take the actual filename into consideration, as the purpose is *general*
80
     *   For that pupose, converters should override checkConvertability
81
     *   Also note that doConvert method is allowed to throw ConverterNotOperationalException too.
82
     *
83
     * @return  void
84
     */
85 2
    public function checkOperationality()
86
    {
87 2
    }
88
89
    /**
90
     * Converters may override this for the purpose of performing checks on the concrete file.
91
     *
92
     * This can for example be used for rejecting big uploads in cloud converters or rejecting unsupported
93
     * image types.
94
     *
95
     * @return  void
96
     */
97 5
    public function checkConvertability()
98
    {
99 5
    }
100
101
    /**
102
     * Constructor.
103
     *
104
     * @param   string  $source              path to source file
105
     * @param   string  $destination         path to destination
106
     * @param   array   $options (optional)  options for conversion
107
     * @param   BaseLogger $logger (optional)
108
     */
109 31
    final public function __construct($source, $destination, $options = [], $logger = null)
110
    {
111 31
        InputValidator::checkSourceAndDestination($source, $destination);
112
113 30
        $this->source = $source;
114 30
        $this->destination = $destination;
115
116 30
        $this->setLogger($logger);
117 30
        $this->setProvidedOptions($options);
118
119 30
        if (!isset($this->options['_skip_input_check'])) {
120 30
            $this->log('WebP Convert 2.3.0', 'italic');
121 30
            $this->logLn(' ignited.');
122 30
            $this->logLn('- PHP version: ' . phpversion());
123 30
            if (isset($_SERVER['SERVER_SOFTWARE'])) {
124
                $this->logLn('- Server software: ' . $_SERVER['SERVER_SOFTWARE']);
125
            }
126 30
            $this->logLn('');
127 30
            $this->logLn(self::getConverterDisplayName() . ' converter ignited');
128
        }
129 30
    }
130
131
    /**
132
     * Get source.
133
     *
134
     * @return string  The source.
135
     */
136
    public function getSource()
137
    {
138
        return $this->source;
139
    }
140
141
    /**
142
     * Get destination.
143
     *
144
     * @return string  The destination.
145
     */
146 12
    public function getDestination()
147
    {
148 12
        return $this->destination;
149
    }
150
151
    /**
152
     * Set destination.
153
     *
154
     * @param   string  $destination         path to destination
155
     * @return  void
156
     */
157 1
    public function setDestination($destination)
158
    {
159 1
        $this->destination = $destination;
160 1
    }
161
162
163
    /**
164
     *  Get converter name for display (defaults to the class name (short)).
165
     *
166
     *  Converters can override this.
167
     *
168
     * @return string  A display name, ie "Gd"
169
     */
170 30
    protected static function getConverterDisplayName()
171
    {
172
        // https://stackoverflow.com/questions/19901850/how-do-i-get-an-objects-unqualified-short-class-name/25308464
173 30
        return substr(strrchr('\\' . static::class, '\\'), 1);
174
    }
175
176
177
    /**
178
     *  Get converter id (defaults to the class name lowercased)
179
     *
180
     *  Converters can override this.
181
     *
182
     * @return string  A display name, ie "Gd"
183
     */
184 30
    protected static function getConverterId()
185
    {
186 30
        return strtolower(self::getConverterDisplayName());
187
    }
188
189
190
    /**
191
     * Create an instance of this class
192
     *
193
     * @param  string  $source       The path to the file to convert
194
     * @param  string  $destination  The path to save the converted file to
195
     * @param  array   $options      (optional)
196
     * @param  \WebPConvert\Loggers\BaseLogger   $logger       (optional)
197
     *
198
     * @return static
199
     */
200 20
    public static function createInstance($source, $destination, $options = [], $logger = null)
201
    {
202 20
        return new static($source, $destination, $options, $logger);
203
    }
204
205 2
    protected function logReduction($source, $destination)
206
    {
207 2
        $sourceSize = filesize($source);
208 2
        $destSize = filesize($destination);
209 2
        $this->log(round(($sourceSize - $destSize)/$sourceSize * 100) . '% ');
210 2
        if ($sourceSize < 10000) {
211
            $this->logLn('(went from ' . strval($sourceSize) . ' bytes to '. strval($destSize) . ' bytes)');
212
        } else {
213 2
            $this->logLn('(went from ' . round($sourceSize/1024) . ' kb to ' . round($destSize/1024) . ' kb)');
214
        }
215 2
    }
216
217
    /**
218
     * Run conversion.
219
     *
220
     * @return void
221
     */
222 12
    private function doConvertImplementation()
223
    {
224 12
        $beginTime = microtime(true);
225
226 12
        $this->activateWarningLogger();
227
228 12
        $this->checkOptions();
229
230
        // Prepare destination folder
231 12
        $this->createWritableDestinationFolder();
232 11
        $this->removeExistingDestinationIfExists();
233
234 11
        if (!isset($this->options['_skip_input_check'])) {
235
            // Check that a file can be written to destination
236 11
            $this->checkDestinationWritable();
237
        }
238
239 11
        $this->checkOperationality();
240 5
        $this->checkConvertability();
241
242 5
        if ($this->options['log-call-arguments']) {
243
            $this->logOptions();
244
            $this->logLn('');
245
        }
246
247 5
        $this->runActualConvert();
248
249 2
        $source = $this->source;
250 2
        $destination = $this->destination;
251
252 2
        if (!@file_exists($destination)) {
253
            throw new ConversionFailedException('Destination file is not there: ' . $destination);
254 2
        } elseif (@filesize($destination) === 0) {
255
            unlink($destination);
256
            throw new ConversionFailedException('Destination file was completely empty');
257
        } else {
258 2
            if (!isset($this->options['_suppress_success_message'])) {
259 2
                $this->ln();
260 2
                $this->log('Converted image in ' . round((microtime(true) - $beginTime) * 1000) . ' ms');
261
262 2
                $sourceSize = @filesize($source);
263 2
                if ($sourceSize !== false) {
264 2
                    $this->log(', reducing file size with ');
265 2
                    $this->logReduction($source, $destination);
266
                }
267
            }
268
        }
269
270 2
        $this->deactivateWarningLogger();
271 2
    }
272
273
    //private function logEx
274
    /**
275
     * Start conversion.
276
     *
277
     * Usually you would rather call the static convert method, but alternatively you can call
278
     * call ::createInstance to get an instance and then ::doConvert().
279
     *
280
     * @return void
281
     */
282 12
    public function doConvert()
283
    {
284
        try {
285
            //trigger_error('hello', E_USER_ERROR);
286 12
            $this->doConvertImplementation();
287 10
        } catch (WebPConvertException $e) {
288 10
            $this->logLn('');
289
            /*
290
            if (isset($e->description) && ($e->description != '')) {
291
                $this->log('Error: ' . $e->description . '. ', 'bold');
292
            } else {
293
                $this->log('Error: ', 'bold');
294
            }
295
            */
296 10
            $this->log('Error: ', 'bold');
297 10
            $this->logLn($e->getMessage(), 'bold');
298 10
            throw $e;
299
        } catch (\Exception $e) {
300
            $className = get_class($e);
301
302
            $classNameParts = explode("\\", $className);
303
            $shortClassName = array_pop($classNameParts);
304
305
            $this->logLn('');
306
            $this->logLn($shortClassName . ' thrown in ' . $e->getFile() . ':' . $e->getLine(), 'bold');
307
            $this->logLn('Message: "' . $e->getMessage() . '"', 'bold');
308
            //$this->logLn('Exception class: ' . $className);
309
310
            $this->logLn('Trace:');
311
            foreach ($e->getTrace() as $trace) {
312
                //$this->logLn(print_r($trace, true));
313
                if (isset($trace['file']) && isset($trace['line'])) {
314
                    $this->logLn(
315
                        $trace['file'] . ':' . $trace['line']
316
                    );
317
                }
318
            }
319
            throw $e;
320
        } /*catch (\Error $e) {
321
            $this->logLn('ERROR');
322
        }*/
323 2
    }
324
325
    /**
326
     * Runs the actual conversion (after setup and checks)
327
     * Simply calls the doActualConvert() of the actual converter.
328
     * However, in the EncodingAutoTrait, this method is overridden to make two conversions
329
     * and select the smallest.
330
     *
331
     * @return void
332
     */
333 3
    protected function runActualConvert()
334
    {
335 3
        $this->doActualConvert();
336 2
    }
337
338
    /**
339
     * Convert an image to webp.
340
     *
341
     * @param   string  $source              path to source file
342
     * @param   string  $destination         path to destination
343
     * @param   array   $options (optional)  options for conversion
344
     * @param   BaseLogger $logger (optional)
345
     *
346
     * @throws  ConversionFailedException   in case conversion fails in an antipiciated way
347
     * @throws  \Exception   in case conversion fails in an unantipiciated way
348
     * @return  void
349
     */
350 13
    public static function convert($source, $destination, $options = [], $logger = null)
351
    {
352 13
        $c = self::createInstance($source, $destination, $options, $logger);
353 12
        $c->doConvert();
354
        //echo $instance->id;
355 2
    }
356
357
    /**
358
     * Get mime type for image (best guess).
359
     *
360
     * It falls back to using file extension. If that fails too, false is returned
361
     *
362
     * PS: Is it a security risk to fall back on file extension?
363
     * - By setting file extension to "jpg", one can lure our library into trying to convert a file, which isn't a jpg.
364
     * hmm, seems very unlikely, though not unthinkable that one of the converters could be exploited
365
     *
366
     * @return  string|false|null mimetype (if it is an image, and type could be determined / guessed),
367
     *    false (if it is not an image type that the server knowns about)
368
     *    or null (if nothing can be determined)
369
     */
370 30
    public function getMimeTypeOfSource()
371
    {
372 30
        return MimeType::getMimeTypeDetectionResult($this->source);
373
    }
374
}
375