Completed
Push — master ( 70a6e8...9554c6 )
by Bjørn
04:06
created

AbstractConverter::getConverterDisplayName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
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 4
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\Convert\Exceptions\ConversionFailedException;
9
use WebPConvert\Convert\Converters\BaseTraits\AutoQualityTrait;
10
use WebPConvert\Convert\Converters\BaseTraits\DestinationPreparationTrait;
11
use WebPConvert\Convert\Converters\BaseTraits\LoggerTrait;
12
use WebPConvert\Convert\Converters\BaseTraits\OptionsTrait;
13
use WebPConvert\Convert\Converters\BaseTraits\SourceValidationTrait;
14
use WebPConvert\Convert\Converters\BaseTraits\WarningLoggerTrait;
15
use WebPConvert\Loggers\BaseLogger;
16
17
use ImageMimeTypeGuesser\ImageMimeTypeGuesser;
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 LoggerTrait;
30
    use OptionsTrait;
31
    use WarningLoggerTrait;
32
    use DestinationPreparationTrait;
33
    use SourceValidationTrait;
34
35
    /**
36
     * The actual conversion is be done by a concrete converter extending this class.
37
     *
38
     * At the stage this method is called, the abstract converter has taken preparational steps.
39
     * - It has created the destination folder (if neccesary)
40
     * - It has checked the input (valid mime type)
41
     * - It has set up an error handler, mostly in order to catch and log warnings during the doConvert fase
42
     *
43
     * Note: This method is not meant to be called from the outside. Use the static *convert* method for converting
44
     *       or, if you wish, create an instance with ::createInstance() and then call ::doConvert()
45
     *
46
     * @throws ConversionFailedException in case conversion failed in an antipiciated way (or subclass)
47
     * @throws \Exception in case conversion failed in an unantipiciated way
48
     */
49
    abstract protected function doActualConvert();
50
51
    /**
52
     * Whether or not the converter supports lossless encoding (even for jpegs)
53
     *
54
     * PS: Converters that supports lossless encoding all use the EncodingAutoTrait, which
55
     * overrides this function.
56
     *
57
     * @return  boolean  Whether the converter supports lossless encoding (even for jpegs).
58
     */
59
    public function supportsLossless()
60
    {
61
        return false;
62
    }
63
64
    /** @var string  The filename of the image to convert (complete path) */
65
    protected $source;
66
67
    /** @var string  Where to save the webp (complete path) */
68
    protected $destination;
69
70
    /** @var string|false|null  Where to save the webp (complete path) */
71
    private $sourceMimeType;
72
73
    /**
74
     * Check basis operationality
75
     *
76
     * Converters may override this method for the purpose of performing basic operationaly checks. It is for
77
     * running general operation checks for a conversion method.
78
     * If some requirement is not met, it should throw a ConverterNotOperationalException (or subtype)
79
     *
80
     * The method is called internally right before calling doActualConvert() method.
81
     * - It SHOULD take options into account when relevant. For example, a missing api key for a
82
     *   cloud converter should be detected here
83
     * - It should NOT take the actual filename into consideration, as the purpose is *general*
84
     *   For that pupose, converters should override checkConvertability
85
     *   Also note that doConvert method is allowed to throw ConverterNotOperationalException too.
86
     *
87
     * @return  void
88
     */
89 2
    public function checkOperationality()
90
    {
91 2
    }
92
93
    /**
94
     * Converters may override this for the purpose of performing checks on the concrete file.
95
     *
96
     * This can for example be used for rejecting big uploads in cloud converters or rejecting unsupported
97
     * image types.
98
     *
99
     * @return  void
100
     */
101 5
    public function checkConvertability()
102
    {
103 5
    }
104
105
    /**
106
     * Constructor.
107
     *
108
     * @param   string  $source              path to source file
109
     * @param   string  $destination         path to destination
110
     * @param   array   $options (optional)  options for conversion
111
     * @param   BaseLogger $logger (optional)
112
     */
113 31
    public function __construct($source, $destination, $options = [], $logger = null)
114
    {
115 31
        $this->source = $source;
116 31
        $this->destination = $destination;
117
118 31
        $this->setLogger($logger);
119 31
        $this->setProvidedOptions($options);
120
121 31
        $this->checkSourceExists();
122 30
        $this->checkSourceMimeType();
123 30
    }
124
125
    /**
126
     * Get source.
127
     *
128
     * @return string  The source.
129
     */
130 31
    public function getSource()
131
    {
132 31
        return $this->source;
133
    }
134
135
    /**
136
     * Get destination.
137
     *
138
     * @return string  The destination.
139
     */
140 12
    public function getDestination()
141
    {
142 12
        return $this->destination;
143
    }
144
145
    /**
146
     * Set destination.
147
     *
148
     * @param   string  $destination         path to destination
149
     * @return string  The destination.
150
     */
151 1
    public function setDestination($destination)
152
    {
153 1
        $this->destination = $destination;
154 1
    }
155
156
157
    /**
158
     *  Get converter name for display (defaults to the class name (short)).
159
     *
160
     *  Converters can override this.
161
     *
162
     * @return string  A display name, ie "Gd"
163
     */
164 31
    protected static function getConverterDisplayName()
165
    {
166
        // https://stackoverflow.com/questions/19901850/how-do-i-get-an-objects-unqualified-short-class-name/25308464
167 31
        return substr(strrchr('\\' . static::class, '\\'), 1);
168
    }
169
170
171
    /**
172
     *  Get converter id (defaults to the class name lowercased)
173
     *
174
     *  Converters can override this.
175
     *
176
     * @return string  A display name, ie "Gd"
177
     */
