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