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\Validator; 25use Fisharebest\Webtrees\View as WebtreesView; 26use Fisharebest\Webtrees\Webtrees; 27use Psr\Http\Message\ResponseFactoryInterface; 28use Psr\Http\Message\ResponseInterface; 29use Psr\Http\Message\ServerRequestInterface; 30use Psr\Http\Message\StreamFactoryInterface; 31 32/** 33 * Get the IoC container, or fetch something from it. 34 * 35 * @param string|null $abstract 36 * 37 * @return mixed 38 */ 39function app(string $abstract = null) 40{ 41 if ($abstract === null) { 42 return Webtrees::container(); 43 } 44 45 return Webtrees::make($abstract); 46} 47 48/** 49 * Generate a URL to an asset file in the public folder. 50 * Add a version parameter for cache-busting. 51 * 52 * @param string $path 53 * 54 * @return string 55 */ 56function asset(string $path): string 57{ 58 if (substr($path, -1) === '/') { 59 $version = ''; 60 } elseif (Webtrees::STABILITY === '') { 61 $version = '?v=' . Webtrees::VERSION; 62 } else { 63 $version = '?v=' . filemtime(Webtrees::ROOT_DIR . 'public/' . $path); 64 } 65 66 $request = app(ServerRequestInterface::class); 67 assert($request instanceof ServerRequestInterface); 68 69 $base_url = Validator::attributes($request)->string('base_url'); 70 71 return $base_url . '/public/' . $path . $version; 72} 73 74/** 75 * Generate a CSRF token form field. 76 * 77 * @return string 78 */ 79function csrf_field(): string 80{ 81 return '<input type="hidden" name="_csrf" value="' . e(WebtreesSession::getCsrfToken()) . '">'; 82} 83 84/** 85 * Get the CSRF token value. 86 * 87 * @return string 88 */ 89function csrf_token(): string 90{ 91 return WebtreesSession::getCsrfToken(); 92} 93 94/** 95 * @param string $url 96 * @param int $code 97 * 98 * @return ResponseInterface 99 */ 100function redirect(string $url, int $code = StatusCodeInterface::STATUS_FOUND): ResponseInterface 101{ 102 /** @var ResponseFactoryInterface $response_factory */ 103 $response_factory = app(ResponseFactoryInterface::class); 104 105 return $response_factory 106 ->createResponse($code) 107 ->withHeader('Location', $url); 108} 109 110/** 111 * Create a response. 112 * 113 * @param mixed $content 114 * @param int $code 115 * @param array<string> $headers 116 * 117 * @return ResponseInterface 118 */ 119function response($content = '', int $code = StatusCodeInterface::STATUS_OK, array $headers = []): ResponseInterface 120{ 121 if ($content === '' && $code === StatusCodeInterface::STATUS_OK) { 122 $code = StatusCodeInterface::STATUS_NO_CONTENT; 123 } 124 125 if ($headers === []) { 126 if (is_string($content)) { 127 $headers = [ 128 'Content-Type' => 'text/html; charset=UTF-8', 129 ]; 130 } else { 131 $content = json_encode($content, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE); 132 $headers = [ 133 'Content-Type' => 'application/json', 134 ]; 135 } 136 } 137 138 /** @var ResponseFactoryInterface $response_factory */ 139 $response_factory = app(ResponseFactoryInterface::class); 140 141 /** @var StreamFactoryInterface $stream_factory */ 142 $stream_factory = app(StreamFactoryInterface::class); 143 144 $stream = $stream_factory->createStream($content); 145 146 $response = $response_factory 147 ->createResponse($code) 148 ->withBody($stream); 149 150 foreach ($headers as $key => $value) { 151 $response = $response->withHeader($key, $value); 152 } 153 154 return $response; 155} 156 157/** 158 * Generate a URL for a named route. 159 * 160 * @param string $route_name 161 * @param array<bool|int|string|array<string>|null> $parameters 162 * 163 * @return string 164 */ 165function route(string $route_name, array $parameters = []): string 166{ 167 $request = app(ServerRequestInterface::class); 168 assert($request instanceof ServerRequestInterface); 169 170 $base_url = Validator::attributes($request)->string('base_url'); 171 $router_container = app(RouterContainer::class); 172 $route = $router_container->getMap()->getRoute($route_name); 173 174 // Generate the URL. 175 $url = $router_container->getGenerator()->generate($route_name, $parameters); 176 177 // Aura ignores parameters that are not tokens. We need to add them as query parameters. 178 $parameters = array_filter($parameters, static function (string $key) use ($route): bool { 179 return !str_contains($route->path, '{' . $key . '}') && !str_contains($route->path, '{/' . $key . '}'); 180 }, ARRAY_FILTER_USE_KEY); 181 182 if (Validator::attributes($request)->boolean('rewrite_urls', false)) { 183 // Make the pretty URL absolute. 184 $base_path = parse_url($base_url, PHP_URL_PATH) ?? ''; 185 $url = $base_url . substr($url, strlen($base_path)); 186 } else { 187 // Turn the pretty URL into an ugly one. 188 $path = parse_url($url, PHP_URL_PATH); 189 $parameters = ['route' => $path] + $parameters; 190 $url = $base_url . '/index.php'; 191 } 192 193 return Html::url($url, $parameters); 194} 195 196/** 197 * Create and render a view in a single operation. 198 * 199 * @param string $name 200 * @param array<mixed> $data 201 * 202 * @return string 203 */ 204function view(string $name, array $data = []): string 205{ 206 return WebtreesView::make($name, $data); 207} 208