PluginProbe
s2Member – Excellent for All Kinds of Memberships, Content Restriction Paywalls & Member Access Subscriptions / trunk
s2Member – Excellent for All Kinds of Memberships, Content Restriction Paywalls & Member Access Subscriptions vtrunk
260909 260829 260814 260805 110710 110731 110812 110815 110912 110913 110915 110926 110927 111002 111003 111011 111017 111029 111105 111206 111216 111220 120213 120219 120301 All 187 releases
s2member / src / includes / classes / utils-urls.inc.php

utils-urls.inc.php in s2Member – Excellent for All Kinds of Memberships, Content Restriction Paywalls & Member Access Subscriptions trunk, at src/includes/classes/utils-urls.inc.php

504 lines 21.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // @codingStandardsIgnoreFile
3 /**
4 * URL utilities.
5 *
6 * Copyright: © 2009-2011
7 * {@link http://websharks-inc.com/ WebSharks, Inc.}
8 * (coded in the USA)
9 *
10 * Released under the terms of the GNU General Public License.
11 * You should have received a copy of the GNU General Public License,
12 * along with this software. In the main directory, see: /licensing/
13 * If not, see: {@link http://www.gnu.org/licenses/}.
14 *
15 * @package s2Member\Utilities
16 * @since 3.5
17 */
18 if(!defined('WPINC')) // MUST have WordPress.
19 exit('Do not access this file directly.');
20
21 if(!class_exists('c_ws_plugin__s2member_utils_urls'))
22 {
23 /**
24 * URL utilities.
25 *
26 * @package s2Member\Utilities
27 * @since 3.5
28 */
29 class c_ws_plugin__s2member_utils_urls
30 {
31 /**
32 * Builds a WordPress signup URL to `/wp-signup.php`.
33 *
34 * @package s2Member\Utilities
35 * @since 3.5
36 *
37 * @return string Full URL to `/wp-signup.php`.
38 */
39 public static function wp_signup_url()
40 {
41 return apply_filters('wp_signup_location', site_url('/wp-signup.php'));
42 }
43
44 /**
45 * Builds a WordPress registration URL to `/wp-login.php?action=register`.
46 *
47 * @package s2Member\Utilities
48 * @since 3.5
49 *
50 * @return string Full URL to `/wp-login.php?action=register`.
51 */
52 public static function wp_register_url()
53 {
54 return apply_filters('wp_register_location', add_query_arg('action', urlencode('register'), wp_login_url()), get_defined_vars());
55 }
56
57 /**
58 * Builds a BuddyPress registration URL to `/register`.
59 *
60 * @package s2Member\Utilities
61 * @since 111009
62 *
63 * @return str|bool Full URL to `/register`, if BuddyPress is installed; else false.
64 */
65 public static function bp_register_url()
66 {
67 if(c_ws_plugin__s2member_utils_conds::bp_is_installed())
68 return home_url(function_exists('bp_get_signup_slug') ? bp_get_signup_slug().'/' : BP_REGISTER_SLUG.'/');
69
70 return false;
71 }
72
73 /**
74 * Filters content redirection status *(uses 302s for browsers)*.
75 *
76 * @package s2Member\Utilities
77 * @since 3.5
78 *
79 * @attaches-to ``add_filter('ws_plugin__s2member_content_redirect_status');``
80 *
81 * @param int|string $status A numeric redirection status code.
82 * @return int|str A numeric status redirection code, possibly modified to a value of `302`.
83 *
84 * @see https://en.wikipedia.org/wiki/Web_browser_engine
85 */
86 public static function redirect_browsers_using_302_status($status = FALSE)
87 {
88 $engines = 'msie|trident|gecko|webkit|presto|konqueror|playstation';
89
90 if((int)$status === 301 && !empty($_SERVER['HTTP_USER_AGENT']))
91 if(($is_browser = preg_match('/('.$engines.')[\/ ]([0-9\.]+)/i', $_SERVER['HTTP_USER_AGENT'])))
92 $status = 302; // Use 302 for browser engines.
93
94 return $status;
95 }
96
97 /**
98 * Encodes all types of amperands to `amp;`, for use in XHTML code.
99 *
100 * Note however, this is usually NOT necessary. Just use WordPress ``esc_html()`` or ``esc_attr()``.
101 *
102 * @package s2Member\Utilities
103 * @since 111106
104 *
105 * @param string $url_uri_query A full URL, a partial URI, or just the query string.
106 * @return string A full URL, a partial URI, or just the query string; after having been encoded by this routine.
107 */
108 public static function e_amps($url_uri_query = FALSE)
109 {
110 return str_replace('&', '&amp;', c_ws_plugin__s2member_utils_urls::n_amps((string)$url_uri_query));
111 }
112
113 /**
114 * Normalizes amperands to `&` when working with URLs, URIs, and/or query strings.
115 *
116 * @package s2Member\Utilities
117 * @since 111106
118 *
119 * @param string $url_uri_query A full URL, a partial URI, or just the query string.
120 * @return string A full URL, a partial URI, or just the query string; after having been normalized by this routine.
121 */
122 public static function n_amps($url_uri_query = FALSE)
123 {
124 $amps = implode('|', array_keys(c_ws_plugin__s2member_utils_strings::$ampersand_entities));
125
126 return preg_replace('/(?:'.$amps.')/', '&', (string)$url_uri_query);
127 }
128
129 /**
130 * Parses out a full valid URI, from either a full URL, or a partial URI.
131 *
132 * Uses {@link s2Member\Utilities\c_ws_plugin__s2member_utils_urls::parse_url()}.
133 *
134 * @package s2Member\Utilities
135 * @since 3.5
136 *
137 * @param string $url_uri Either a full URL, or a partial URI.
138 * @return string A valid URI, starting with `/` on success, else an empty string.
139 */
140 public static function parse_uri($url_uri = FALSE)
141 {
142 if(is_string($url_uri) && is_array($parse = c_ws_plugin__s2member_utils_urls::parse_url($url_uri)))
143 {
144 $parse['path'] = !empty($parse['path']) ? (strpos($parse['path'], '/') === 0 ? $parse['path'] : '/'.$parse['path']) : '/';
145
146 return !empty($parse['query']) ? $parse['path'].'?'.$parse['query'] : $parse['path'];
147 }
148 return ''; // Default return value.
149 }
150
151 /**
152 * Parses a URL/URI with same args as PHP's ``parse_url()`` function.
153 *
154 * This works around issues with this PHP function in versions prior to 5.3.8.
155 *
156 * @package s2Member\Utilities
157 * @since 111017
158 *
159 * @param string $url_uri Either a full URL, or a partial URI to parse.
160 * @param bool|int $component Optional. See PHP documentation on ``parse_url()`` function.
161 * @param bool $clean_path Defaults to true. s2Member will cleanup any return array `path`.
162 * @return str|array|bool The return value from PHP's ``parse_url()`` function.
163 * However, if ``$component`` is passed, s2Member forces a string return.
164 */
165 public static function parse_url($url_uri = FALSE, $component = FALSE, $clean_path = TRUE)
166 {
167 $component = $component === FALSE || $component === -1 ? -1 : $component;
168
169 if(is_string($url_uri) && strpos($url_uri, '?') !== FALSE)
170 {
171 list($_, $query) = preg_split /* Split @ query string marker. */('/\?/', $url_uri, 2);
172 $query = /* See: <https://bugs.php.net/bug.php?id=38143>. */ str_replace('://', urlencode('://'), $query);
173 $url_uri = /* Put it all back together again, after the above modifications. */ $_.'?'.$query;
174 unset($_, $query); // A little housekeeping here. Unset these vars.
175 }
176 $parse = @parse_url($url_uri, $component); // Let PHP work its magic via ``parse_url()``.
177
178 if($clean_path && is_array($parse) && !empty($parse['path']) && is_string($parse['path']))
179 $parse['path'] = preg_replace('/\/+/', '/', $parse['path']);
180
181 return $component !== -1 ? (string)$parse : $parse;
182 }
183
184 /**
185 * Responsible for all remote communications processed by s2Member.
186 *
187 * Uses ``wp_remote_request()`` through the `WP_Http` class.
188 *
189 * @package s2Member\Utilities
190 * @since 3.5
191 *
192 * @param string $url Full URL with possible query string parameters.
193 * @param string|array $post_body Optional. Either a string of POST data, or an array.
194 * @param array $args Optional. An array of additional arguments used by ``wp_remote_request()``.
195 * @param bool $return_array Optional. If true, instead of a string, we return an array with elements:
196 * `code` *(http response code)*, `message` *(http response message)*, `headers` *(an array of lowercase headers)*, `body` *(the response body string)*, `response` *(full response array)*.
197 * @return str|array|bool Requested response str|array from remote location *(see ``$return_array`` parameter )*; else (bool)`false` on failure.
198 */
199 public static function remote($url = FALSE, $post_body = FALSE, $args = FALSE, $return_array = FALSE)
200 {
201 if(!$url || !is_string($url))
202 return false;
203
204 $args = !is_array($args) ? array() : $args;
205
206 $args['s2member'] = WS_PLUGIN__S2MEMBER_VERSION; // s2Member connection.
207 $args['httpversion'] = !isset($args['httpversion']) ? '1.1' : $args['httpversion'];
208 $args['user-agent'] = !isset($args['user-agent']) ? 's2Member v'.WS_PLUGIN__S2MEMBER_VERSION.'; '.home_url() : $args['user-agent'];
209
210 if(!isset($args['sslverify']) && c_ws_plugin__s2member_utils_conds::is_localhost())
211 $args['sslverify'] = FALSE; // Force this off on localhost installs.
212
213 else if(!isset($args['sslverify']) && strcasecmp(self::parse_url($url, PHP_URL_HOST), $_SERVER['HTTP_HOST']) === 0)
214 $args['sslverify'] = FALSE; // Don't require verification when posting to self.
215
216 if($post_body && (is_array($post_body) || is_string($post_body)))
217 $args = array_merge($args, array('method' => 'POST', 'body' => $post_body));
218
219 if(!empty($args['method']) && strcasecmp((string)$args['method'], 'DELETE') === 0 && version_compare(get_bloginfo('version'), '3.4', '<'))
220 add_filter('use_curl_transport', '__return_false', /* ID via priority. */ 111209554);
221
222 foreach(array_keys(get_defined_vars())as$__v)$__refs[$__v]=&$$__v;
223 do_action('ws_plugin__s2member_before_wp_remote_request', get_defined_vars());
224 unset($__refs, $__v); // Housekeeping.
225
226 $response = wp_remote_request($url, $args);
227
228 remove_filter('use_curl_transport', '__return_false', 111209554);
229
230 if($return_array && !is_wp_error($response) && is_array($response))
231 {
232 $a = array('code' => (int)wp_remote_retrieve_response_code($response));
233 $a = array_merge($a, array('message' => wp_remote_retrieve_response_message($response)));
234 $a = array_merge($a, array('headers' => wp_remote_retrieve_headers($response)));
235 $a = array_merge($a, array('body' => wp_remote_retrieve_body($response)));
236 $a = array_merge($a, array('response' => $response));
237
238 return $a; // Return array w/ ``$response`` too.
239 }
240 if(!is_wp_error($response) && is_array($response))
241 return wp_remote_retrieve_body($response);
242
243 return false; // Remote request failed, return false.
244 }
245
246 /**
247 * Shortens a long URL, based on s2Member configuration.
248 *
249 * @package s2Member\Utilities
250 * @since 111002
251 *
252 * @param string $url A full/long URL to be shortened.
253 * @param string $api_sp Optional. A specific URL shortening API to use. Defaults to that which is configured in the s2Member Dashboard. Normally `s2member`, by default.
254 * @param bool $try_backups Defaults to true. If a failure occurs with the first API, we'll try others until we have success.
255 * @param int $expiration Optional. Transient expiration, in seconds, for built-in s2Member short links.
256 * @return str|bool The shortened URL on success, else false on failure.
257 */
258 public static function shorten($url = '', $api_sp = '', $try_backups = TRUE, $expiration = 0)
259 {
260 $url = $url && is_string($url) ? $url : FALSE;
261 $expiration = (int)$expiration;
262 $apis = array('s2member', 'bitly'); //260612 Supported APIs.
263 $api_sp = $api_sp && is_string($api_sp) ? strtolower($api_sp) : FALSE;
264 $default_url_shortener = $GLOBALS['WS_PLUGIN__']['s2member']['o']['default_url_shortener'];
265 $default_url_shortener_key = $GLOBALS['WS_PLUGIN__']['s2member']['o']['default_url_shortener_key'];
266 $default_custom_str_url_shortener = $GLOBALS['WS_PLUGIN__']['s2member']['o']['default_custom_str_url_shortener'];
267 $api = $api_sp ? $api_sp : $default_url_shortener;
268 $api = ($api === 'tiny_url') ? 's2member' : $api; //260612 TinyURL legacy API is deprecated; use built-in short links instead.
269
270 if($url && $api) // If specific, use it. Otherwise, try customs, else use the default shortening API.
271 {
272 if(!$api_sp // If not a specific API, give filters a chance to shorten it here.
273 && ($custom_url = trim(apply_filters('ws_plugin__s2member_url_shorten', FALSE, get_defined_vars())))
274 && stripos($custom_url, 'http') === 0)
275 return ($shorter_url = $custom_url);
276
277 else if(!$api_sp // If not specific, try custom settings.
278 && stripos($default_custom_str_url_shortener, 'http') === 0
279 && ($custom_url = trim(self::remote(str_ireplace(array('%%s2_long_url%%', '%%s2_long_url_md5%%'), array(rawurlencode($url), urlencode(md5($url))), $default_custom_str_url_shortener))))
280 && stripos($custom_url, 'http') === 0)
281 return ($shorter_url = $custom_url);
282
283 else if($api === 'none') // Don't shorten.
284 return $url;
285
286 else if($api === 's2member') //260612 Built-in short links.
287 return (($shorter_url = self::short_link($url, $expiration)) ? $shorter_url : $url);
288
289 else if($api === 'bitly' // Using the Bitly API in this case?
290 && ($bitly_endpoint = 'https://api-ssl.bitly.com/v3/shorten')
291 && ($bitly_endpoint_key = $default_url_shortener_key) // Must be configured by site owner.
292 && ($bitly_endpoint = add_query_arg('access_token', urlencode($bitly_endpoint_key), $bitly_endpoint))
293 && ($bitly_endpoint = add_query_arg('longUrl', urlencode($url), $bitly_endpoint))
294 && ($bitly_response = json_decode(trim(self::remote($bitly_endpoint))))
295 && !empty($bitly_response->data->url) && stripos($bitly_url = $bitly_response->data->url, 'http') === 0)
296 return ($shorter_url = $bitly_url);
297
298 else if($try_backups && count($apis) > 1) // Try backups?
299 {
300 foreach(array_diff($apis, array($api)) as $_backup_api)
301 if(($_backup_api_url = self::shorten($url, $_backup_api, FALSE, $expiration)))
302 return ($shorter_url = $_backup_api_url);
303 unset($_backup_api, $_backup_api_url); // Housekeeping.
304 }
305 }
306 return FALSE; // Default return value.
307 }
308
309 /**
310 * Creates a built-in s2Member short link.
311 *
312 * @package s2Member\Utilities
313 * @since 260612
314 *
315 * @param string $url A full/long s2Member URL to be shortened.
316 * @param int $expiration Optional. Transient expiration, in seconds.
317 * @return str|bool The shortened URL on success, else false on failure.
318 */
319 public static function short_link($url = '', $expiration = 0)
320 {
321 $url = $url && is_string($url) ? trim($url) : FALSE;
322 $expiration = (int)$expiration;
323
324 if(!$url || stripos($url, 'http') !== 0)
325 return FALSE;
326
327 $url_scheme = self::parse_url($url, PHP_URL_SCHEME);
328
329 if(!in_array(strtolower($url_scheme), array('http', 'https'), TRUE))
330 return FALSE;
331
332 $query = trim(self::parse_url($url, PHP_URL_QUERY), '?&=');
333 wp_parse_str($query, $vars);
334
335 if(empty($vars['s2member_register']) && empty($vars['s2member_sp_access']))
336 return FALSE;
337
338 $url_host = self::parse_url($url, PHP_URL_HOST);
339 $home_host = self::parse_url(home_url('/'), PHP_URL_HOST);
340
341 if(!$url_host || !$home_host || strcasecmp($url_host, $home_host) !== 0)
342 return FALSE;
343
344 $expiration = ($expiration > 0) ? $expiration : 7 * DAY_IN_SECONDS;
345
346 for($i = 0; $i < 10; $i++)
347 {
348 $code = wp_generate_password(10, FALSE, FALSE);
349 $transient_key = 's2member_link_'.$code;
350
351 if(get_transient($transient_key) === FALSE)
352 {
353 if(set_transient($transient_key, $url, $expiration))
354 return add_query_arg('s2_link', rawurlencode($code), home_url('/'));
355 }
356 }
357 return FALSE; // Default return value.
358 }
359
360 /**
361 * Redirects built-in s2Member short links.
362 *
363 * @package s2Member\Utilities
364 * @since 260612
365 *
366 * @attaches-to ``add_action('init');``
367 *
368 * @return null Exits script execution after redirecting.
369 */
370 public static function short_link_redirect()
371 {
372 if(empty($_GET['s2_link']) || !is_string($_GET['s2_link']))
373 return;
374
375 $code = trim(wp_unslash((string)$_GET['s2_link']));
376
377 if(!preg_match('/^[a-zA-Z0-9]{10}$/', $code))
378 return;
379
380 $url = get_transient('s2member_link_'.$code);
381
382 if(!$url || !is_string($url) || stripos($url, 'http') !== 0)
383 return;
384
385 $url_scheme = self::parse_url($url, PHP_URL_SCHEME);
386
387 if(!in_array(strtolower($url_scheme), array('http', 'https'), TRUE))
388 return;
389
390 $url_host = self::parse_url($url, PHP_URL_HOST);
391 $home_host = self::parse_url(home_url('/'), PHP_URL_HOST);
392
393 if(!$url_host || !$home_host || strcasecmp($url_host, $home_host) !== 0)
394 return;
395
396 $query = trim(self::parse_url($url, PHP_URL_QUERY), '?&=');
397 wp_parse_str($query, $vars);
398
399 if(empty($vars['s2member_register']) && empty($vars['s2member_sp_access']))
400 return;
401
402 nocache_headers();
403 wp_safe_redirect($url, 302);
404 exit;
405 }
406 /**
407 * Removes all s2Member-generated signatures from a full URL, a partial URI, or just a query string.
408 *
409 * @package s2Member\Utilities
410 * @since 111106
411 *
412 * @param string $url_uri_query A full URL, a partial URI, or just the query string; to remove s2Member-generated signatures from.
413 * @param string $sig_var Optional. The name of the s2Member-generated signature variable. Defaults to `_s2member_sig`.
414 * @return string A full URL, a partial URI, or just the query string; without any s2Member-generated signatures.
415 */
416 public static function remove_s2member_sigs($url_uri_query = FALSE, $sig_var = FALSE)
417 {
418 $url_uri_query = c_ws_plugin__s2member_utils_strings::trim((string)$url_uri_query, false, '?&=');
419 $sig_var = ($sig_var && is_string($sig_var)) ? $sig_var : '_s2member_sig';
420 $sigs = array_unique(array($sig_var, '_s2member_sig'));
421
422 return trim(remove_query_arg($sigs, $url_uri_query), '?&=');
423 }
424
425 /**
426 * Adds an s2Member-generated signature onto a full URL, a partial URI, or just a query string.
427 *
428 * @package s2Member\Utilities
429 * @since 111106
430 *
431 * @param string $url_uri_query A full URL, a partial URI, or just a query string; to append the s2Member-generated signature onto.
432 * @param string $sig_var Optional. The name of the s2Member-generated signature variable. Defaults to `_s2member_sig`.
433 * @return string A full URL, a partial URI, or just a query string; with an s2Member-generated signature.
434 */
435 public static function add_s2member_sig($url_uri_query = FALSE, $sig_var = FALSE)
436 {
437 $url_uri_query = $query = c_ws_plugin__s2member_utils_strings::trim((string)$url_uri_query, false, '?&=');
438 $sig_var = $sig_var && is_string($sig_var) ? $sig_var : '_s2member_sig';
439
440 $url_uri_query = $query = c_ws_plugin__s2member_utils_urls::remove_s2member_sigs($url_uri_query, $sig_var);
441 if(preg_match('/^(?:[a-z]+\:\/\/|\/)/i', $url_uri_query)) // Is this a full URL or a partial URI?
442 $query = trim(c_ws_plugin__s2member_utils_urls::parse_url($url_uri_query, PHP_URL_QUERY), '?&=');
443
444 $key = c_ws_plugin__s2member_utils_encryption::key(); // Obtain key.
445
446 if($url_uri_query && is_string($query)) // We DO allow empty query strings. So we can sign a URL without one.
447 {
448 wp_parse_str($query, $vars); // Parse the query string into an array of ``$vars``.
449 $vars = c_ws_plugin__s2member_utils_arrays::remove_0b_strings(c_ws_plugin__s2member_utils_strings::trim_deep($vars));
450 $vars = serialize(c_ws_plugin__s2member_utils_arrays::ksort_deep($vars));
451
452 $sig = ($time = time()).'-'.md5($key.$time.$vars);
453 $url_uri_query = add_query_arg($sig_var, urlencode($sig), $url_uri_query);
454 }
455 return $url_uri_query; // Possibly with a ``$sig_var`` variable.
456 }
457
458 /**
459 * Verifies an s2Member-generated signature; in a full URL, a partial URI, or in just a query string.
460 *
461 * @package s2Member\Utilities
462 * @since 111106
463 *
464 * @param string $url_uri_query A full URL, a partial URI, or just a query string. Must have an s2Member-generated signature to validate.
465 * @param bool $check_time Optional. Defaults to false. If true, s2Member will also check if the signature has expired, based on ``$exp_secs``.
466 * @param string|int $exp_secs Optional. Defaults to (int)10. If ``$check_time`` is true, s2Member will check if the signature has expired, based on ``$exp_secs``.
467 * @param string $sig_var Optional. The name of the s2Member-generated signature variable. Defaults to `_s2member_sig`.
468 * @return bool True if the s2Member-generated signature is OK, else false.
469 */
470 public static function s2member_sig_ok($url_uri_query = FALSE, $check_time = FALSE, $exp_secs = FALSE, $sig_var = FALSE)
471 {
472 $url_uri_query = $query = c_ws_plugin__s2member_utils_strings::trim((string)$url_uri_query, false, '?&=');
473 if(preg_match('/^(?:[a-z]+\:\/\/|\/)/i', $url_uri_query)) // Is this a full URL or a partial URI?
474 $query = trim(c_ws_plugin__s2member_utils_urls::parse_url($url_uri_query, PHP_URL_QUERY), '?&=');
475
476 $check_time = (bool)$check_time; // Check time?
477 $exp_secs = is_numeric($exp_secs) ? (int)$exp_secs : 10;
478 $sig_var = $sig_var && is_string($sig_var) ? $sig_var : '_s2member_sig';
479
480 $key = c_ws_plugin__s2member_utils_encryption::key(); // Obtain key.
481
482 if(preg_match_all('/'.preg_quote($sig_var, '/').'\=([0-9]+)-([^&$]+)/', $query, $sigs))
483 {
484 $query = c_ws_plugin__s2member_utils_urls::remove_s2member_sigs($query, $sig_var);
485
486 wp_parse_str($query, $vars); // Parse the query string into an array of ``$vars``.
487 $vars = c_ws_plugin__s2member_utils_arrays::remove_0b_strings(c_ws_plugin__s2member_utils_strings::trim_deep($vars));
488 $vars = serialize(c_ws_plugin__s2member_utils_arrays::ksort_deep($vars));
489
490 $i = count($sigs[1]) - 1; // Last one.
491 $time = $sigs[1][$i]; // Timestamp.
492 $sig = $sigs[2][$i]; // Signature.
493 $valid_sig = md5($key.$time.$vars);
494
495 if($check_time) // This must NOT be older than ``$exp_secs`` seconds ago.
496 return $sig === $valid_sig && $time >= strtotime('-'.$exp_secs.' seconds');
497
498 return $sig === $valid_sig;
499 }
500 return false; // False, it's NOT ok.
501 }
502 }
503 }
504