Passed
Pull Request — master (#3661)
by Rico
06:28
created

ModuleCustomTrait::extractVersion()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 3
rs 10
1
<?php
2
3
/**
4
 * webtrees: online genealogy
5
 * Copyright (C) 2020 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
18
declare(strict_types=1);
19
20
namespace Fisharebest\Webtrees\Module;
21
22
use Fig\Http\Message\StatusCodeInterface;
23
use Fisharebest\Webtrees\Exceptions\HttpAccessDeniedException;
24
use Fisharebest\Webtrees\Exceptions\HttpNotFoundException;
25
use Fisharebest\Webtrees\Mime;
26
use Fisharebest\Webtrees\Registry;
27
use GuzzleHttp\Client;
28
use GuzzleHttp\Exception\RequestException;
29
use Psr\Http\Message\ResponseInterface;
30
use Psr\Http\Message\ServerRequestInterface;
31
32
use function str_contains;
33
use function strlen;
34
use function strtolower;
35
36
/**
37
 * Trait ModuleCustomTrait - default implementation of ModuleCustomInterface
38
 */
39
trait ModuleCustomTrait
40
{
41
    /**
42
     * The person or organisation who created this module.
43
     *
44
     * @return string
45
     */
46
    public function customModuleAuthorName(): string
47
    {
48
        return '';
49
    }
50
51
    /**
52
     * The version of this module.
53
     *
54
     * @return string  e.g. '1.2.3'
55
     */
56
    public function customModuleVersion(): string
57
    {
58
        return '';
59
    }
60
61
    /**
62
     * A URL that will provide the latest version of this module.
63
     *
64
     * @return string
65
     */
66
    public function customModuleLatestVersionUrl(): string
67
    {
68
        return '';
69
    }
70
71
    /**
72
     * Fetch the version information from the given content string.
73
     * 
74
     * @param string $content
75
     *
76
     * @return string
77
     */
78
    protected function extractVersion(string $content): string
79
    {
80
        return $content;
81
    }
82
83
    /**
84
     * Fetch the latest version of this module.
85
     *
86
     * @return string
87
     */
88
    public function customModuleLatestVersion(): string
89
    {
90
        // No update URL provided.
91
        if ($this->customModuleLatestVersionUrl() === '') {
92
            return $this->customModuleVersion();
93
        }
94
95
        return Registry::cache()->file()->remember($this->name() . '-latest-version', function () {
0 ignored issues
show
Bug introduced by
It seems like name() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

95
        return Registry::cache()->file()->remember($this->/** @scrutinizer ignore-call */ name() . '-latest-version', function () {
Loading history...
96
            try {
97
                $client = new Client([
98
                    'timeout' => 3,
99
                ]);
100
101
                $response = $client->get($this->customModuleLatestVersionUrl());
102
103
                if ($response->getStatusCode() === StatusCodeInterface::STATUS_OK) {
104
                    $version = $this->extractVersion($response->getBody()->getContents());
105
106
                    // Does the response look like a version?
107
                    if (preg_match('/^\d+\.\d+\.\d+/', $version)) {
108
                        return $version;
109
                    }
110
                }
111
            } catch (RequestException $ex) {
112
                // Can't connect to the server?
113
            }
114
115
            return $this->customModuleVersion();
116
        }, 86400);
117
    }
118
119
    /**
120
     * Where to get support for this module.  Perhaps a github repository?
121
     *
122
     * @return string
123
     */
124
    public function customModuleSupportUrl(): string
125
    {
126
        return '';
127
    }
128
129
    /**
130
     * Additional/updated translations.
131
     *
132
     * @param string $language
133
     *
134
     * @return array<string,string>
135
     */
136
    public function customTranslations(string $language): array
0 ignored issues
show
Unused Code introduced by
The parameter $language is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

136
    public function customTranslations(/** @scrutinizer ignore-unused */ string $language): array

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
137
    {
138
        return [];
139
    }
140
141
    /**
142
     * Create a URL for an asset.
143
     *
144
     * @param string $asset e.g. "css/theme.css" or "img/banner.png"
145
     *
146
     * @return string
147
     */
148
    public function assetUrl(string $asset): string
149
    {
150
        $file = $this->resourcesFolder() . $asset;
0 ignored issues
show
Bug introduced by
It seems like resourcesFolder() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

150
        $file = $this->/** @scrutinizer ignore-call */ resourcesFolder() . $asset;
Loading history...
151
152
        // Add the file's modification time to the URL, so we can set long expiry cache headers.
153
        $hash = filemtime($file);
154
155
        return route('module', [
156
            'module' => $this->name(),
157
            'action' => 'Asset',
158
            'asset'  => $asset,
159
            'hash'   => $hash,
160
        ]);
161
    }
162
163
    /**
164
     * Serve a CSS/JS file.
165
     *
166
     * @param ServerRequestInterface $request
167
     *
168
     * @return ResponseInterface
169
     */
170
    public function getAssetAction(ServerRequestInterface $request): ResponseInterface
171
    {
172
        // The file being requested.  e.g. "css/theme.css"
173
        $asset = $request->getQueryParams()['asset'];
174
175
        // Do not allow requests that try to access parent folders.
176
        if (str_contains($asset, '..')) {
0 ignored issues
show
Deprecated Code introduced by
The function str_contains() has been deprecated: Str::contains() should be used directly instead. Will be removed in Laravel 6.0. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

176
        if (/** @scrutinizer ignore-deprecated */ str_contains($asset, '..')) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
177
            throw new HttpAccessDeniedException($asset);
178
        }
179
180
        // Find the file for this asset.
181
        // Note that we could also generate CSS files using views/templates.
182
        // e.g. $file = view(....)
183
        $file = $this->resourcesFolder() . $asset;
184
185
        if (!file_exists($file)) {
186
            throw new HttpNotFoundException(e($file));
187
        }
188
189
        $content   = file_get_contents($file);
190
        $extension = strtolower(pathinfo($asset, PATHINFO_EXTENSION));
191
        $mime_type = Mime::TYPES[$extension] ?? Mime::DEFAULT_TYPE;
192
193
        return response($content, StatusCodeInterface::STATUS_OK, [
194
            'Cache-Control'  => 'public,max-age=31536000',
195
            'Content-Length' => (string) strlen($content),
196
            'Content-Type'   => $mime_type,
197
        ]);
198
    }
199
}
200