Passed
Push — master ( e8c17c...92b8a3 )
by Bjørn
02:37
created

AbstractConverter::getDestination()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
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
    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 string  The destination.
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
203 20
        return new static($source, $destination, $options, $logger);
204
    }
205
206 3
    protected function logReduction($source, $destination)
207
    {
208 3
        $sourceSize = filesize($source);
209 3
        $destSize = filesize($destination);
210 3
        $this->log(round(($sourceSize - $destSize)/$sourceSize * 100) . '% ');
211 3
        if ($sourceSize < 10000) {
212 1
            $this->logLn('(went from ' . strval($sourceSize) . ' bytes to '. strval($destSize) . ' bytes)');
213
        } else {
214 3
            $this->logLn('(went from ' . round($sourceSize/1024) . ' kb to ' . round($destSize/1024) . ' kb)');
215
        }
216 3
    }
217
218
    /**
219
     * Run conversion.
220
     *
221
     * @return void
222
     */
223 12
    private function doConvertImplementation()
224
    {
225 12
        $beginTime = microtime(true);
226
227 12
        $this->activateWarningLogger();
228
229 12
        $this->checkOptions();
230
231
        // Prepare destination folder
232 12
        $this->createWritableDestinationFolder();
233 11
        $this->removeExistingDestinationIfExists();
234
235 11
        if (!isset($this->options['_skip_input_check'])) {
236
            // Check that a file can be written to destination
237 11
            $this->checkDestinationWritable();
238
        }
239
240 11
        $this->checkOperationality();
241 6
        $this->checkConvertability();
242
243 6
        if ($this->options['log-call-arguments']) {
244
            $this->logOptions();
245
            $this->logLn('');
246
        }
247
248 6
        $this->runActualConvert();
249
250 3
        $source = $this->source;
251 3
        $destination = $this->destination;
252
253 3
        if (!@file_exists($destination)) {
254
            throw new ConversionFailedException('Destination file is not there: ' . $destination);
255 3
        } elseif (@filesize($destination) === 0) {
256
            unlink($destination);
257
            throw new ConversionFailedException('Destination file was completely empty');
258
        } else {
259 3
            if (!isset($this->options['_suppress_success_message'])) {
260 3
                $this->ln();
261 3
                $this->log('Converted image in ' . round((microtime(true) - $beginTime) * 1000) . ' ms');
262
263 3
                $sourceSize = @filesize($source);
264 3
                if ($sourceSize !== false) {
265 3
                    $this->log(', reducing file size with ');
266 3
                    $this->logReduction($source, $destination);
267
                }
268
            }
269
        }
270
271 3
        $this->deactivateWarningLogger();
272 3
    }
273
274
    //private function logEx
275
    /**
276
     * Start conversion.
277
     *
278
     * Usually you would rather call the static convert method, but alternatively you can call
279
     * call ::createInstance to get an instance and then ::doConvert().
280
     *
281
     * @return void
282
     */
283 12
    public function doConvert()
284
    {
285
        try {
286
            //trigger_error('hello', E_USER_ERROR);
287 12
            $this->doConvertImplementation();
288 9
        } catch (WebPConvertException $e) {
289 9
            $this->logLn('');
290
            /*
291
            if (isset($e->description) && ($e->description != '')) {
292
                $this->log('Error: ' . $e->description . '. ', 'bold');
293
            } else {
294
                $this->log('Error: ', 'bold');
295
            }
296
            */
297 9
            $this->log('Error: ', 'bold');
298 9
            $this->logLn($e->getMessage(), 'bold');
299 9
            throw $e;
300
        } catch (\Exception $e) {
301
            $className = get_class($e);
302
303
            $classNameParts = explode("\\", $className);
304
            $shortClassName = array_pop($classNameParts);
305
306
            $this->logLn('');
307
            $this->logLn($shortClassName . ' thrown in ' . $e->getFile() . ':' . $e->getLine(), 'bold');
308
            $this->logLn('Message: "' . $e->getMessage() . '"', 'bold');
309
            //$this->logLn('Exception class: ' . $className);
310
311
            $this->logLn('Trace:');
312
            foreach ($e->getTrace() as $trace) {
313
                //$this->logLn(print_r($trace, true));
314
                if (isset($trace['file']) && isset($trace['line'])) {
315
                    $this->logLn(
316
                        $trace['file'] . ':' . $trace['line']
317
                    );
318
                }
319
            }
320
            throw $e;
321
        } /*catch (\Error $e) {
322
            $this->logLn('ERROR');
323
        }*/
324 3
    }
325
326
    /**
327
     * Runs the actual conversion (after setup and checks)
328
     * Simply calls the doActualConvert() of the actual converter.
329
     * However, in the EncodingAutoTrait, this method is overridden to make two conversions
330
     * and select the smallest.
331
     *
332
     * @return void
333
     */
334 4
    protected function runActualConvert()
335
    {
336 4
        $this->doActualConvert();
337 3
    }
338
339
    /**
340
     * Convert an image to webp.
341
     *
342
     * @param   string  $source              path to source file
343
     * @param   string  $destination         path to destination
344
     * @param   array   $options (optional)  options for conversion
345
     * @param   BaseLogger $logger (optional)
346
     *
347
     * @throws  ConversionFailedException   in case conversion fails in an antipiciated way
348
     * @throws  \Exception   in case conversion fails in an unantipiciated way
349
     * @return  void
350
     */
351 13
    public static function convert($source, $destination, $options = [], $logger = null)
352
    {
353 13
        $c = self::createInstance($source, $destination, $options, $logger);
354 12
        $c->doConvert();
355
        //echo $instance->id;
356 3
    }
357
358
    /**
359
     * Get mime type for image (best guess).
360
     *
361
     * It falls back to using file extension. If that fails too, false is returned
362
     *
363
     * PS: Is it a security risk to fall back on file extension?
364
     * - By setting file extension to "jpg", one can lure our library into trying to convert a file, which isn't a jpg.
365
     * hmm, seems very unlikely, though not unthinkable that one of the converters could be exploited
366
     *
367
     * @return  string|false|null mimetype (if it is an image, and type could be determined / guessed),
368
     *    false (if it is not an image type that the server knowns about)
369
     *    or null (if nothing can be determined)
370
     */
371 30
    public function getMimeTypeOfSource()
372
    {
373 30
        return MimeType::getMimeTypeDetectionResult($this->source);
374
    }
375
}
376