Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.00% covered (success)
95.00%
38 / 40
50.00% covered (danger)
50.00%
1 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
ServeFile
95.00% covered (success)
95.00%
38 / 40
50.00% covered (danger)
50.00%
1 / 2
13
0.00% covered (danger)
0.00%
0 / 1
 processOptions
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
3
 serve
90.00% covered (success)
90.00%
18 / 20
0.00% covered (danger)
0.00%
0 / 1
10.10
1<?php
2namespace WebPConvert\Serve;
3
4//use WebPConvert\Serve\Report;
5use WebPConvert\Helpers\InputValidator;
6use WebPConvert\Options\ArrayOption;
7use WebPConvert\Options\BooleanOption;
8use WebPConvert\Options\Options;
9use WebPConvert\Options\StringOption;
10use WebPConvert\Serve\Header;
11use WebPConvert\Serve\Exceptions\ServeFailedException;
12
13/**
14 * Serve a file (send to standard output)
15 *
16 * @package    WebPConvert
17 * @author     Bjørn Rosell <it@rosell.dk>
18 * @since      Class available since Release 2.0.0
19 */
20class ServeFile
21{
22
23    /**
24     * Process options.
25     *
26     * @throws \WebPConvert\Options\Exceptions\InvalidOptionTypeException   If the type of an option is invalid
27     * @throws \WebPConvert\Options\Exceptions\InvalidOptionValueException  If the value of an option is invalid
28     * @param array $options
29     */
30    private static function processOptions($options)
31    {
32        $options2 = new Options();
33        $options2->addOptions(
34            new ArrayOption('headers', []),
35            new StringOption('cache-control-header', 'public, max-age=31536000')
36        );
37        foreach ($options as $optionId => $optionValue) {
38            $options2->setOrCreateOption($optionId, $optionValue);
39        }
40        $options2->check();
41        $options = $options2->getOptions();
42
43        // headers option
44        // --------------
45
46        $headerOptions = new Options();
47        $headerOptions->addOptions(
48            new BooleanOption('cache-control', false),
49            new BooleanOption('content-length', true),
50            new BooleanOption('content-type', true),
51            new BooleanOption('expires', false),
52            new BooleanOption('last-modified', true),
53            new BooleanOption('vary-accept', false)
54        );
55        foreach ($options['headers'] as $optionId => $optionValue) {
56            $headerOptions->setOrCreateOption($optionId, $optionValue);
57        }
58        $options['headers'] = $headerOptions->getOptions();
59        return $options;
60    }
61
62    /**
63     * Serve existing file.
64     *
65     * @param  string  $filename     File to serve (absolute path)
66     * @param  string  $contentType  Content-type (used to set header).
67     *                                    Only used when the "set-content-type-header" option is set.
68     *                                    Set to ie "image/jpeg" for serving jpeg file.
69     * @param  array   $options      Array of named options (optional).
70     *       Supported options:
71     *       'add-vary-accept-header'  => (boolean)   Whether to add *Vary: Accept* header or not. Default: true.
72     *       'set-content-type-header' => (boolean)   Whether to set *Content-Type* header or not. Default: true.
73     *       'set-last-modified-header' => (boolean)  Whether to set *Last-Modified* header or not. Default: true.
74     *       'set-cache-control-header' => (boolean)  Whether to set *Cache-Control* header or not. Default: true.
75     *       'cache-control-header' => string         Cache control header. Default: "public, max-age=86400"
76     *
77     * @throws ServeFailedException  if serving failed
78     * @return  void
79     */
80    public static function serve($filename, $contentType, $options = [])
81    {
82        // Check mimetype - this also checks that path is secure and file exists
83        InputValidator::checkMimeType($filename, [
84            'image/jpeg',
85            'image/png',
86            'image/webp',
87            'image/gif'
88        ]);
89
90        /*
91        if (!file_exists($filename)) {
92            Header::addHeader('X-WebP-Convert-Error: Could not read file');
93            throw new ServeFailedException('Could not read file');
94        }*/
95
96        $options = self::processOptions($options);
97
98        if ($options['headers']['last-modified']) {
99            Header::setHeader("Last-Modified: " . gmdate("D, d M Y H:i:s", @filemtime($filename)) . " GMT");
100        }
101
102        if ($options['headers']['content-type']) {
103            Header::setHeader('Content-Type: ' . $contentType);
104        }
105
106        if ($options['headers']['vary-accept']) {
107            Header::addHeader('Vary: Accept');
108        }
109
110        if (!empty($options['cache-control-header'])) {
111            if ($options['headers']['cache-control']) {
112                Header::setHeader('Cache-Control: ' . $options['cache-control-header']);
113            }
114            if ($options['headers']['expires']) {
115                // Add exprires header too (#126)
116                // Check string for something like this: max-age:86400
117                if (preg_match('#max-age\\s*=\\s*(\\d*)#', $options['cache-control-header'], $matches)) {
118                    $seconds = $matches[1];
119                    Header::setHeader('Expires: ' . gmdate('D, d M Y H:i:s \G\M\T', time() + intval($seconds)));
120                }
121            }
122        }
123
124        if ($options['headers']['content-length']) {
125            Header::setHeader('Content-Length: ' . filesize($filename));
126        }
127
128        if (@readfile($filename) === false) {
129            Header::addHeader('X-WebP-Convert-Error: Could not read file');
130            throw new ServeFailedException('Could not read file');
131        }
132    }
133}