1<?php 2 3/** 4 * webtrees: online genealogy 5 * Copyright (C) 2019 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 <http://www.gnu.org/licenses/>. 16 */ 17 18declare(strict_types=1); 19 20use Aura\Router\RouterContainer; 21use Fig\Http\Message\StatusCodeInterface; 22use Fisharebest\Webtrees\Application; 23use Fisharebest\Webtrees\Html; 24use Fisharebest\Webtrees\Session; 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 Application::getInstance(); 43 } 44 45 return Application::getInstance()->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 $base_url = app(ServerRequestInterface::class)->getAttribute('base_url'); 67 68 return $base_url . '/public/' . $path . $version; 69} 70 71/** 72 * Generate a CSRF token form field. 73 * 74 * @return string 75 */ 76function csrf_field() 77{ 78 return '<input type="hidden" name="csrf" value="' . e(Session::getCsrfToken()) . '">'; 79} 80 81/** 82 * Get the CSRF token value. 83 * 84 * @return string 85 */ 86function csrf_token() 87{ 88 return Session::getCsrfToken(); 89} 90 91/** 92 * @param string $url 93 * @param int $code 94 * 95 * @return ResponseInterface 96 */ 97function redirect(string $url, $code = StatusCodeInterface::STATUS_FOUND): ResponseInterface 98{ 99 /** @var ResponseFactoryInterface $response_factory */ 100 $response_factory = app(ResponseFactoryInterface::class); 101 102 return $response_factory 103 ->createResponse($code) 104 ->withHeader('Location', $url); 105} 106 107/** 108 * Create a response. 109 * 110 * @param mixed $content 111 * @param int $code 112 * @param string[] $headers 113 * 114 * @return ResponseInterface 115 */ 116function response($content = '', $code = StatusCodeInterface::STATUS_OK, $headers = []): ResponseInterface 117{ 118 if ($content === '' && $code === StatusCodeInterface::STATUS_OK) { 119 $code = StatusCodeInterface::STATUS_NO_CONTENT; 120 } 121 122 if ($headers === []) { 123 if (is_string($content)) { 124 $headers = [ 125 'Content-Type' => 'text/html; charset=utf-8', 126 'Content-Length' => strlen($content), 127 ]; 128 } else { 129 $content = json_encode($content, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT); 130 $headers = [ 131 'Content-Type' => 'application/json', 132 'Content-Length' => strlen($content), 133 ]; 134 } 135 } 136 137 /** @var ResponseFactoryInterface $response_factory */ 138 $response_factory = app(ResponseFactoryInterface::class); 139 140 /** @var StreamFactoryInterface $stream_factory */ 141 $stream_factory = app(StreamFactoryInterface::class); 142 143 $stream = $stream_factory->createStream($content); 144 145 $response = $response_factory 146 ->createResponse($code) 147 ->withBody($stream); 148 149 foreach ($headers as $key => $value) { 150 $response = $response->withHeader($key, $value); 151 } 152 153 return $response; 154} 155 156/** 157 * Generate a URL for a named route. 158 * 159 * @param string $route_name 160 * @param mixed[] $parameters 161 * 162 * @return string 163 */ 164function route(string $route_name, array $parameters = []): string 165{ 166 $request = app(ServerRequestInterface::class); 167 $base_url = $request->getAttribute('base_url'); 168 $router_container = app(RouterContainer::class); 169 $route = $router_container->getMap()->getRoute($route_name); 170 171 // Generate the URL. 172 $url = $router_container->getGenerator()->generate($route_name, $parameters); 173 174 // Aura ignores parameters that are not tokens. We need to add them as query parameters. 175 $parameters = array_filter($parameters, static function (string $key) use ($route): bool { 176 return strpos($route->path, '{' . $key . '}') === false && strpos($route->path, '{/' . $key . '}') === false; 177 }, ARRAY_FILTER_USE_KEY); 178 179 if ($request->getAttribute('rewrite_urls') === '1') { 180 // Make the pretty URL absolute. 181 $base_path = parse_url($base_url, PHP_URL_PATH) ?? ''; 182 $url = $base_url . substr($url, strlen($base_path)); 183 } else { 184 // Turn the pretty URL into an ugly one. 185 $path = parse_url($url, PHP_URL_PATH); 186 $parameters = ['route' => $path] + $parameters; 187 $url = $base_url . '/index.php'; 188 } 189 190 return Html::url($url, $parameters); 191} 192 193/** 194 * Cerate and render a view in a single operation. 195 * 196 * @param string $name 197 * @param mixed[] $data 198 * 199 * @return string 200 */ 201function view(string $name, array $data = []) 202{ 203 return WebtreesView::make($name, $data); 204} 205