1<?php 2 3/** 4 * webtrees: online genealogy 5 * Copyright (C) 2021 webtrees development team 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation, either version 3 of the License, or 9 * (at your option) any later version. 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <https://www.gnu.org/licenses/>. 16 */ 17 18declare(strict_types=1); 19 20use Aura\Router\RouterContainer; 21use Fig\Http\Message\StatusCodeInterface; 22use Fisharebest\Webtrees\Html; 23use Fisharebest\Webtrees\Session as WebtreesSession; 24use Fisharebest\Webtrees\View as WebtreesView; 25use Fisharebest\Webtrees\Webtrees; 26use Psr\Http\Message\ResponseFactoryInterface; 27use Psr\Http\Message\ResponseInterface; 28use Psr\Http\Message\ServerRequestInterface; 29use Psr\Http\Message\StreamFactoryInterface; 30 31/** 32 * Get the IoC container, or fetch something from it. 33 * 34 * @param string|null $abstract 35 * 36 * @return mixed 37 */ 38function app(string $abstract = null) 39{ 40 if ($abstract === null) { 41 return Webtrees::container(); 42 } 43 44 return Webtrees::make($abstract); 45} 46 47/** 48 * Generate a URL to an asset file in the public folder. 49 * Add a version parameter for cache-busting. 50 * 51 * @param string $path 52 * 53 * @return string 54 */ 55function asset(string $path): string 56{ 57 if (substr($path, -1) === '/') { 58 $version = ''; 59 } elseif (Webtrees::STABILITY === '') { 60 $version = '?v=' . Webtrees::VERSION; 61 } else { 62 $version = '?v=' . filemtime(Webtrees::ROOT_DIR . 'public/' . $path); 63 } 64 65 $base_url = app(ServerRequestInterface::class)->getAttribute('base_url'); 66 67 return $base_url . '/public/' . $path . $version; 68} 69 70/** 71 * Generate a CSRF token form field. 72 * 73 * @return string 74 */ 75function csrf_field(): string 76{ 77 return '<input type="hidden" name="_csrf" value="' . e(WebtreesSession::getCsrfToken()) . '">'; 78} 79 80/** 81 * Get the CSRF token value. 82 * 83 * @return string 84 */ 85function csrf_token(): string 86{ 87 return WebtreesSession::getCsrfToken(); 88} 89 90/** 91 * @param string $url 92 * @param int $code 93 * 94 * @return ResponseInterface 95 */ 96function redirect(string $url, int $code = StatusCodeInterface::STATUS_FOUND): ResponseInterface 97{ 98 /** @var ResponseFactoryInterface $response_factory */ 99 $response_factory = app(ResponseFactoryInterface::class); 100 101 return $response_factory 102 ->createResponse($code) 103 ->withHeader('Location', $url); 104} 105 106/** 107 * Create a response. 108 * 109 * @param mixed $content 110 * @param int $code 111 * @param string[] $headers 112 * 113 * @return ResponseInterface 114 */ 115function response($content = '', int $code = StatusCodeInterface::STATUS_OK, array $headers = []): ResponseInterface 116{ 117 if ($content === '' && $code === StatusCodeInterface::STATUS_OK) { 118 $code = StatusCodeInterface::STATUS_NO_CONTENT; 119 } 120 121 if ($headers === []) { 122 if (is_string($content)) { 123 $headers = [ 124 'Content-Type' => 'text/html; charset=UTF-8', 125 ]; 126 } else { 127 $content = json_encode($content, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE); 128 $headers = [ 129 'Content-Type' => 'application/json', 130 ]; 131 } 132 } 133 134 /** @var ResponseFactoryInterface $response_factory */ 135 $response_factory = app(ResponseFactoryInterface::class); 136 137 /** @var StreamFactoryInterface $stream_factory */ 138 $stream_factory = app(StreamFactoryInterface::class); 139 140 $stream = $stream_factory->createStream($content); 141 142 $response = $response_factory 143 ->createResponse($code) 144 ->withBody($stream); 145 146 foreach ($headers as $key => $value) { 147 $response = $response->withHeader($key, $value); 148 } 149 150 return $response; 151} 152 153/** 154 * Generate a URL for a named route. 155 * 156 * @param string $route_name 157 * @param array<mixed> $parameters 158 * 159 * @return string 160 */ 161function route(string $route_name, array $parameters = []): string 162{ 163 $request = app(ServerRequestInterface::class); 164 $base_url = $request->getAttribute('base_url'); 165 $router_container = app(RouterContainer::class); 166 $route = $router_container->getMap()->getRoute($route_name); 167 168 // Generate the URL. 169 $url = $router_container->getGenerator()->generate($route_name, $parameters); 170 171 // Aura ignores parameters that are not tokens. We need to add them as query parameters. 172 $parameters = array_filter($parameters, static function (string $key) use ($route): bool { 173 return !str_contains($route->path, '{' . $key . '}') && !str_contains($route->path, '{/' . $key . '}'); 174 }, ARRAY_FILTER_USE_KEY); 175 176 if ($request->getAttribute('rewrite_urls') === '1') { 177 // Make the pretty URL absolute. 178 $base_path = parse_url($base_url, PHP_URL_PATH) ?? ''; 179 $url = $base_url . substr($url, strlen($base_path)); 180 } else { 181 // Turn the pretty URL into an ugly one. 182 $path = parse_url($url, PHP_URL_PATH); 183 $parameters = ['route' => $path] + $parameters; 184 $url = $base_url . '/index.php'; 185 } 186 187 return Html::url($url, $parameters); 188} 189 190/** 191 * Create and render a view in a single operation. 192 * 193 * @param string $name 194 * @param array<mixed> $data 195 * 196 * @return string 197 */ 198function view(string $name, array $data = []): string 199{ 200 return WebtreesView::make($name, $data); 201} 202