xref: /webtrees/app/Module/ModuleCustomTrait.php (revision 0f97530b568da7b969b729d6803c91fba4652cde)
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/jpg',
156            'jpeg' => 'image/jpg',
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