061230cf5f0b533e244dec0f3e1a061f61ae7e1d
[phancap.git] / src / phancap / Options.php
1 <?php
2 /**
3  * Part of phancap
4  *
5  * PHP version 5
6  *
7  * @category  Tools
8  * @package   Options
9  * @author    Christian Weiske <cweiske@cweiske.de>
10  * @copyright 2014 Christian Weiske
11  * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL v3
12  * @link      http://cweiske.de/phancap.htm
13  */
14 namespace phancap;
15
16 /**
17  * Options a user can give to the API
18  *
19  * @category  Tools
20  * @package   Options
21  * @author    Christian Weiske <cweiske@cweiske.de>
22  * @copyright 2014 Christian Weiske
23  * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL v3
24  * @version   Release: @package_version@
25  * @link      http://cweiske.de/phancap.htm
26  */
27 class Options
28 {
29     /**
30      * Available options and their configuration
31      *
32      * @var array
33      */
34     public $options = array(
35         /**
36          * Browser settings
37          */
38         'url' => array(
39             'title'     => 'Website URL',
40             'default'   => null,
41             'type'      => 'url',
42             'required'  => true,
43         ),
44         'bwidth' => array(
45             'title'   => 'Browser width',
46             'default' => 1024,
47             'type'    => 'int',
48             'min'     => 16,
49             'max'     => 2048,
50         ),
51         'bheight' => array(
52             'title'   => 'Browser height',
53             'default' => null,
54             'type'    => 'int',
55             'min'     => 16,
56             'max'     => 8192,
57         ),
58         /**
59          * Screenshot settings
60          */
61         'swidth' => array(
62             'title'   => 'Screenshot width',
63             'default' => null,
64             'type'    => 'int',
65             'min'     => 16,
66             'max'     => 8192,
67         ),
68         'sheight' => array(
69             'title'   => 'Screenshot height',
70             'default' => null,
71             'type'    => 'int',
72             'min'     => 16,
73             'max'     => 8192,
74         ),
75         'sformat' => array(
76             'title'   => 'Screenshot format',
77             'default' => 'png',
78             'type'    => array('png', 'jpg', 'pdf'),
79         ),
80         'smode' => array(
81             'title'   => 'Screenshot mode',
82             'default' => 'screen',
83             'type'    => array('screen', 'page'),
84         ),
85         'smaxage' => array(
86             'title'   => 'Maximum age for a screenshot',
87             'default' => null,
88             'type'    => 'age',
89             'min'     => null,
90         ),
91         /**
92          * Authentication
93          */
94         'atimestamp' => array(
95             'title'   => 'Timestamp the request has been generated',
96             'default' => null,
97             'type'    => 'skip',
98         ),
99         'atoken' => array(
100             'title'   => 'Access token (user name)',
101             'default' => null,
102             'type'    => 'skip',
103         ),
104         'asignature' => array(
105             'title'   => 'Access signature',
106             'default' => null,
107             'type'    => 'skip',
108         ),
109     );
110
111     /**
112      * Actual values we use after parsing the GET parameters
113      *
114      * @var array
115      */
116     public $values = array();
117
118     /**
119      * @var Config
120      */
121     protected $config;
122
123
124     /**
125      * Parses an array of options, validates them and writes them into
126      * $this->values.
127      *
128      * @param array $arValues Array of options, e.g. $_GET
129      *
130      * @return void
131      * @throws \InvalidArgumentException When required parameters are missing
132      *         or parameter values are invalid.
133      */
134     public function parse($arValues)
135     {
136         foreach ($this->options as $name => $arOption) {
137             $this->values[$name] = $arOption['default'];
138             if (!isset($arValues[$name])) {
139                 if (isset($arOption['required'])) {
140                     throw new \InvalidArgumentException(
141                         $name . ' parameter missing'
142                     );
143                 }
144                 continue;
145             }
146
147             if ($arOption['type'] == 'url') {
148                 $this->values[$name] = $this->validateUrl($arValues[$name]);
149             } else if ($arOption['type'] == 'int') {
150                 $this->values[$name] = $this->validateInt(
151                     $arValues[$name], $arOption['min'], $arOption['max']
152                 );
153             } else if (gettype($arOption['type']) == 'array') {
154                 $this->values[$name] = $this->validateArray(
155                     $arValues[$name], $arOption['type']
156                 );
157             } else if ($arOption['type'] == 'age') {
158                 $this->values[$name] = $this->clamp(
159                     static::validateAge($arValues[$name]),
160                     $arOption['min'], null,
161                     true
162                 );
163             } else if ($arOption['type'] != 'skip') {
164                 throw new \InvalidArgumentException(
165                     'Unsupported option type: ' . $arOption['type']
166                 );
167             }
168             unset($arValues[$name]);
169         }
170
171         if (count($arValues) > 0) {
172             throw new \InvalidArgumentException(
173                 'Unsupported parameter: ' . implode(', ', array_keys($arValues))
174             );
175         }
176
177         $this->calcPageSize();
178     }
179
180     /**
181      * Calculate the browser size and screenshot size from the given options
182      *
183      * @return void
184      */
185     protected function calcPageSize()
186     {
187         if ($this->values['swidth'] === null) {
188             $this->values['swidth'] = $this->values['bwidth'];
189         }
190         if ($this->values['smode'] == 'page') {
191             return;
192         }
193
194         if ($this->values['sheight'] !== null) {
195             $this->values['bheight'] = intval(
196                 $this->values['bwidth'] / $this->values['swidth']
197                 * $this->values['sheight']
198             );
199         } else if ($this->values['bheight'] !== null) {
200             $this->values['sheight'] = intval(
201                 $this->values['swidth'] / $this->values['bwidth']
202                 * $this->values['bheight']
203             );
204         } else {
205             //no height set. use 4:3
206             $this->values['sheight'] = $this->values['swidth'] / 4 * 3;
207             $this->values['bheight'] = $this->values['bwidth'] / 4 * 3;
208         }
209     }
210
211     /**
212      * Makes sure a value is between $min and $max (inclusive)
213      *
214      * @param integer $value  Value to check
215      * @param integer $min    Minimum allowed value
216      * @param integer $max    Maximum allowed value
217      * @param boolean $silent When silent, invalid values are corrected.
218      *                        An exception is thrown otherwise.
219      *
220      * @return integer Corrected value
221      * @throws \InvalidArgumentException When not silent and value outside range
222      */
223     protected function clamp($value, $min, $max, $silent = false)
224     {
225         if ($min !== null && $value < $min) {
226             if ($silent) {
227                 $value = $min;
228             } else {
229                 throw new \InvalidArgumentException(
230                     'Value must be at least ' . $min
231                 );
232             }
233         }
234         if ($max !== null && $value > $max) {
235             if ($silent) {
236                 $value = $max;
237             } else {
238                 throw new \InvalidArgumentException(
239                     'Value may be up to ' . $min
240                 );
241             }
242         }
243         return $value;
244     }
245
246     /**
247      * Validates an age is numeric. If it is not numeric, it's interpreted as
248      * a ISO 8601 duration specification.
249      *
250      * @param string $value Age in seconds
251      *
252      * @return integer Age in seconds
253      * @throws \InvalidArgumentException
254      * @link   http://en.wikipedia.org/wiki/Iso8601#Durations
255      */
256     public static function validateAge($value)
257     {
258         if (!is_numeric($value)) {
259             //convert short notation to seconds
260             $value = 'P' . ltrim(strtoupper($value), 'P');
261             try {
262                 $interval = new \DateInterval($value);
263             } catch (\Exception $e) {
264                 throw new \InvalidArgumentException(
265                     'Invalid age: ' . $value
266                 );
267             }
268             $value = 86400 * (
269                 $interval->y * 365
270                 + $interval->m * 30
271                 + $interval->d
272             ) + $interval->h * 3600
273                 + $interval->m * 60
274                 + $interval->s;
275         }
276         return $value;
277     }
278
279     /**
280      * Check that a given value exists in an array
281      *
282      * @param string $value   Value to check
283      * @param array  $options Array of allowed values
284      *
285      * @return string Value
286      * @throws \InvalidArgumentException If the value does not exist in $options
287      */
288     protected function validateArray($value, $options)
289     {
290         if (array_search($value, $options) === false) {
291             throw new \InvalidArgumentException(
292                 'Invalid value ' . $value . '.'
293                 . ' Allowed: ' . implode(', ', $options)
294             );
295         }
296         return $value;
297     }
298
299     /**
300      * Validate that a value is numeric and between $min and $max (inclusive)
301      *
302      * @param string  $value Value to check
303      * @param integer $min   Minimum allowed value
304      * @param integer $max   Maximum allowed value
305      *
306      * @return integer Value as integer
307      * @throws \InvalidArgumentException When outside range or not numeric
308      */
309     protected function validateInt($value, $min, $max)
310     {
311         if (!is_numeric($value)) {
312             throw new \InvalidArgumentException(
313                 'Value must be a number'
314             );
315         }
316         $value = (int) $value;
317         return $this->clamp($value, $min, $max);
318     }
319
320     /**
321      * Validate (and fix) an URL
322      *
323      * @param string $url URL
324      *
325      * @return string Fixed URL
326      * @throws \InvalidArgumentException
327      */
328     protected function validateUrl($url)
329     {
330         $parts = parse_url($url);
331         if ($parts === false) {
332             throw new \InvalidArgumentException('Invalid URL');
333         }
334         if (!isset($parts['scheme'])) {
335             $url = 'http://' . $url;
336             $parts = parse_url($url);
337         } else if ($parts['scheme'] != 'http' && $parts['scheme'] != 'https') {
338             throw new \InvalidArgumentException('Unsupported protocol');
339         }
340         if (!isset($parts['host'])) {
341             throw new \InvalidArgumentException('URL host missing');
342         }
343         return $url;
344     }
345
346     /**
347      * Set phancap configuration
348      *
349      * @param Config $config Phancap configuration
350      *
351      * @return void
352      */
353     public function setConfig(Config $config)
354     {
355         $this->config = $config;
356         $this->options['smaxage']['default'] = $this->config->screenshotMaxAge;
357         $this->options['smaxage']['min']     = $this->config->screenshotMinAge;
358     }
359 }
360 ?>