178 31
    protected static function getConverterId()
179
    {
180 31
        return strtolower(self::getConverterDisplayName());
181
    }
182
183
184
    /**
185
     * Create an instance of this class
186
     *
187
     * @param  string  $source       The path to the file to convert
188
     * @param  string  $destination  The path to save the converted file to
189
     * @param  array   $options      (optional)
190
     * @param  \WebPConvert\Loggers\BaseLogger   $logger       (optional)
191
     *
192
     * @return static
193
     */
194 20
    public static function createInstance($source, $destination, $options = [], $logger = null)
195
    {
196 20
        return new static($source, $destination, $options, $logger);
197
    }
198
199
200
    /**
201
     * Start conversion.
202
     *
203
     * Usually you would rather call the static convert method, but alternatively you can call
204
     * call ::createInstance to get an instance and then ::doConvert().
205
     *
206
     * @return void
207
     */
208 12
    public function doConvert()
209
    {
210 12
        $beginTime = microtime(true);
211
212 12
        $this->activateWarningLogger();
213
214 12
        $this->checkOptions();
215
216
        // Prepare destination folder
217 12
        $this->createWritableDestinationFolder();
218 11
        $this->removeExistingDestinationIfExists();
219
220 11
        if (!isset($this->options['_skip_input_check'])) {
221
            // Run basic input validations (if source exists and if file extension is valid)
222 11
            $this->checkSourceExists();
223 11
            $this->checkSourceMimeType();
224
225
            // Check that a file can be written to destination
226 11
            $this->checkDestinationWritable();
227
        }
228
229 11
        $this->checkOperationality();
230 6
        $this->checkConvertability();
231 6
        $this->runActualConvert();
232
233 3
        $source = $this->source;
234 3
        $destination = $this->destination;
235
236 3
        if (!@file_exists($destination)) {
237
            throw new ConversionFailedException('Destination file is not there: ' . $destination);
238 3
        } elseif (@filesize($destination) === 0) {
239
            unlink($destination);
240
            throw new ConversionFailedException('Destination file was completely empty');
241
        } else {
242 3
            if (!isset($this->options['_suppress_success_message'])) {
243 3
                $this->ln();
244
                $msg = 'Converted image in ' .
245 3
                    round((microtime(true) - $beginTime) * 1000) . ' ms';
246
247 3
                $sourceSize = @filesize($source);
248 3
                if ($sourceSize !== false) {
249
                    $msg .= ', reducing file size with ' .
250 3
                        round((filesize($source) - filesize($destination))/filesize($source) * 100) . '% ';
251
252 3
                    if ($sourceSize < 10000) {
253 1
                        $msg .= '(went from ' . round(filesize($source)) . ' bytes to ';
254 1
                        $msg .= round(filesize($destination)) . ' bytes)';
255
                    } else {
256 3
                        $msg .= '(went from ' . round(filesize($source)/1024) . ' kb to ';
257 3
                        $msg .= round(filesize($destination)/1024) . ' kb)';
258
                    }
259
                }
260 3
                $this->logLn($msg);
261
            }
262
        }
263
264 3
        $this->deactivateWarningLogger();
265 3
    }
266
267
    /**
268
     * Runs the actual conversion (after setup and checks)
269
     * Simply calls the doActualConvert() of the actual converter.
270
     * However, in the EncodingAutoTrait, this method is overridden to make two conversions
271
     * and select the smallest.
272
     *
273
     * @return void
274
     */
275 4
    protected function runActualConvert()
276
    {
277 4
        $this->doActualConvert();
278 3
    }
279
280
    /**
281
     * Convert an image to webp.
282
     *
283
     * @param   string  $source              path to source file
284
     * @param   string  $destination         path to destination
285
     * @param   array   $options (optional)  options for conversion
286
     * @param   BaseLogger $logger (optional)
287
     *
288
     * @throws  ConversionFailedException   in case conversion fails in an antipiciated way
289
     * @throws  \Exception   in case conversion fails in an unantipiciated way
290
     * @return  void
291
     */
292 13
    public static function convert($source, $destination, $options = [], $logger = null)
293
    {
294 13
        $c = self::createInstance($source, $destination, $options, $logger);
295
296 12
        $c->logLn(self::getConverterDisplayName() . ' converter ignited');
297 12
        $c->logLn('source: ' . $source, 'italic');
298 12
        $c->logLn('destination: ' . $destination, 'italic');
299
300
301 12
        $c->doConvert();
302
        //echo $instance->id;
303 3
    }
304
305
    /**
306
     * Get mime type for image (best guess).
307
     *
308
     * It falls back to using file extension. If that fails too, false is returned
309
     *
310
     * PS: Is it a security risk to fall back on file extension?
311
     * - By setting file extension to "jpg", one can lure our library into trying to convert a file, which isn't a jpg.
312
     * hmm, seems very unlikely, though not unthinkable that one of the converters could be exploited
313
     *
314
     * @return  string|false|null mimetype (if it is an image, and type could be determined / guessed),
315
     *    false (if it is not an image type that the server knowns about)
316
     *    or null (if nothing can be determined)
317
     */
318 31
    public function getMimeTypeOfSource()
319
    {
320 31
        if (!isset($this->sourceMimeType)) {
321 31
            $this->sourceMimeType = ImageMimeTypeGuesser::lenientGuess($this->source);
322
        }
323 31
        return $this->sourceMimeType;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->sourceMimeType also could return the type boolean which is incompatible with the documented return type false|null|string.
Loading history...
324
    }
325
}
326