PluginProbe
GiveWP – Donation Plugin and Fundraising Platform / 4.18.0
GiveWP – Donation Plugin and Fundraising Platform v4.18.0
4.18.0 4.17.0 4.16.9 4.16.8.1 4.16.8 4.16.7.2 4.16.7.1 4.16.7 4.16.6.1 4.16.6 4.16.5.1 4.16.5 4.16.4 4.16.3 4.16.2 4.16.1 4.16.0 4.15.5 4.15.4 4.15.3 4.15.2 4.15.1 4.15.0 2.3.0 2.3.1 All 257 releases
give / src / Framework / Routes / ScriptResponse.php

ScriptResponse.php in GiveWP – Donation Plugin and Fundraising Platform 4.18.0, at src/Framework/Routes/ScriptResponse.php

155 lines 4.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Give\Framework\Routes;
4
5 use Give\Framework\Support\Facades\Scripts\ScriptAsset;
6
7 /**
8 * Sends a built script with revalidation headers. The ETag is the build hash
9 * from the script's asset file, so it changes exactly when the bundle does.
10 *
11 * @since 4.17.0
12 */
13 class ScriptResponse
14 {
15 /**
16 * Seconds a browser may reuse the response before revalidating its ETag.
17 *
18 * @since 4.17.0
19 */
20 protected int $maxAge = HOUR_IN_SECONDS;
21
22 /**
23 * @since 4.17.0
24 */
25 protected string $file;
26
27 /**
28 * @since 4.17.0
29 */
30 protected string $assetFile;
31
32 /**
33 * @since 4.17.0
34 */
35 protected string $objectName = '';
36
37 /**
38 * @var callable|null
39 *
40 * @since 4.17.0
41 */
42 protected $data;
43
44 /**
45 * @since 4.17.0
46 *
47 * @param string $file Absolute path to the built script. Its version comes
48 * from the .asset.php file @wordpress/scripts writes
49 * next to it.
50 */
51 public function __construct(string $file)
52 {
53 $this->file = $file;
54 $this->assetFile = preg_replace('/\.js$/', '.asset.php', $file);
55 }
56
57 /**
58 * Expose data to the script as a global object, the way wp_localize_script()
59 * does for enqueued scripts. The callable runs at request time, after
60 * translations are loaded, so the data can carry the site's translated
61 * strings.
62 *
63 * @since 4.17.0
64 *
65 * @param callable $data Returns the array to expose; must be JSON-encodable. Receives the
66 * request data the router matched (query arguments and a path id).
67 */
68 public function localize(string $objectName, callable $data): self
69 {
70 $this->objectName = $objectName;
71 $this->data = $data;
72
73 return $this;
74 }
75
76 /**
77 * @since 4.17.0
78 *
79 * @param array $request Request data handed to the localize callable.
80 */
81 public function send(array $request = []): void
82 {
83 if (!is_readable($this->file)) {
84 status_header(404);
85 exit;
86 }
87
88 $prologue = $this->prologue($request);
89 $etag = $this->etag($prologue);
90
91 header('Content-Type: application/javascript; charset=utf-8');
92 header('X-Content-Type-Options: nosniff');
93 header("Cache-Control: public, max-age={$this->maxAge}");
94 header("ETag: {$etag}");
95
96 if ($this->matchesIfNoneMatch($etag, $_SERVER['HTTP_IF_NONE_MATCH'] ?? '')) {
97 status_header(304);
98 exit;
99 }
100
101 echo $prologue;
102 readfile($this->file);
103 exit;
104 }
105
106 /**
107 * The statement printed ahead of the file when data is localized.
108 *
109 * @since 4.17.0
110 */
111 public function prologue(array $request = []): string
112 {
113 if (!$this->data) {
114 return '';
115 }
116
117 return sprintf("var %s = %s;\n", $this->objectName, wp_json_encode(($this->data)($request)));
118 }
119
120 /**
121 * The build hash from the asset file, quoted as a strong validator. Localized
122 * data is part of the response, so its hash is part of the validator too.
123 *
124 * @since 4.17.0
125 */
126 public function etag(string $prologue = ''): string
127 {
128 $version = (string) ScriptAsset::getVersion($this->assetFile);
129
130 if ($prologue !== '') {
131 $version .= '-' . substr(md5($prologue), 0, 8);
132 }
133
134 return sprintf('"%s"', $version);
135 }
136
137 /**
138 * Weak validators and the "-gzip" suffix mod_deflate appends both name the same file.
139 *
140 * @since 4.17.0
141 */
142 public function matchesIfNoneMatch(string $etag, string $header): bool
143 {
144 if ($header === '') {
145 return false;
146 }
147
148 $candidates = array_map(static function (string $value): string {
149 return trim(str_replace(['W/', '-gzip"'], ['', '"'], $value));
150 }, explode(',', wp_unslash($header)));
151
152 return in_array($etag, $candidates, true) || in_array('*', $candidates, true);
153 }
154 }
155