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 / Router.php

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

262 lines 7.5 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\Exceptions\Primitives\InvalidArgumentException;
6 use Give\Helpers\Language;
7 use WP;
8
9 use function is_callable;
10 use function str_contains;
11
12 /**
13 * @since 4.17.0 Add script routes served from a plugin-controlled URL
14 * @since 3.0.0
15 */
16 class Router
17 {
18 /**
19 * Base path segment for pretty script URLs. Fixed on purpose: these URLs are
20 * pasted into third-party sites, so they must not follow any setting.
21 *
22 * @since 4.17.0
23 */
24 protected string $scriptBase = 'give';
25
26 /**
27 * @since 3.0.0
28 * @param string $uri
29 * @param string|callable $action
30 * @param string $method
31 *
32 * @return void
33 */
34 public function get(string $uri, $action, $method = '__invoke')
35 {
36 $this->addRoute('GET', $method, $uri, $action);
37 }
38
39 /**
40 * @since 3.0.0
41 * @param string $uri
42 * @param string|callable $action
43 * @param string $method
44 *
45 * @return void
46 */
47 public function post(string $uri, $action, $method = '__invoke')
48 {
49 $this->addRoute('POST', $method, $uri, $action);
50 }
51
52 /**
53 * Serve a built script from a URL the plugin controls, so the file can move
54 * without breaking URLs already pasted elsewhere. Matching happens on
55 * parse_request against the path WordPress already resolved, so no rewrite
56 * rule is registered and nothing needs flushing. See scriptUrl() for the
57 * URL shape per permalink setting.
58 *
59 * @since 4.17.0
60 *
61 * @param string $uri Path below the base, e.g. "embed/donation-form/script.js"
62 * @param string $file Absolute path to the built script
63 *
64 * @return ScriptResponse The response, so callers can chain localize()
65 */
66 public function script(string $uri, string $file): ScriptResponse
67 {
68 $response = new ScriptResponse($file);
69
70 add_action('parse_request', function (WP $wp) use ($uri, $response) {
71 $request = $this->scriptRequest($wp, $uri);
72
73 if ($request === null) {
74 return;
75 }
76
77 $response->send($request);
78 });
79
80 return $response;
81 }
82
83 /**
84 * Pretty permalinks: /give/{uri}
85 * Index permalinks: /index.php/give/{uri}
86 * Plain permalinks: /?givewp-route={uri}
87 *
88 * @since 4.17.0
89 *
90 * @param array $args Query arguments appended to the URL; the script's localize callable
91 * receives them at request time.
92 */
93 public function scriptUrl(string $uri, array $args = []): string
94 {
95 global $wp_rewrite;
96
97 if (!$wp_rewrite->using_permalinks()) {
98 $url = $this->url($uri);
99 } else {
100 $prefix = $wp_rewrite->using_index_permalinks() ? $wp_rewrite->index . '/' : '';
101 $url = home_url("/{$prefix}{$this->scriptBase}/{$uri}");
102 }
103
104 if (!$args) {
105 return $url;
106 }
107
108 // Appended by hand: add_query_arg() would re-encode the givewp-route value's slashes.
109 return $url . (strpos($url, '?') === false ? '?' : '&') . http_build_query($args);
110 }
111
112 /**
113 * @since 4.17.0
114 */
115 public function isScriptRequested(WP $wp, string $uri): bool
116 {
117 return $this->scriptRequest($wp, $uri) !== null;
118 }
119
120 /**
121 * The request data for a script route when the current request is for it, null otherwise.
122 * The data is the query string run through give_clean(), minus givewp-route itself, so a
123 * `?form-id=42` argument arrives as `['form-id' => '42']`. Matching covers the pretty path and
124 * the givewp-route query var, each with an optional numeric segment before the file name
125 * (embed/donation-form/42/script.js), which comes back as `id`. The segment exists for caches
126 * that drop query strings from their key; a query argument is the primary way to pass data
127 * to a script route.
128 *
129 * @since 4.17.0
130 */
131 public function scriptRequest(WP $wp, string $uri): ?array
132 {
133 $directory = dirname($uri);
134 $directory = $directory === '.' ? '' : preg_quote($directory, '#') . '/';
135 $pattern = $directory . '(?:(\d+)/)?' . preg_quote(basename($uri), '#');
136
137 $candidates = [
138 '#^' . preg_quote($this->scriptBase, '#') . '/' . $pattern . '$#' => (string)$wp->request,
139 '#^' . $pattern . '$#' => isset($_GET['givewp-route']) ? (string)$_GET['givewp-route'] : '',
140 ];
141
142 foreach ($candidates as $regex => $subject) {
143 if ($subject === '' || !preg_match($regex, $subject, $matches)) {
144 continue;
145 }
146
147 $request = $this->getDataFromGetRequest();
148 unset($request['givewp-route']);
149
150 if (!empty($matches[1])) {
151 $request['id'] = (int)$matches[1];
152 }
153
154 return $request;
155 }
156
157 return null;
158 }
159
160 /**
161 * @since 3.0.0
162 */
163 protected function isRouteValid(string $route): bool
164 {
165 return isset($_GET['givewp-route']) && $_GET['givewp-route'] === $route;
166 }
167
168 /**
169 * @since 3.0.0
170 */
171 protected function getRequestDataByType(string $type): array
172 {
173 if ($type === 'POST'){
174 return $this->getDataFromPostRequest();
175 }
176
177 return $this->getDataFromGetRequest();
178 }
179
180 /**
181 * @since 3.0.0
182 */
183 protected function getDataFromPostRequest(): array
184 {
185 $requestData = [];
186
187 if (!isset($_SERVER['CONTENT_TYPE'])) {
188 return $requestData;
189 }
190
191 if (str_contains($_SERVER['CONTENT_TYPE'], "application/json")) {
192 $requestData = file_get_contents('php://input');
193 $requestData = json_decode($requestData, true);
194 $requestData = give_clean($requestData);
195 } else {
196 $requestData = array_merge(
197 give_clean($_REQUEST),
198 give_clean($_FILES)
199 );
200 }
201
202 return $requestData;
203 }
204
205 /**
206 * @since 3.0.0
207 */
208 protected function getDataFromGetRequest(): array
209 {
210 return give_clean($_GET);
211 }
212
213 /**
214 * @since 3.22.0 Add locale support
215 * @since 3.0.0
216 *
217 * @param string $type
218 * @param string $method
219 * @param string $uri
220 * @param $action
221 *
222 * @return void
223 */
224 protected function addRoute(string $type, string $method, string $uri, $action)
225 {
226 add_action('template_redirect', function () use ($type, $method, $uri, $action) {
227 if (!$this->isRouteValid($uri)) {
228 // fail silently for use with template_redirect
229 return;
230 }
231
232 $request = $this->getRequestDataByType($type);
233 $request['locale'] = ! empty($request['locale']) ? $request['locale'] : Language::getLocale();
234
235 if (is_callable($action)) {
236 return $action($request);
237 }
238
239 if (!method_exists($action, $method)) {
240 throw new InvalidArgumentException("The method $method does not exist on $action");
241 }
242
243 return give($action)->$method($request);
244 });
245 }
246
247 /**
248 * @since 4.3.0 Use trailingslashit() method to prevent errors on websites installed in subdirectories
249 * @since 3.0.0
250 */
251 public function url(string $uri, array $args = []): string
252 {
253 return add_query_arg(
254 array_merge(
255 ['givewp-route' => $uri],
256 $args
257 ),
258 trailingslashit(home_url())
259 );
260 }
261 }
262