julienmonnerie/kirby/src/Http/Visitor.php
2022-06-17 17:51:59 +02:00

252 lines
6.6 KiB
PHP

<?php
namespace Kirby\Http;
use Kirby\Filesystem\Mime;
use Kirby\Toolkit\Collection;
use Kirby\Toolkit\Obj;
use Kirby\Toolkit\Str;
/**
* The Visitor class makes it easy to inspect information
* like the ip address, language, user agent and more
* of the current visitor
*
* @package Kirby Http
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://opensource.org/licenses/MIT
*/
class Visitor
{
/**
* IP address
* @var string|null
*/
protected $ip;
/**
* user agent
* @var string|null
*/
protected $userAgent;
/**
* accepted language
* @var string|null
*/
protected $acceptedLanguage;
/**
* accepted mime type
* @var string|null
*/
protected $acceptedMimeType;
/**
* Creates a new visitor object.
* Optional arguments can be passed to
* modify the information about the visitor.
*
* By default everything is pulled from $_SERVER
*
* @param array $arguments
*/
public function __construct(array $arguments = [])
{
$this->ip($arguments['ip'] ?? $_SERVER['REMOTE_ADDR'] ?? '');
$this->userAgent($arguments['userAgent'] ?? $_SERVER['HTTP_USER_AGENT'] ?? '');
$this->acceptedLanguage($arguments['acceptedLanguage'] ?? $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? '');
$this->acceptedMimeType($arguments['acceptedMimeType'] ?? $_SERVER['HTTP_ACCEPT'] ?? '');
}
/**
* Sets the accepted language if
* provided or returns the user's
* accepted language otherwise
*
* @param string|null $acceptedLanguage
* @return \Kirby\Toolkit\Obj|\Kirby\Http\Visitor|null
*/
public function acceptedLanguage(string $acceptedLanguage = null)
{
if ($acceptedLanguage === null) {
return $this->acceptedLanguages()->first();
}
$this->acceptedLanguage = $acceptedLanguage;
return $this;
}
/**
* Returns an array of all accepted languages
* including their quality and locale
*
* @return \Kirby\Toolkit\Collection
*/
public function acceptedLanguages()
{
$accepted = Str::accepted($this->acceptedLanguage);
$languages = [];
foreach ($accepted as $language) {
$value = $language['value'];
$parts = Str::split($value, '-');
$code = isset($parts[0]) ? Str::lower($parts[0]) : null;
$region = isset($parts[1]) ? Str::upper($parts[1]) : null;
$locale = $region ? $code . '_' . $region : $code;
$languages[$locale] = new Obj([
'code' => $code,
'locale' => $locale,
'original' => $value,
'quality' => $language['quality'],
'region' => $region,
]);
}
return new Collection($languages);
}
/**
* Checks if the user accepts the given language
*
* @param string $code
* @return bool
*/
public function acceptsLanguage(string $code): bool
{
$mode = Str::contains($code, '_') === true ? 'locale' : 'code';
foreach ($this->acceptedLanguages() as $language) {
if ($language->$mode() === $code) {
return true;
}
}
return false;
}
/**
* Sets the accepted mime type if
* provided or returns the user's
* accepted mime type otherwise
*
* @param string|null $acceptedMimeType
* @return \Kirby\Toolkit\Obj|\Kirby\Http\Visitor
*/
public function acceptedMimeType(string $acceptedMimeType = null)
{
if ($acceptedMimeType === null) {
return $this->acceptedMimeTypes()->first();
}
$this->acceptedMimeType = $acceptedMimeType;
return $this;
}
/**
* Returns a collection of all accepted mime types
*
* @return \Kirby\Toolkit\Collection
*/
public function acceptedMimeTypes()
{
$accepted = Str::accepted($this->acceptedMimeType);
$mimes = [];
foreach ($accepted as $mime) {
$mimes[$mime['value']] = new Obj([
'type' => $mime['value'],
'quality' => $mime['quality'],
]);
}
return new Collection($mimes);
}
/**
* Checks if the user accepts the given mime type
*
* @param string $mimeType
* @return bool
*/
public function acceptsMimeType(string $mimeType): bool
{
return Mime::isAccepted($mimeType, $this->acceptedMimeType);
}
/**
* Returns the MIME type from the provided list that
* is most accepted (= preferred) by the visitor
* @since 3.3.0
*
* @param string ...$mimeTypes MIME types to query for
* @return string|null Preferred MIME type
*/
public function preferredMimeType(string ...$mimeTypes): ?string
{
foreach ($this->acceptedMimeTypes() as $acceptedMime) {
// look for direct matches
if (in_array($acceptedMime->type(), $mimeTypes)) {
return $acceptedMime->type();
}
// test each option against wildcard `Accept` values
foreach ($mimeTypes as $expectedMime) {
if (Mime::matches($expectedMime, $acceptedMime->type()) === true) {
return $expectedMime;
}
}
}
return null;
}
/**
* Returns true if the visitor prefers a JSON response over
* an HTML response based on the `Accept` request header
* @since 3.3.0
*
* @return bool
*/
public function prefersJson(): bool
{
return $this->preferredMimeType('application/json', 'text/html') === 'application/json';
}
/**
* Sets the ip address if provided
* or returns the ip of the current
* visitor otherwise
*
* @param string|null $ip
* @return string|Visitor|null
*/
public function ip(string $ip = null)
{
if ($ip === null) {
return $this->ip;
}
$this->ip = $ip;
return $this;
}
/**
* Sets the user agent if provided
* or returns the user agent string of
* the current visitor otherwise
*
* @param string|null $userAgent
* @return string|Visitor|null
*/
public function userAgent(string $userAgent = null)
{
if ($userAgent === null) {
return $this->userAgent;
}
$this->userAgent = $userAgent;
return $this;
}
}