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
17 * Options a user can give to the API
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
30 * Available options and their configuration
34 public $options = array(
39 'title' => 'Website URL',
45 'title' => 'Browser width',
52 'title' => 'Browser height',
62 'title' => 'Screenshot width',
69 'title' => 'Screenshot height',
76 'title' => 'Screenshot format',
78 'type' => array('png', 'jpg', 'pdf'),
81 'title' => 'Screenshot mode',
82 'default' => 'screen',
83 'type' => array('screen', 'page'),
86 'title' => 'Maximum age for a screenshot',
94 'atimestamp' => array(
95 'title' => 'Timestamp the request has been generated',
100 'title' => 'Access token (user name)',
104 'asignature' => array(
105 'title' => 'Access signature',
112 * Actual values we use after parsing the GET parameters
116 public $values = array();
119 * Configuration object
127 * Parses an array of options, validates them and writes them into
130 * @param array $arValues Array of options, e.g. $_GET
133 * @throws \InvalidArgumentException When required parameters are missing
134 * or parameter values are invalid.
136 public function parse($arValues)
138 foreach ($this->options as $name => $arOption) {
139 $this->values[$name] = $arOption['default'];
140 if (!isset($arValues[$name])) {
141 if (isset($arOption['required'])) {
142 throw new \InvalidArgumentException(
143 $name . ' parameter missing'
149 if ($arValues[$name] === ''
150 && !isset($arOption['required'])
152 //allow empty value; default value will be used
153 } else if ($arOption['type'] == 'url') {
154 $this->values[$name] = $this->validateUrl($arValues[$name]);
155 } else if ($arOption['type'] == 'int') {
156 $this->values[$name] = $this->validateInt(
157 $name, $arValues[$name],
158 $arOption['min'], $arOption['max']
160 } else if (gettype($arOption['type']) == 'array') {
161 $this->values[$name] = $this->validateArray(
162 $name, $arValues[$name], $arOption['type']
164 } else if ($arOption['type'] == 'age') {
165 $this->values[$name] = $this->clamp(
166 static::validateAge($arValues[$name]),
167 $arOption['min'], null,
170 } else if ($arOption['type'] != 'skip') {
171 throw new \InvalidArgumentException(
172 'Unsupported option type: ' . $arOption['type']
175 unset($arValues[$name]);
178 if (count($arValues) > 0) {
179 throw new \InvalidArgumentException(
180 'Unsupported parameter: ' . implode(', ', array_keys($arValues))
184 $this->calcPageSize();
188 * Calculate the browser size and screenshot size from the given options
192 protected function calcPageSize()
194 if ($this->values['swidth'] === null) {
195 $this->values['swidth'] = $this->values['bwidth'];
197 if ($this->values['smode'] == 'page') {
201 if ($this->values['sheight'] !== null) {
202 $this->values['bheight'] = intval(
203 $this->values['bwidth'] / $this->values['swidth']
204 * $this->values['sheight']
206 } else if ($this->values['bheight'] !== null) {
207 $this->values['sheight'] = intval(
208 $this->values['swidth'] / $this->values['bwidth']
209 * $this->values['bheight']
212 //no height set. use 4:3
213 $this->values['sheight'] = $this->values['swidth'] / 4 * 3;
214 $this->values['bheight'] = $this->values['bwidth'] / 4 * 3;
219 * Makes sure a value is between $min and $max (inclusive)
221 * @param integer $value Value to check
222 * @param integer $min Minimum allowed value
223 * @param integer $max Maximum allowed value
224 * @param boolean $silent When silent, invalid values are corrected.
225 * An exception is thrown otherwise.
227 * @return integer Corrected value
228 * @throws \InvalidArgumentException When not silent and value outside range
230 protected function clamp($value, $min, $max, $silent = false)
232 if ($min !== null && $value < $min) {
236 throw new \InvalidArgumentException(
237 'Value must be at least ' . $min
241 if ($max !== null && $value > $max) {
245 throw new \InvalidArgumentException(
246 'Value may be up to ' . $min
254 * Validates an age is numeric. If it is not numeric, it's interpreted as
255 * a ISO 8601 duration specification.
257 * @param string $value Age in seconds
259 * @return integer Age in seconds
260 * @throws \InvalidArgumentException
261 * @link http://en.wikipedia.org/wiki/Iso8601#Durations
263 public static function validateAge($value)
265 if (!is_numeric($value)) {
266 //convert short notation to seconds
267 $value = 'P' . ltrim(strtoupper($value), 'P');
269 $interval = new \DateInterval($value);
270 } catch (\Exception $e) {
271 throw new \InvalidArgumentException(
272 'Invalid age: ' . $value
279 ) + $interval->h * 3600
287 * Check that a given value exists in an array
289 * @param string $name Variable name
290 * @param string $value Value to check
291 * @param array $options Array of allowed values
293 * @return string Value
294 * @throws \InvalidArgumentException If the value does not exist in $options
296 protected function validateArray($name, $value, $options)
298 if (array_search($value, $options) === false) {
299 throw new \InvalidArgumentException(
300 'Invalid value ' . $value . ' for ' . $name . '.'
301 . ' Allowed: ' . implode(', ', $options)
308 * Validate that a value is numeric and between $min and $max (inclusive)
310 * @param string $name Variable name
311 * @param string $value Value to check
312 * @param integer $min Minimum allowed value
313 * @param integer $max Maximum allowed value
315 * @return integer Value as integer
316 * @throws \InvalidArgumentException When outside range or not numeric
318 protected function validateInt($name, $value, $min, $max)
320 if (!is_numeric($value)) {
321 throw new \InvalidArgumentException(
322 $name . ' value must be a number'
325 $value = (int) $value;
326 return $this->clamp($value, $min, $max);
330 * Validate (and fix) an URL
332 * @param string $url URL
334 * @return string Fixed URL
335 * @throws \InvalidArgumentException
337 protected function validateUrl($url)
340 throw new \InvalidArgumentException('URL is empty');
342 $parts = parse_url($url);
343 if ($parts === false) {
344 throw new \InvalidArgumentException('Invalid URL');
346 if (!isset($parts['scheme'])) {
347 $url = 'http://' . $url;
348 $parts = parse_url($url);
349 } else if ($parts['scheme'] != 'http' && $parts['scheme'] != 'https') {
350 throw new \InvalidArgumentException('Unsupported protocol');
352 if (!isset($parts['host'])) {
353 throw new \InvalidArgumentException('URL host missing');
357 if (strlen(preg_replace('#[[:ascii:]]#', '', $parts['host']))) {
358 //non-ascii characters in the host name
359 $host = idn_to_ascii($parts['host']);
360 if ($host === false) {
361 //incoming URL was not UTF-8 but some ISO dialect
362 $host = idn_to_ascii(utf8_encode($parts['host']));
363 if ($host === false) {
364 throw new \InvalidArgumentException(
365 'Strange characters in host name'
369 $parts['host'] = $host;
372 if (strlen(preg_replace('#[[:ascii:]]#', '', $parts['path']))) {
373 //non-ascii characters in the path
374 $parts['path'] = str_replace('%2F', '/', urlencode($parts['path']));
379 $url = static::http_build_url($parts);
386 * Set phancap configuration
388 * @param Config $config Phancap configuration
392 public function setConfig(Config $config)
394 $this->config = $config;
395 $this->options['smaxage']['default'] = $this->config->screenshotMaxAge;
396 $this->options['smaxage']['min'] = $this->config->screenshotMinAge;
400 * Re-build an URL parts array generated by parse_url()
402 * @param string $parts Array of URL parts
406 protected static function http_build_url($parts)
408 $scheme = isset($parts['scheme'])
409 ? $parts['scheme'] . '://' : '';
410 $host = isset($parts['host'])
411 ? $parts['host'] : '';
412 $port = isset($parts['port'])
413 ? ':' . $parts['port'] : '';
414 $user = isset($parts['user'])
415 ? $parts['user'] : '';
416 $pass = isset($parts['pass'])
418 ? ':' . $parts['pass'] : '';
419 $pass = ($user || $pass)
421 $path = isset($parts['path'])
422 ? $parts['path'] : '';
423 $query = isset($parts['query'])
424 ? '?' . $parts['query'] : '';
425 $fragment = isset($parts['fragment'])
426 ? '#' . $parts['fragment'] : '';
427 return "$scheme$user$pass$host$port$path$query$fragment";