1<?php 2/** 3 * webtrees: online genealogy 4 * Copyright (C) 2019 webtrees development team 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation, either version 3 of the License, or 8 * (at your option) any later version. 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * You should have received a copy of the GNU General Public License 14 * along with this program. If not, see <http://www.gnu.org/licenses/>. 15 */ 16declare(strict_types=1); 17 18namespace Fisharebest\Webtrees\Module; 19 20use Fig\Http\Message\StatusCodeInterface; 21use Fisharebest\Webtrees\Carbon; 22use Illuminate\Support\Str; 23use Psr\Http\Message\ResponseInterface; 24use Psr\Http\Message\ServerRequestInterface; 25use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; 26use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; 27use function strlen; 28 29/** 30 * Trait ModuleCustomTrait - default implementation of ModuleCustomInterface 31 */ 32trait ModuleCustomTrait 33{ 34 /** 35 * Where does this module store its resources 36 * 37 * @return string 38 */ 39 abstract public function resourcesFolder(): string; 40 41 /** 42 * A unique internal name for this module (based on the installation folder). 43 * 44 * @return string 45 */ 46 abstract public function name(): string; 47 48 /** 49 * The person or organisation who created this module. 50 * 51 * @return string 52 */ 53 public function customModuleAuthorName(): string 54 { 55 return ''; 56 } 57 58 /** 59 * The version of this module. 60 * 61 * @return string e.g. '1.2.3' 62 */ 63 public function customModuleVersion(): string 64 { 65 return ''; 66 } 67 68 /** 69 * A URL that will provide the latest version of this module. 70 * 71 * @return string 72 */ 73 public function customModuleLatestVersionUrl(): string 74 { 75 return ''; 76 } 77 78 /** 79 * Where to get support for this module. Perhaps a github respository? 80 * 81 * @return string 82 */ 83 public function customModuleSupportUrl(): string 84 { 85 return ''; 86 } 87 88 /** 89 * Additional/updated translations. 90 * 91 * @param string $language 92 * 93 * @return string[] 94 */ 95 public function customTranslations(string $language): array 96 { 97 return []; 98 } 99 100 /** 101 * Create a URL for an asset. 102 * 103 * @param string $asset e.g. "css/theme.css" or "img/banner.png" 104 * 105 * @return string 106 */ 107 public function assetUrl(string $asset): string 108 { 109 $file = $this->resourcesFolder() . $asset; 110 111 // Add the file's modification time to the URL, so we can set long expiry cache headers. 112 $hash = filemtime($file); 113 114 return route('module', [ 115 'module' => $this->name(), 116 'action' => 'asset', 117 'asset' => $asset, 118 'hash' => $hash, 119 ]); 120 } 121 122 /** 123 * Serve a CSS/JS file. 124 * 125 * @param ServerRequestInterface $request 126 * 127 * @return ResponseInterface 128 */ 129 public function getAssetAction(ServerRequestInterface $request): ResponseInterface 130 { 131 // The file being requested. e.g. "css/theme.css" 132 $asset = $request->getQueryParams()['asset']; 133 134 // Do not allow requests that try to access parent folders. 135 if (Str::contains($asset, '..')) { 136 throw new AccessDeniedHttpException($asset); 137 } 138 139 // Find the file for this asset. 140 // Note that we could also generate CSS files using views/templates. 141 // e.g. $file = view(.... 142 $file = $this->resourcesFolder() . $asset; 143 144 if (!file_exists($file)) { 145 throw new NotFoundHttpException($file); 146 } 147 148 $content = file_get_contents($file); 149 $extension = pathinfo($asset, PATHINFO_EXTENSION); 150 151 $mime_types = [ 152 'css' => 'text/css', 153 'gif' => 'image/gif', 154 'js' => 'application/javascript', 155 'jpg' => 'image/jpeg', 156 'jpeg' => 'image/jpeg', 157 'json' => 'application/json', 158 'png' => 'image/png', 159 'txt' => 'text/plain', 160 ]; 161 162 $mime_type = $mime_types[$extension] ?? 'application/octet-stream'; 163 164 $headers = [ 165 'Content-Type' => $mime_type, 166 'Cache-Control' => 'max-age=31536000, public', 167 'Content-Length' => strlen($content), 168 'Expires' => Carbon::now()->addYears(10)->toRfc7231String(), 169 ]; 170 171 return response($content, StatusCodeInterface::STATUS_OK, $headers); 172 } 173} 174