Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.24% covered (success)
95.24%
60 / 63
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
ServeConvertedWebP
95.24% covered (success)
95.24%
60 / 63
75.00% covered (warning)
75.00%
3 / 4
20
0.00% covered (danger)
0.00%
0 / 1
 processOptions
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
2
 serveOriginal
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 serveDestination
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 warningHandler
n/a
0 / 0
n/a
0 / 0
1
 serve
92.68% covered (success)
92.68%
38 / 41
0.00% covered (danger)
0.00%
0 / 1
13.07
1<?php
2namespace WebPConvert\Serve;
3
4use WebPConvert\Convert\Exceptions\ConversionFailedException;
5use WebPConvert\Helpers\InputValidator;
6use WebPConvert\Helpers\MimeType;
7use WebPConvert\Helpers\PathChecker;
8use WebPConvert\Serve\Exceptions\ServeFailedException;
9use WebPConvert\Serve\Header;
10use WebPConvert\Serve\Report;
11use WebPConvert\Serve\ServeFile;
12use WebPConvert\Options\ArrayOption;
13use WebPConvert\Options\BooleanOption;
14use WebPConvert\Options\Options;
15use WebPConvert\Options\SensitiveArrayOption;
16use WebPConvert\Options\Exceptions\InvalidOptionTypeException;
17use WebPConvert\Options\Exceptions\InvalidOptionValueException;
18use WebPConvert\WebPConvert;
19
20/**
21 * Serve a converted webp image.
22 *
23 * The webp that is served might end up being one of these:
24 * - a fresh convertion
25 * - the destionation
26 * - the original
27 *
28 * Exactly which is a decision based upon options, file sizes and file modification dates
29 * (see the serve method of this class for details)
30 *
31 * @package    WebPConvert
32 * @author     Bjørn Rosell <it@rosell.dk>
33 * @since      Class available since Release 2.0.0
34 */
35class ServeConvertedWebP
36{
37
38    /**
39     * Process options.
40     *
41     * @throws \WebPConvert\Options\Exceptions\InvalidOptionTypeException   If the type of an option is invalid
42     * @throws \WebPConvert\Options\Exceptions\InvalidOptionValueException  If the value of an option is invalid
43     * @param array $options
44     */
45    private static function processOptions($options)
46    {
47        $options2 = new Options();
48        $options2->addOptions(
49            new BooleanOption('reconvert', false),
50            new BooleanOption('serve-original', false),
51            new BooleanOption('show-report', false),
52            new BooleanOption('suppress-warnings', true),
53            new BooleanOption('redirect-to-self-instead-of-serving', false),
54            new ArrayOption('serve-image', []),
55            new SensitiveArrayOption('convert', [])
56        );
57        foreach ($options as $optionId => $optionValue) {
58            $options2->setOrCreateOption($optionId, $optionValue);
59        }
60        $options2->check();
61        return $options2->getOptions();
62    }
63
64    /**
65     * Serve original file (source).
66     *
67     * @param   string  $source                        path to source file
68     * @param   array   $serveImageOptions (optional)  options for serving an image
69     *                  Supported options:
70     *                  - All options supported by ServeFile::serve()
71     * @throws  ServeFailedException  if source is not an image or mime type cannot be determined
72     * @return  void
73     */
74    public static function serveOriginal($source, $serveImageOptions = [])
75    {
76        // PS: We do not use InputValidator::checkSource($source) because we want to be
77        // a bit more lenient here and allow any image to be served (even though ie webp does not
78        // qualify for being used as a source when converting)
79
80        // Check that the filename is ok (no control chars, streamwrappers), and that the file exists
81        // and is not a dir
82        PathChecker::checkSourcePath($source);
83
84        $contentType = MimeType::getMimeTypeDetectionResult($source);
85        if (is_null($contentType)) {
86            throw new ServeFailedException('Rejecting to serve original (mime type cannot be determined)');
87        } elseif ($contentType === false) {
88            throw new ServeFailedException('Rejecting to serve original (it is not an image)');
89        } else {
90            ServeFile::serve($source, $contentType, $serveImageOptions);
91        }
92    }
93
94    /**
95     * Serve destination file.
96     *
97     * TODO: SHould this really be public?
98     *
99     * @param   string  $destination                   path to destination file
100     * @param   array   $serveImageOptions (optional)  options for serving (such as which headers to add)
101     *       Supported options:
102     *       - All options supported by ServeFile::serve()
103     * @return  void
104     */
105    public static function serveDestination($destination, $serveImageOptions = [])
106    {
107        InputValidator::checkDestination($destination);
108        ServeFile::serve($destination, 'image/webp', $serveImageOptions);
109    }
110
111
112    public static function warningHandler()
113    {
114        // do nothing! - as we do not return anything, the warning is suppressed
115    }
116
117    /**
118     * Serve converted webp.
119     *
120     * Serve a converted webp. If a file already exists at the destination, that is served (unless it is
121     * older than the source - in that case a fresh conversion will be made, or the file at the destination
122     * is larger than the source - in that case the source is served). Some options may alter this logic.
123     * In case no file exists at the destination, a fresh conversion is made and served.
124     *
125     * @param   string  $source              path to source file
126     * @param   string  $destination         path to destination
127     * @param   array   $options (optional)  options for serving/converting
128     *       Supported options:
129     *       'show-report'     => (boolean)   If true, the decision will always be 'report'
130     *       'serve-original'  => (boolean)   If true, the decision will be 'source' (unless above option is set)
131     *       'reconvert     '  => (boolean)   If true, the decision will be 'fresh-conversion' (unless one of the
132     *                                        above options is set)
133     *       - All options supported by WebPConvert::convert()
134     *       - All options supported by ServeFile::serve()
135     * @param  \WebPConvert\Loggers\BaseLogger $serveLogger (optional)
136     * @param  \WebPConvert\Loggers\BaseLogger $convertLogger (optional)
137     *
138     * @throws  \WebPConvert\Exceptions\WebPConvertException  If something went wrong.
139     * @return  void
140     */
141    public static function serve($source, $destination, $options = [], $serveLogger = null, $convertLogger = null)
142    {
143        InputValidator::checkSourceAndDestination($source, $destination);
144
145        $options = self::processOptions($options);
146
147        if ($options['suppress-warnings']) {
148            set_error_handler(
149                array('\\WebPConvert\\Serve\\ServeConvertedWebP', "warningHandler"),
150                E_WARNING | E_USER_WARNING | E_NOTICE | E_USER_NOTICE
151            );
152        }
153
154
155        //$options = array_merge(self::$defaultOptions, $options);
156
157        // Step 1: Is there a file at the destination? If not, trigger conversion
158        // However 1: if "show-report" option is set, serve the report instead
159        // However 2: "reconvert" option should also trigger conversion
160        if ($options['show-report']) {
161            Header::addLogHeader('Showing report', $serveLogger);
162            Report::convertAndReport($source, $destination, $options);
163            return;
164        }
165
166        if (!@file_exists($destination)) {
167            Header::addLogHeader('Converting (there were no file at destination)', $serveLogger);
168            WebPConvert::convert($source, $destination, $options['convert'], $convertLogger);
169        } elseif ($options['reconvert']) {
170            Header::addLogHeader('Converting (told to reconvert)', $serveLogger);
171            WebPConvert::convert($source, $destination, $options['convert'], $convertLogger);
172        } else {
173            // Step 2: Is the destination older than the source?
174            //         If yes, trigger conversion (deleting destination is implicit)
175            $timestampSource = @filemtime($source);
176            $timestampDestination = @filemtime($destination);
177            if (($timestampSource !== false) &&
178                ($timestampDestination !== false) &&
179                ($timestampSource > $timestampDestination)) {
180                    Header::addLogHeader('Converting (destination was older than the source)', $serveLogger);
181                    WebPConvert::convert($source, $destination, $options['convert'], $convertLogger);
182            }
183        }
184
185        // Step 3: Serve the smallest file (destination or source)
186        // However, first check if 'serve-original' is set
187        if ($options['serve-original']) {
188            Header::addLogHeader('Serving original (told to)', $serveLogger);
189            self::serveOriginal($source, $options['serve-image']);
190            return;
191        }
192
193        if ($options['redirect-to-self-instead-of-serving']) {
194            Header::addLogHeader(
195                'Redirecting to self! ' .
196                    '(hope you got redirection to existing webps set up, otherwise you will get a loop!)',
197                $serveLogger
198            );
199            header('Location: ?fresh', 302);
200            return;
201        }
202
203        $filesizeDestination = @filesize($destination);
204        $filesizeSource = @filesize($source);
205        if (($filesizeSource !== false) &&
206            ($filesizeDestination !== false) &&
207            ($filesizeDestination > $filesizeSource)) {
208                Header::addLogHeader('Serving original (it is smaller)', $serveLogger);
209                self::serveOriginal($source, $options['serve-image']);
210                return;
211        }
212
213        Header::addLogHeader('Serving converted file', $serveLogger);
214        self::serveDestination($destination, $options['serve-image']);
215    }
216}