WordPress_Security_Txt_Public::route()   B
last analyzed

Complexity

Conditions 9
Paths 6

Size

Total Lines 20
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 10
nc 6
nop 0
dl 0
loc 20
rs 8.0555
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * The public-facing functionality of the plugin.
5
 *
6
 * @link       https://github.com/austinheap/wordpress-security-txt
7
 * @since      1.0.0
8
 *
9
 * @package    WordPress_Security_Txt
10
 * @subpackage WordPress_Security_Txt/public
11
 */
12
13
/**
14
 * The public-facing functionality of the plugin.
15
 *
16
 * Defines the plugin name, version, and hooks for the public-facing stylesheet and JavaScript.
17
 *
18
 * @package    WordPress_Security_Txt
19
 * @subpackage WordPress_Security_Txt/public
20
 * @author     Austin Heap <[email protected]>
21
 */
22
class WordPress_Security_Txt_Public
23
{
24
25
    /**
26
     * The ID of this plugin.
27
     *
28
     * @since    1.0.0
29
     * @access   private
30
     * @var      string $plugin_name The ID of this plugin.
31
     */
32
    private $plugin_name;
33
34
    /**
35
     * The version of this plugin.
36
     *
37
     * @since    1.0.0
38
     * @access   private
39
     * @var      string $version The current version of this plugin.
40
     */
41
    private $version;
42
43
    /**
44
     * The plugin options.
45
     *
46
     * @since         1.0.0
47
     * @access        private
48
     * @var        array $options The plugin options.
49
     */
50
    private $options;
51
52
    /**
53
     * The plugin cache cleared flag.
54
     *
55
     * @since         1.0.0
56
     * @access        private
57
     * @var        bool $cache_cleared The plugin cache cleared flag.
58
     */
59
    private static $cache_cleared;
60
61
    /**
62
     * Initialize the class and set its properties.
63
     *
64
     * @since    1.0.0
65
     *
66
     * @param      string $plugin_name The name of the plugin.
67
     * @param      string $version     The version of this plugin.
68
     */
69
    public function __construct($plugin_name, $version)
70
    {
71
        $this->plugin_name = $plugin_name;
72
        $this->version     = $version;
73
74
        self::$cache_cleared = get_transient('WORDPRESS_SECURITY_TXT_CACHE_CLEARED');
0 ignored issues
show
Bug introduced by
The function get_transient was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

74
        self::$cache_cleared = /** @scrutinizer ignore-call */ get_transient('WORDPRESS_SECURITY_TXT_CACHE_CLEARED');
Loading history...
75
76
        if ($this->version != WORDPRESS_SECURITY_TXT_VERSION) {
77
            throw new Exception('Internal version mismatch in plugin wordpress-security-txt; it needs to be reinstalled.');
78
        }
79
    }
80
81
    /**
82
     * Hijacks requests for enabled routes
83
     *
84
     * @return   void
85
     */
86
    public function route()
87
    {
88
        if (! isset($_SERVER) || ! isset($_SERVER['REQUEST_URI']) || ! isset($_SERVER['HTTP_HOST'])) {
89
            return;
90
        }
91
92
        $this->options = WordPress_Security_Txt_Admin::get_options($this->plugin_name);
93
94
        if (! is_array($this->options) || ! isset($this->options['enable']) || ! $this->options['enable']) {
95
            return;
96
        }
97
98
        $request = ($this->is_secure() ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
99
        $site    = get_site_url();
0 ignored issues
show
Bug introduced by
The function get_site_url was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

99
        $site    = /** @scrutinizer ignore-call */ get_site_url();
Loading history...
100
101
        if (strpos($request, $site) !== 0) {
102
            return;
103
        }
104
105
        $this->apply_routes(substr($request, strlen($site)));
106
    }
107
108
    /**
109
     * Applies plugin routes to a given URI.
110
     *
111
     * @param string $uri
112
     */
113
    private function apply_routes($uri)
114
    {
115
        $routes = [
116
            '/security.txt'             => ['method' => 'redirect', 'document' => 'security.txt'],
117
            '/.well-known/security.txt' => ['method' => 'show', 'document' => 'security.txt'],
118
        ];
119
120
        if (isset($this->options['encryption']) && isset($this->options['encryption'])) {
121
            $routes = array_merge($routes, [
122
                '/gpg.txt'             => ['method' => 'redirect', 'document' => 'gpg.txt'],
123
                '/.well-known/gpg.txt' => ['method' => 'show', 'document' => 'gpg.txt'],
124
            ]);
125
        }
126
127
        if (isset($routes[$uri])) {
128
            if ($routes[$uri]['method'] == 'redirect') {
129
                $this->redirect($routes[$uri]['document']);
130
            } elseif ($routes[$uri]['method'] == 'show') {
131
                $this->show($routes[$uri]['document']);
132
            }
133
        }
134
    }
135
136
    /**
137
     * Determines if current request is made via HTTPS.
138
     *
139
     * @since 1.0.0
140
     * @return bool
141
     */
142
    private function is_secure()
143
    {
144
        return (! empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || $_SERVER['SERVER_PORT'] == 443;
145
    }
146
147
    /**
148
     * Redirects a request to the correct location.
149
     *
150
     * @since 1.0.0
151
     *
152
     * @param string $document
153
     *
154
     * @return void
155
     */
156
    private function redirect($document)
157
    {
158
        header('Location: ' . get_site_url() . '/.well-known/' . $document);
0 ignored issues
show
Bug introduced by
The function get_site_url was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

158
        header('Location: ' . /** @scrutinizer ignore-call */ get_site_url() . '/.well-known/' . $document);
Loading history...
159
160
        exit;
161
    }
162
163
    /**
164
     * Gets the cache file for the plugin.
165
     *
166
     * @since 1.0.0
167
     *
168
     * @return string
169
     */
170
    public static function cache_file()
171
    {
172
        return get_temp_dir() . DIRECTORY_SEPARATOR . 'wordpress-security-txt-cache.txt';
0 ignored issues
show
Bug introduced by
The function get_temp_dir was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

172
        return /** @scrutinizer ignore-call */ get_temp_dir() . DIRECTORY_SEPARATOR . 'wordpress-security-txt-cache.txt';
Loading history...
173
    }
174
175
    /**
176
     * Removes the cache file for the plugin.
177
     *
178
     * @since 1.0.0
179
     *
180
     * @return bool
181
     */
182
    public static function cache_clear()
183
    {
184
        $cache_file = self::cache_file();
185
186
        if (is_file($cache_file)) {
187
            $result = unlink($cache_file);
188
            set_transient('WORDPRESS_SECURITY_TXT_CACHE_CLEARED', $result, 5);
0 ignored issues
show
Bug introduced by
The function set_transient was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

188
            /** @scrutinizer ignore-call */ 
189
            set_transient('WORDPRESS_SECURITY_TXT_CACHE_CLEARED', $result, 5);
Loading history...
189
            self::$cache_cleared = get_transient('WORDPRESS_SECURITY_TXT_CACHE_CLEARED');
0 ignored issues
show
Bug introduced by
The function get_transient was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

189
            self::$cache_cleared = /** @scrutinizer ignore-call */ get_transient('WORDPRESS_SECURITY_TXT_CACHE_CLEARED');
Loading history...
190
        }
191
192
        return false;
193
    }
194
195
    /**
196
     * Indicates if the cache was cleared during the current request.
197
     *
198
     * @since 1.0.0
199
     *
200
     * @return bool
201
     */
202
    public static function cache_cleared($reset = false)
203
    {
204
        $result = is_null(self::$cache_cleared) ? false : self::$cache_cleared;
0 ignored issues
show
introduced by
The condition is_null(self::cache_cleared) is always false.
Loading history...
205
206
        if ($reset) {
207
            delete_transient('WORDPRESS_SECURITY_TXT_CACHE_CLEARED');
0 ignored issues
show
Bug introduced by
The function delete_transient was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

207
            /** @scrutinizer ignore-call */ 
208
            delete_transient('WORDPRESS_SECURITY_TXT_CACHE_CLEARED');
Loading history...
208
        }
209
210
        return $result;
211
    }
212
213
    /**
214
     * Displays a document, assuming it can be rendered correctly.
215
     *
216
     * @since 1.0.0
217
     *
218
     * @param string $document
219
     *
220
     * @return void
221
     */
222
    private function show($document)
223
    {
224
        if ($document == 'security.txt') {
225
            $output = $this->render_security_txt();
226
        } elseif ($document == 'gpg.txt') {
227
            $output = $this->render_gpg_txt();
228
        }
229
230
        if (empty($output)) {
231
            return;
232
        }
233
234
        header('Content-Length: ' . strlen($output));
235
        header('Content-Type: text/plain');
236
237
        print $output;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $output does not seem to be defined for all execution paths leading up to this point.
Loading history...
238
239
        exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
240
    }
241
242
    /**
243
     * Renders gpg.txt for display
244
     *
245
     * @return string
246
     */
247
    private function render_gpg_txt()
248
    {
249
        return $this->options['encryption'];
250
    }
251
252
    /**
253
     * Renders security.txt for display
254
     *
255
     * @return string
256
     */
257
    private function render_security_txt()
258
    {
259
        $output = $this->get_security_txt_cache();
260
261
        if (empty($output)) {
262
            WordPress_Security_Txt::import_lib();
263
264
            $writer = (new \AustinHeap\Security\Txt\Writer)->setDebug(isset($this->options['credits']) ? $this->options['credits'] : false)
265
                                                           ->addContact($this->options['contact']);
266
267
            if (! empty($this->options['encryption'])) {
268
                $writer->setEncryption(get_site_url() . '/.well-known/gpg.txt');
0 ignored issues
show
Bug introduced by
The function get_site_url was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

268
                $writer->setEncryption(/** @scrutinizer ignore-call */ get_site_url() . '/.well-known/gpg.txt');
Loading history...
269
            }
270
271
            if (! empty($this->options['disclosure']) && $this->options['disclosure'] != 'default') {
272
                $writer->setDisclosure($this->options['disclosure']);
273
            }
274
275
            if (! empty($this->options['acknowledgement'])) {
276
                $writer->setAcknowledgement($this->options['acknowledgement']);
277
            }
278
279
            $output = $writer->execute()->getText();
280
        }
281
282
        $this->write_security_txt_cache($output);
283
284
        return $output;
285
    }
286
287
    /**
288
     * Write to the security.txt cache.
289
     *
290
     * @param string $data
291
     * @return void
292
     */
293
    private function write_security_txt_cache($data)
294
    {
295
        if (isset($this->options['cache']) && $this->options['cache']) {
296
            file_put_contents(self::cache_file(), $data);
297
            WordPress_Security_Txt::event('cache');
298
        }
299
    }
300
301
    /**
302
     * Get the security.txt cache.
303
     *
304
     * @return mixed
305
     */
306
    private function get_security_txt_cache()
307
    {
308
        $data = null;
309
310
        if (isset($this->options['cache']) && $this->options['cache']) {
311
            $cache_file = self::cache_file();
312
313
            if (is_file($cache_file) && filemtime($cache_file) < time() - 86400) {
314
                self::cache_clear();
315
            }
316
317
            if (is_readable($cache_file)) {
318
                $data = file_get_contents($cache_file);
319
            }
320
        }
321
322
        return $data;
323
    }
324
}
325