code = $code;
}
}
/**
* Sets HTTP response code.
* @return static
* @throws \Packetery\Nette\InvalidArgumentException if code is invalid
* @throws \Packetery\Nette\InvalidStateException if HTTP headers have been sent
*/
public function setCode(int $code, ?string $reason = null)
{
if ($code < 100 || $code > 599) {
throw new \Packetery\Nette\InvalidArgumentException("Bad HTTP response '{$code}'.");
}
self::checkHeaders();
$this->code = $code;
$protocol = $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.1';
$reason = $reason ?? self::ReasonPhrases[$code] ?? 'Unknown status';
\header("{$protocol} {$code} {$reason}");
return $this;
}
/**
* Returns HTTP response code.
*/
public function getCode() : int
{
return $this->code;
}
/**
* Sends an HTTP header and overwrites previously sent header of the same name.
* @return static
* @throws \Packetery\Nette\InvalidStateException if HTTP headers have been sent
*/
public function setHeader(string $name, ?string $value)
{
self::checkHeaders();
if ($value === null) {
\header_remove($name);
} elseif (\strcasecmp($name, 'Content-Length') === 0 && \ini_get('zlib.output_compression')) {
// ignore, PHP bug #44164
} else {
\header($name . ': ' . $value);
}
return $this;
}
/**
* Sends an HTTP header and doesn't overwrite previously sent header of the same name.
* @return static
* @throws \Packetery\Nette\InvalidStateException if HTTP headers have been sent
*/
public function addHeader(string $name, string $value)
{
self::checkHeaders();
\header($name . ': ' . $value, \false);
return $this;
}
/**
* Deletes a previously sent HTTP header.
* @return static
* @throws \Packetery\Nette\InvalidStateException if HTTP headers have been sent
*/
public function deleteHeader(string $name)
{
self::checkHeaders();
\header_remove($name);
return $this;
}
/**
* Sends a Content-type HTTP header.
* @return static
* @throws \Packetery\Nette\InvalidStateException if HTTP headers have been sent
*/
public function setContentType(string $type, ?string $charset = null)
{
$this->setHeader('Content-Type', $type . ($charset ? '; charset=' . $charset : ''));
return $this;
}
/**
* Response should be downloaded with 'Save as' dialog.
* @return static
* @throws \Packetery\Nette\InvalidStateException if HTTP headers have been sent
*/
public function sendAsFile(string $fileName)
{
$this->setHeader('Content-Disposition', 'attachment; filename="' . \str_replace('"', '', $fileName) . '"; ' . "filename*=utf-8''" . \rawurlencode($fileName));
return $this;
}
/**
* Redirects to another URL. Don't forget to quit the script then.
* @throws \Packetery\Nette\InvalidStateException if HTTP headers have been sent
*/
public function redirect(string $url, int $code = self::S302_Found) : void
{
$this->setCode($code);
$this->setHeader('Location', $url);
if (\preg_match('#^https?:|^\\s*+[a-z0-9+.-]*+[^:]#i', $url)) {
$escapedUrl = \htmlspecialchars($url, \ENT_IGNORE | \ENT_QUOTES, 'UTF-8');
echo "
Redirect
\n\nPlease click here to continue.
";
}
}
/**
* Sets the expiration of the HTTP document using the `Cache-Control` and `Expires` headers.
* The parameter is either a time interval (as text) or `null`, which disables caching.
* @return static
* @throws \Packetery\Nette\InvalidStateException if HTTP headers have been sent
*/
public function setExpiration(?string $expire)
{
$this->setHeader('Pragma', null);
if (!$expire) {
// no cache
$this->setHeader('Cache-Control', 's-maxage=0, max-age=0, must-revalidate');
$this->setHeader('Expires', 'Mon, 23 Jan 1978 10:00:00 GMT');
return $this;
}
$expire = DateTime::from($expire);
$this->setHeader('Cache-Control', 'max-age=' . ($expire->format('U') - \time()));
$this->setHeader('Expires', Helpers::formatDate($expire));
return $this;
}
/**
* Returns whether headers have already been sent from the server to the browser,
* so it is no longer possible to send headers or change the response code.
*/
public function isSent() : bool
{
return \headers_sent();
}
/**
* Returns the sent HTTP header, or `null` if it does not exist. The parameter is case-insensitive.
*/
public function getHeader(string $header) : ?string
{
if (\func_num_args() > 1) {
\trigger_error(__METHOD__ . '() parameter $default is deprecated, use operator ??', \E_USER_DEPRECATED);
}
$header .= ':';
$len = \strlen($header);
foreach (\headers_list() as $item) {
if (\strncasecmp($item, $header, $len) === 0) {
return \ltrim(\substr($item, $len));
}
}
return null;
}
/**
* Returns all sent HTTP headers as associative array.
*/
public function getHeaders() : array
{
$headers = [];
foreach (\headers_list() as $header) {
$a = \strpos($header, ':');
$headers[\substr($header, 0, $a)] = (string) \substr($header, $a + 2);
}
return $headers;
}
/**
* Sends a cookie.
* @param string|int|\DateTimeInterface $expire expiration time, value null means "until the browser session ends"
* @return static
* @throws \Packetery\Nette\InvalidStateException if HTTP headers have been sent
*/
public function setCookie(string $name, string $value, $expire, ?string $path = null, ?string $domain = null, ?bool $secure = null, ?bool $httpOnly = null, ?string $sameSite = null)
{
self::checkHeaders();
$options = ['expires' => $expire ? (int) DateTime::from($expire)->format('U') : 0, 'path' => $path ?? ($domain ? '/' : $this->cookiePath), 'domain' => $domain ?? ($path ? '' : $this->cookieDomain), 'secure' => $secure ?? $this->cookieSecure, 'httponly' => $httpOnly ?? \true, 'samesite' => $sameSite = $sameSite ?? self::SameSiteLax];
if (\PHP_VERSION_ID >= 70300) {
\setcookie($name, $value, $options);
} else {
\setcookie($name, $value, $options['expires'], $options['path'] . ($sameSite ? "; SameSite={$sameSite}" : ''), $options['domain'], $options['secure'], $options['httponly']);
}
return $this;
}
/**
* Deletes a cookie.
* @throws \Packetery\Nette\InvalidStateException if HTTP headers have been sent
*/
public function deleteCookie(string $name, ?string $path = null, ?string $domain = null, ?bool $secure = null) : void
{
$this->setCookie($name, '', 0, $path, $domain, $secure);
}
private function checkHeaders() : void
{
if (\PHP_SAPI === 'cli') {
} elseif (\headers_sent($file, $line)) {
throw new \Packetery\Nette\InvalidStateException('Cannot send header after HTTP headers have been sent' . ($file ? " (output started at {$file}:{$line})." : '.'));
} elseif ($this->warnOnBuffer && \ob_get_length() && !\array_filter(\ob_get_status(\true), function (array $i) : bool {
return !$i['chunk_size'];
})) {
\trigger_error('Possible problem: you are sending a HTTP header while already having some data in output buffer. Try Tracy\\OutputDebugger or send cookies/start session earlier.');
}
}
}