-
Notifications
You must be signed in to change notification settings - Fork 2
/
DM_URL.php
385 lines (332 loc) · 13.9 KB
/
DM_URL.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
<?php
defined( 'ABSPATH' ) or die();
class DM_URL {
/**
* Constructor.
*/
public function __construct() {
/**
* Prevent accidental URL mapping on requests which are not GET requests for the admin area. For example; a POST
* request will include the postback for saving a post.
*
* A good example is the Yoast SEO plugin when detecting the internal links as part of the SEO score. This hooks
* on to the `save_post` action and then uses `home_url()` and `get_permalink()` as part of the process. If the
* URLs are mapped / unmapped here then it can cause the functionality to fail.
*
* @link https://github.com/Yoast/wordpress-seo/blob/11.6/admin/links/class-link-content-processor.php#L43-L48 Yoast SEO code reference.
*/
if ( is_admin() && ! wp_doing_ajax() && ! empty( $_SERVER['REQUEST_METHOD'] ) && 'GET' !== $_SERVER['REQUEST_METHOD'] ) {
return;
}
/**
* Disable all the URL mapping if viewing through Customizer. This is to
* ensure maximum functionality by retaining the Admin URL.
*/
if ( ! empty( $_GET['customize_changeset_uuid'] ) ) {
return;
}
/**
* Dark Matter will disengage if the website is no longer public or is
* archived or deleted.
*/
$blog = get_site();
if ( (int) $blog->public < 0 || $blog->archived !== '0' || $blog->deleted !== '0' ) {
return;
}
/**
* This is the earliest possible action we can start to prepare the
* setup for the mapping logic. This is because the WP->parse_request()
* method utilises home_url() which means for requests permitted on both
* the unmapped and mapped domain - like REST API and XMLRPC - will not
* be properly detected for the rewrite rules.
*/
add_action( 'muplugins_loaded', array( $this, 'prepare' ), -10 );
/**
* Jetpack compatibility. This filter ensures that Jetpack gets the
* correct domain for the home URL.
*/
add_action( 'jetpack_sync_home_url', array( $this, 'map' ), 10, 1 );
}
/**
* Handle the mapping of Admin URLs for the public-serving side when viewed
* on a primary domain. This is specifically to ensure compatibility with
* plugins which utilise the `admin-ajax.php` and `admin-post.php` URLs for
* functionality, such as form postbacks and other such functionality on the
* public-serving side.
*
* @param string $url The complete admin area URL including scheme and path.
* @param string $path Path relative to the admin area URL. Blank string if no path is specified.
* @param int|null $blog_id Site ID, or null for the current site.
* @return string URL which is mapped if appropriate, unchanged otherwise.
*/
public function adminurl( $url = '', $path = '', $blog_id = 0 ) {
$filename = basename( $path );
/**
* This will cover a number of requests which are only `/wp-admin/` with
* no file in the $path.
*/
if ( empty( $filename ) ) {
return $url;
}
$valid_paths = array(
'admin-ajax.php', 'admin-post.php'
);
if ( in_array( $filename, $valid_paths ) ) {
return $this->map( $url, $blog_id );
}
return $url;
}
/**
* Checks to ensure that "mapped" domains are considered internal to WordPress and not external.
*
* @param bool $external Whether HTTP request is external or not.
* @param string $host Host name of the requested URL.
* @return bool False if the URL is "mapped" domain. The provided $external value otherwise.
*/
public function is_external( $external = false, $host = '' ) {
/**
* WordPress defaults to "false". If it is not this value, then this means another hook has modified it. We skip
* this to ensure that original logic is not overridden by Dark Matter.
*/
if ( $external ) {
return $external;
}
/**
* Attempt to find the domain in Dark Matter. If the domain is found, then tell WordPress it is an internal
* domain.
*/
$db = DarkMatter_Domains::instance();
$domain = $db->find( $host );
if ( is_a( $domain, 'DM_Domain' ) ) {
return true;
}
return $external;
}
/**
* Determines if the requested domain is mapped using the DOMAIN_MAPPING
* constant from sunrise.php.
*
* @return boolean True if the domain is the Primary domain. False if the Admin domain.
*/
private function is_mapped() {
return ( defined( 'DOMAIN_MAPPING' ) && DOMAIN_MAPPING );
}
/**
* Map the primary domain on the passed in value if it contains the unmapped
* URL and the Site has a primary domain.
*
* @param mixed $value Potentially a value containing the site's unmapped URL.
* @param integer $value Site (Blog) ID for the URL which is being mapped.
* @return string If unmapped URL is found, then returns the primary URL. Untouched otherwise.
*/
public function map( $value = '', $blog_id = 0 ) {
/**
* Ensure that we are working with a string.
*/
if ( ! is_string( $value ) ) {
return $value;
}
/**
* Retrieve the current blog.
*/
$blog = get_site( absint( $blog_id ) );
$primary = DarkMatter_Primary::instance()->get( $blog->blog_id );
$unmapped = untrailingslashit( $blog->domain . $blog->path );
/**
* If there is no primary domain or the unmapped version cannot be found
* then we return the value as-is.
*/
if ( empty( $primary ) || false === stripos( $value, $unmapped ) ) {
return $value;
}
$domain = 'http' . ( $primary->is_https ? 's' : '' ) . '://' . $primary->domain;
return preg_replace( "#https?://{$unmapped}#", $domain, $value );
}
/**
* Setup the actions to handle the URL mappings.
*
* @return void
*/
public function prepare() {
add_filter( 'the_content', array( $this, 'map' ), 50, 1 );
add_filter( 'http_request_host_is_external', array( $this, 'is_external' ), 10, 2 );
/**
* We only wish to affect `the_content` for Previews and nothing else.
*/
if ( ! empty( $_GET['preview'] ) || ! empty( $_GET['p'] ) ) {
return;
}
/**
* Treat the Admin area slightly differently. This is because we do not
* wish to modify all URLs to the mapped primary domain as this will
* affect database and cache updates to ensure compatibility if the
* domain mapping is changed or removed.
*/
if (
is_admin()
||
(
! $this->is_mapped()
&&
false !== strpos( $_SERVER['REQUEST_URI'], rest_get_url_prefix() )
)
) {
add_action( 'admin_init', array( $this, 'prepare_admin' ) );
return;
}
/**
* Every thing here is designed to ensure all URLs throughout WordPress
* is consistent. This is the public serving / theme powered code.
*/
add_filter( 'admin_url', array( $this, 'adminurl' ), -10, 3 );
add_filter( 'home_url', array( $this, 'siteurl' ), -10, 4 );
add_filter( 'site_url', array( $this, 'siteurl' ), -10, 4 );
add_filter( 'content_url', array( $this, 'map' ), -10, 1 );
add_filter( 'get_shortlink', array( $this, 'map' ), -10, 4 );
add_filter( 'script_loader_tag', array( $this, 'map' ), -10, 4 );
add_filter( 'style_loader_tag', array( $this, 'map' ), -10, 4 );
add_filter( 'upload_dir', array( $this, 'upload' ), 10, 1 );
}
/**
* Some filters need to be handled later in the process when running in the
* admin area. This handles the preparation work for mapping URLs for Admni
* only requests.
*
* @return void
*/
function prepare_admin() {
/**
* This is to prevent the Classic Editor's AJAX action for inserting a
* link from putting the mapped domain in to the database. However, we
* cannot rely on `is_admin()` as this is always true for calls to the
* old AJAX. Therefore we check the referer to ensure it's the admin
* side rather than the front-end.
*/
if ( wp_doing_ajax()
&&
false !== stripos( wp_get_referer(), '/wp-admin/' )
&&
( empty( $_POST['action'] ) || ! in_array( $_POST['action'], array( 'query-attachments', 'sample-permalink', 'upload-attachment' ) ) )
) {
return;
}
add_filter( 'home_url', array( $this, 'siteurl' ), -10, 4 );
/**
* The Preview link in the metabox of Post Publish cannot be handled by the home_url hook. This is because it
* uses get_permalink() to retrieve the URL before appending the "preview=true" query string parameter.
*
* @link https://github.com/WordPress/WordPress/blob/5.2.2/wp-admin/includes/meta-boxes.php#L57 Preview Metabox call to get Preview URL.
* @link https://github.com/WordPress/WordPress/blob/5.2.2/wp-includes/link-template.php#L1311-L1312 Query string parameter "preview=true" being added to the URL.
*/
add_filter( 'preview_post_link', array( $this, 'unmap' ), 10, 1 );
}
/**
* Handle Home URL and Site URL mappings when and where appropriate.
*
* @param string $url The complete site URL including scheme and path.
* @param string $path Path relative to the site URL. Blank string if no path is specified.
* @param string $scheme Scheme to give $url. Currently 'http', 'https', 'login', 'login_post', 'admin', 'relative', 'rest', 'rpc', or null.
* @param integer $blog_id Site ID, or null for the current site.
* @return string Mapped URL unless a specific scheme which should be ignored.
*/
public function siteurl( $url = '', $path = '', $scheme = null, $blog_id = 0 ) {
global $wp_customize;
/**
* Ensure we are not in Customizer.
*/
if ( is_a( $wp_customize, 'WP_Customize_Manager' ) ) {
return $url;
}
$valid_schemes = array( 'http', 'https' );
if ( ! is_admin() ) {
$valid_schemes[] = 'json';
$valid_schemes[] = 'rest';
}
if ( apply_filters( 'darkmatter_allow_logins', false ) ) {
$valid_schemes[] = 'login';
}
if ( null === $scheme || in_array( $scheme, $valid_schemes ) ) {
/**
* Determine if there is any query string paramters present.
*/
$query = wp_parse_url( $url, PHP_URL_QUERY );
if ( ! empty( $query ) ) {
/**
* Retrieve and construct an array of the various query strings.
*/
parse_str( $query, $parts );
/**
* Determine if the URL we are attempting to map is a Preview
* URL, which is to remain on the Admin domain.
*/
if ( ! empty( $parts['p'] ) || ! empty( $parts['page_id'] ) || ! empty( $parts['preview'] ) ) {
return $url;
}
}
/**
* We pass in the potential URL along with the Blog ID. This covers
* when the `get_home_url()` and `home_url()` are called from within
* a `switch_blog()` context.
*/
return $this->map( $url, $blog_id );
}
return $url;
}
/**
* Map the primary domain on the passed in value if it contains the unmapped
* URL and the Site has a primary domain.
*
* @param mixed $value Potentially a value containing the site's unmapped URL.
* @return mixed If unmapped URL is found, then returns the primary URL. Untouched otherwise.
*/
public function unmap( $value = '' ) {
/**
* Ensure that we are working with a string.
*/
if ( ! is_string( $value ) ) {
return $value;
}
/**
* Retrieve the current blog.
*/
$blog = get_site();
$primary = DarkMatter_Primary::instance()->get();
/**
* If there is no primary domain or the primary domain cannot be found
* then we return the value as-is.
*/
if ( empty( $primary ) || false === stripos( $value, $primary->domain ) ) {
return $value;
}
$unmapped = 'http' . ( $primary->is_https ? 's' : '' ) . '://' . untrailingslashit( $blog->domain . $blog->path );
return preg_replace( "#https?://{$primary->domain}#", $unmapped, $value );
}
/**
* Handle the Uploads URL mappings when and where appropriate.
*
* @param array $uploads Array of upload directory data with keys of 'path', 'url', 'subdir, 'basedir', 'baseurl', and 'error'.
* @return array Array of upload directory data with the values with URLs mapped as appropriate.
*/
public function upload( $uploads ) {
if ( ! empty( $uploads['url'] ) ) {
$uploads['url'] = $this->map( $uploads['url'] );
}
if ( ! empty( $uploads['baseurl'] ) ) {
$uploads['baseurl'] = $this->map( $uploads['baseurl'] );
}
return $uploads;
}
/**
* Return the Singleton Instance of the class.
*
* @return void
*/
public static function instance() {
static $instance = false;
if ( ! $instance ) {
$instance = new self();
}
return $instance;
}
}
DM_URL::instance();