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 20namespace Fisharebest\Webtrees\Module; 21 22use Fig\Http\Message\StatusCodeInterface; 23use Fisharebest\Webtrees\Carbon; 24use Illuminate\Support\Str; 25use Psr\Http\Message\ResponseInterface; 26use Psr\Http\Message\ServerRequestInterface; 27use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; 28use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; 29 30use function strlen; 31 32/** 33 * Trait ModuleCustomTrait - default implementation of ModuleCustomInterface 34 */ 35trait ModuleCustomTrait 36{ 37 /** 38 * Where does this module store its resources 39 * 40 * @return string 41 */ 42 abstract public function resourcesFolder(): string; 43 44 /** 45 * A unique internal name for this module (based on the installation folder). 46 * 47 * @return string 48 */ 49 abstract public function name(): string; 50 51 /** 52 * The person or organisation who created this module. 53 * 54 * @return string 55 */ 56 public function customModuleAuthorName(): string 57 { 58 return ''; 59 } 60 61 /** 62 * The version of this module. 63 * 64 * @return string e.g. '1.2.3' 65 */ 66 public function customModuleVersion(): string 67 { 68 return ''; 69 } 70 71 /** 72 * A URL that will provide the latest version of this module. 73 * 74 * @return string 75 */ 76 public function customModuleLatestVersionUrl(): string 77 { 78 return ''; 79 } 80 81 /** 82 * Where to get support for this module. Perhaps a github respository? 83 * 84 * @return string 85 */ 86 public function customModuleSupportUrl(): string 87 { 88 return ''; 89 } 90 91 /** 92 * Additional/updated translations. 93 * 94 * @param string $language 95 * 96 * @return string[] 97 */ 98 public function customTranslations(string $language): array 99 { 100 return []; 101 } 102 103 /** 104 * Create a URL for an asset. 105 * 106 * @param string $asset e.g. "css/theme.css" or "img/banner.png" 107 * 108 * @return string 109 */ 110 public function assetUrl(string $asset): string 111 { 112 $file = $this->resourcesFolder() . $asset; 113 114 // Add the file's modification time to the URL, so we can set long expiry cache headers. 115 $hash = filemtime($file); 116 117 return route('module', [ 118 'module' => $this->name(), 119 'action' => 'Asset', 120 'asset' => $asset, 121 'hash' => $hash, 122 ]); 123 } 124 125 /** 126 * Serve a CSS/JS file. 127 * 128 * @param ServerRequestInterface $request 129 * 130 * @return ResponseInterface 131 */ 132 public function getAssetAction(ServerRequestInterface $request): ResponseInterface 133 { 134 // The file being requested. e.g. "css/theme.css" 135 $asset = $request->getQueryParams()['asset']; 136 137 // Do not allow requests that try to access parent folders. 138 if (Str::contains($asset, '..')) { 139 throw new AccessDeniedHttpException($asset); 140 } 141 142 // Find the file for this asset. 143 // Note that we could also generate CSS files using views/templates. 144 // e.g. $file = view(.... 145 $file = $this->resourcesFolder() . $asset; 146 147 if (!file_exists($file)) { 148 throw new NotFoundHttpException($file); 149 } 150 151 $content = file_get_contents($file); 152 $extension = pathinfo($asset, PATHINFO_EXTENSION); 153 154 $mime_types = [ 155 'css' => 'text/css', 156 'gif' => 'image/gif', 157 'js' => 'application/javascript', 158 'jpg' => 'image/jpeg', 159 'jpeg' => 'image/jpeg', 160 'json' => 'application/json', 161 'png' => 'image/png', 162 'txt' => 'text/plain', 163 ]; 164 165 $mime_type = $mime_types[$extension] ?? 'application/octet-stream'; 166 167 $headers = [ 168 'Content-Type' => $mime_type, 169 'Cache-Control' => 'max-age=31536000, public', 170 'Content-Length' => strlen($content), 171 'Expires' => Carbon::now()->addYears(10)->toRfc7231String(), 172 ]; 173 174 return response($content, StatusCodeInterface::STATUS_OK, $headers); 175 } 176} 177