| 1 |
<?php |
| 2 |
/** |
| 3 |
* Shared environment identity and capability policy. |
| 4 |
* |
| 5 |
* @package Booking Calendar |
| 6 |
* @since 11.9.0 |
| 7 |
*/ |
| 8 |
|
| 9 |
if ( ! defined( 'ABSPATH' ) ) { |
| 10 |
exit; |
| 11 |
} |
| 12 |
|
| 13 |
/** |
| 14 |
* Provide one authoritative classification for Booking Calendar environments. |
| 15 |
* |
| 16 |
* Environment identity is intentionally separate from individual capabilities. |
| 17 |
* A public live demo may block WordPress page publishing and automatic |
| 18 |
* onboarding while still allowing selected Setup Wizard configuration. Internal |
| 19 |
* test sites, including the Free test site, retain normal publishing behavior. |
| 20 |
*/ |
| 21 |
final class WPBC_Environment_Policy { |
| 22 |
|
| 23 |
/** Standard customer or development installation. */ |
| 24 |
const TYPE_STANDARD = 'standard'; |
| 25 |
|
| 26 |
/** Public, shared Booking Calendar live demo. */ |
| 27 |
const TYPE_PUBLIC_LIVE_DEMO = 'public_live_demo'; |
| 28 |
|
| 29 |
/** Internal Booking Calendar test environment. */ |
| 30 |
const TYPE_INTERNAL_TEST = 'internal_test'; |
| 31 |
|
| 32 |
/** WordPress Playground installation. */ |
| 33 |
const TYPE_PLAYGROUND = 'playground'; |
| 34 |
|
| 35 |
/** |
| 36 |
* Return the current environment type. |
| 37 |
* |
| 38 |
* WordPress's configured home host is authoritative. The current HTTP Host is |
| 39 |
* used only when no configured host is available, which prevents a request |
| 40 |
* header from overriding a valid site identity. |
| 41 |
* |
| 42 |
* @return string One of the TYPE_* constants. |
| 43 |
*/ |
| 44 |
public static function get_environment_type() { |
| 45 |
$site_host = self::get_site_host(); |
| 46 |
$request_host = self::get_request_host(); |
| 47 |
$canonical_host = '' !== $site_host ? $site_host : $request_host; |
| 48 |
|
| 49 |
// A managed public host retains write restrictions even if Playground is also detected. |
| 50 |
if ( self::is_public_live_demo_host( $canonical_host ) ) { |
| 51 |
$environment_type = self::TYPE_PUBLIC_LIVE_DEMO; |
| 52 |
} elseif ( self::is_playground() ) { |
| 53 |
$environment_type = self::TYPE_PLAYGROUND; |
| 54 |
} elseif ( self::is_internal_test_host( $canonical_host ) ) { |
| 55 |
$environment_type = self::TYPE_INTERNAL_TEST; |
| 56 |
} else { |
| 57 |
$environment_type = self::TYPE_STANDARD; |
| 58 |
} |
| 59 |
|
| 60 |
/** |
| 61 |
* Filter the authoritative Booking Calendar environment type. |
| 62 |
* |
| 63 |
* The returned value must be one of the documented TYPE_* constants. An |
| 64 |
* unsupported value is ignored so a malformed integration cannot weaken a |
| 65 |
* detected live-demo restriction. |
| 66 |
* |
| 67 |
* @since 11.9.0 |
| 68 |
* |
| 69 |
* @param string $environment_type Detected environment type. |
| 70 |
* @param string $canonical_host Normalized host used for classification. |
| 71 |
* @param string $site_host Normalized WordPress home URL host. |
| 72 |
* @param string $request_host Normalized request host used as fallback. |
| 73 |
*/ |
| 74 |
$filtered_type = function_exists( 'apply_filters' ) |
| 75 |
? apply_filters( 'wpbc_environment_type', $environment_type, $canonical_host, $site_host, $request_host ) |
| 76 |
: $environment_type; |
| 77 |
$allowed_types = array( |
| 78 |
self::TYPE_STANDARD, |
| 79 |
self::TYPE_PUBLIC_LIVE_DEMO, |
| 80 |
self::TYPE_INTERNAL_TEST, |
| 81 |
self::TYPE_PLAYGROUND, |
| 82 |
); |
| 83 |
|
| 84 |
return in_array( $filtered_type, $allowed_types, true ) ? $filtered_type : $environment_type; |
| 85 |
} |
| 86 |
|
| 87 |
/** |
| 88 |
* Determine whether this is a public Booking Calendar live demo. |
| 89 |
* |
| 90 |
* @return bool True for a public shared live demo. |
| 91 |
*/ |
| 92 |
public static function is_live_demo() { |
| 93 |
return self::TYPE_PUBLIC_LIVE_DEMO === self::get_environment_type(); |
| 94 |
} |
| 95 |
|
| 96 |
/** |
| 97 |
* Determine whether this is an internal Booking Calendar test environment. |
| 98 |
* |
| 99 |
* @return bool True for an explicitly identified internal test host. |
| 100 |
*/ |
| 101 |
public static function is_internal_test() { |
| 102 |
return self::TYPE_INTERNAL_TEST === self::get_environment_type(); |
| 103 |
} |
| 104 |
|
| 105 |
/** |
| 106 |
* Determine whether WordPress is running in Playground. |
| 107 |
* |
| 108 |
* @return bool True only for the canonical strict boolean constant. |
| 109 |
*/ |
| 110 |
public static function is_playground() { |
| 111 |
return defined( 'WPBC_IS_PLAYGROUND' ) && true === WPBC_IS_PLAYGROUND; |
| 112 |
} |
| 113 |
|
| 114 |
/** |
| 115 |
* Determine whether WordPress page discovery and publishing are restricted. |
| 116 |
* |
| 117 |
* The released publishing filter is applied here, rather than in an |
| 118 |
* individual publisher, so every Setup Wizard and publishing consumer sees |
| 119 |
* the same capability decision. |
| 120 |
* |
| 121 |
* @return bool True when Booking Calendar must not discover or mutate pages. |
| 122 |
*/ |
| 123 |
public static function is_page_publishing_restricted() { |
| 124 |
$is_restricted = self::is_live_demo(); |
| 125 |
$site_host = self::get_site_host(); |
| 126 |
$request_host = self::get_request_host(); |
| 127 |
|
| 128 |
/** |
| 129 |
* Filter whether Booking Calendar page publishing is restricted. |
| 130 |
* |
| 131 |
* @since 11.6.0 |
| 132 |
* |
| 133 |
* @param bool $is_restricted Whether the shared environment policy restricts publishing. |
| 134 |
* @param string $site_host Normalized WordPress home URL host. |
| 135 |
* @param string $request_host Normalized request host used only as a fallback. |
| 136 |
*/ |
| 137 |
return function_exists( 'apply_filters' ) |
| 138 |
? (bool) apply_filters( 'wpbc_publish_booking_form_is_demo_restricted', $is_restricted, $site_host, $request_host ) |
| 139 |
: $is_restricted; |
| 140 |
} |
| 141 |
|
| 142 |
/** |
| 143 |
* Determine whether Booking Calendar may discover and publish WordPress pages. |
| 144 |
* |
| 145 |
* @return bool True when page publishing is available. |
| 146 |
*/ |
| 147 |
public static function allows_page_publishing() { |
| 148 |
return ! self::is_page_publishing_restricted(); |
| 149 |
} |
| 150 |
|
| 151 |
/** |
| 152 |
* Determine whether Setup Wizard may collect Business details. |
| 153 |
* |
| 154 |
* @return bool True on customer and internal test installations. |
| 155 |
*/ |
| 156 |
public static function allows_setup_business_details() { |
| 157 |
return ! self::is_live_demo() && ! self::is_playground(); |
| 158 |
} |
| 159 |
|
| 160 |
/** |
| 161 |
* Determine whether Setup Wizard may send its optional summary email. |
| 162 |
* |
| 163 |
* @return bool True when the Business details consent step is available. |
| 164 |
*/ |
| 165 |
public static function allows_setup_summary_email() { |
| 166 |
return self::allows_setup_business_details(); |
| 167 |
} |
| 168 |
|
| 169 |
/** |
| 170 |
* Determine whether the environment permits automatic Setup onboarding. |
| 171 |
* |
| 172 |
* Request-specific restrictions such as network administration, bulk |
| 173 |
* activation, and iframe requests remain the activation coordinator's |
| 174 |
* responsibility. |
| 175 |
* |
| 176 |
* @return bool True when the environment permits automatic onboarding. |
| 177 |
*/ |
| 178 |
public static function allows_automatic_setup_onboarding() { |
| 179 |
return ! self::is_live_demo() && ! self::is_playground(); |
| 180 |
} |
| 181 |
|
| 182 |
/** |
| 183 |
* Return the normalized WordPress home host. |
| 184 |
* |
| 185 |
* @return string Normalized host, or an empty string when unavailable. |
| 186 |
*/ |
| 187 |
public static function get_site_host() { |
| 188 |
if ( ! function_exists( 'home_url' ) ) { |
| 189 |
return ''; |
| 190 |
} |
| 191 |
|
| 192 |
return self::normalize_host( home_url( '/' ) ); |
| 193 |
} |
| 194 |
|
| 195 |
/** |
| 196 |
* Return the normalized current request host. |
| 197 |
* |
| 198 |
* This value is only a fallback when WordPress has no configured home host. |
| 199 |
* Proxy-only forwarding headers are intentionally ignored. |
| 200 |
* |
| 201 |
* @return string Normalized host, or an empty string outside HTTP. |
| 202 |
*/ |
| 203 |
public static function get_request_host() { |
| 204 |
if ( empty( $_SERVER['HTTP_HOST'] ) ) { |
| 205 |
return ''; |
| 206 |
} |
| 207 |
|
| 208 |
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- normalize_host() validates and reduces the value to a hostname. |
| 209 |
$request_host = function_exists( 'wp_unslash' ) ? wp_unslash( $_SERVER['HTTP_HOST'] ) : $_SERVER['HTTP_HOST']; |
| 210 |
|
| 211 |
return self::normalize_host( $request_host ); |
| 212 |
} |
| 213 |
|
| 214 |
/** |
| 215 |
* Normalize a URL host or HTTP Host value. |
| 216 |
* |
| 217 |
* @param mixed $host Hostname, optionally containing a port or trailing dot. |
| 218 |
* |
| 219 |
* @return string Lowercase hostname, or an empty string when invalid. |
| 220 |
*/ |
| 221 |
public static function normalize_host( $host ) { |
| 222 |
$host = trim( strtolower( (string) $host ) ); |
| 223 |
if ( '' === $host ) { |
| 224 |
return ''; |
| 225 |
} |
| 226 |
|
| 227 |
$host_url = preg_match( '#^[a-z][a-z0-9+.-]*://#i', $host ) ? $host : 'http://' . ltrim( $host, '/' ); |
| 228 |
$normalized_host = function_exists( 'wp_parse_url' ) |
| 229 |
? wp_parse_url( $host_url, PHP_URL_HOST ) |
| 230 |
: parse_url( $host_url, PHP_URL_HOST ); |
| 231 |
if ( ! is_string( $normalized_host ) ) { |
| 232 |
return ''; |
| 233 |
} |
| 234 |
|
| 235 |
return strtolower( rtrim( $normalized_host, '.' ) ); |
| 236 |
} |
| 237 |
|
| 238 |
/** |
| 239 |
* Determine whether a host is one of the managed public live demos. |
| 240 |
* |
| 241 |
* Exact matching prevents internal, customer, and lookalike subdomains from |
| 242 |
* inheriting demo restrictions merely because their names share a suffix. |
| 243 |
* |
| 244 |
* @param string $host Normalized or unnormalized hostname. |
| 245 |
* |
| 246 |
* @return bool True for a managed public live-demo host. |
| 247 |
*/ |
| 248 |
public static function is_public_live_demo_host( $host ) { |
| 249 |
$host = self::normalize_host( $host ); |
| 250 |
|
| 251 |
/** |
| 252 |
* Filter the exact public live-demo host allow-list. |
| 253 |
* |
| 254 |
* @since 11.9.0 |
| 255 |
* |
| 256 |
* @param string[] $live_demo_hosts Normalized public live-demo hosts. |
| 257 |
*/ |
| 258 |
$live_demo_hosts = array( |
| 259 |
'personal.wpbookingcalendar.com', |
| 260 |
'bs.wpbookingcalendar.com', |
| 261 |
'bm.wpbookingcalendar.com', |
| 262 |
'bl.wpbookingcalendar.com', |
| 263 |
'multiuser.wpbookingcalendar.com', |
| 264 |
'personaltest.wpbookingcalendar.com', |
| 265 |
'bstest.wpbookingcalendar.com', |
| 266 |
'bmtest.wpbookingcalendar.com', |
| 267 |
'bltest.wpbookingcalendar.com', |
| 268 |
'multiusertest.wpbookingcalendar.com', |
| 269 |
); |
| 270 |
if ( function_exists( 'apply_filters' ) ) { |
| 271 |
$live_demo_hosts = apply_filters( 'wpbc_environment_public_live_demo_hosts', $live_demo_hosts ); |
| 272 |
} |
| 273 |
$live_demo_hosts = self::normalize_host_list( $live_demo_hosts ); |
| 274 |
|
| 275 |
return '' !== $host && in_array( $host, $live_demo_hosts, true ); |
| 276 |
} |
| 277 |
|
| 278 |
/** |
| 279 |
* Determine whether a host is an explicitly identified internal test site. |
| 280 |
* |
| 281 |
* Internal test sites intentionally keep standard publishing and onboarding |
| 282 |
* capabilities. This explicit identity prevents future suffix-based logic |
| 283 |
* from accidentally treating the Free test site as a public demo. |
| 284 |
* |
| 285 |
* @param string $host Normalized or unnormalized hostname. |
| 286 |
* |
| 287 |
* @return bool True for an internal test host. |
| 288 |
*/ |
| 289 |
public static function is_internal_test_host( $host ) { |
| 290 |
$host = self::normalize_host( $host ); |
| 291 |
|
| 292 |
/** |
| 293 |
* Filter the exact internal test host allow-list. |
| 294 |
* |
| 295 |
* @since 11.9.0 |
| 296 |
* |
| 297 |
* @param string[] $internal_test_hosts Normalized internal test hosts. |
| 298 |
*/ |
| 299 |
$internal_test_hosts = array( |
| 300 |
'freetest.wpbookingcalendar.com', |
| 301 |
'beta', |
| 302 |
); |
| 303 |
if ( function_exists( 'apply_filters' ) ) { |
| 304 |
$internal_test_hosts = apply_filters( 'wpbc_environment_internal_test_hosts', $internal_test_hosts ); |
| 305 |
} |
| 306 |
$internal_test_hosts = self::normalize_host_list( $internal_test_hosts ); |
| 307 |
|
| 308 |
return '' !== $host && in_array( $host, $internal_test_hosts, true ); |
| 309 |
} |
| 310 |
|
| 311 |
/** |
| 312 |
* Normalize and deduplicate a filtered hostname list. |
| 313 |
* |
| 314 |
* @param mixed $hosts Candidate hostname list. |
| 315 |
* |
| 316 |
* @return string[] Valid normalized hostnames. |
| 317 |
*/ |
| 318 |
private static function normalize_host_list( $hosts ) { |
| 319 |
$normalized_hosts = array(); |
| 320 |
|
| 321 |
foreach ( is_array( $hosts ) ? $hosts : array() as $host ) { |
| 322 |
if ( ! is_scalar( $host ) ) { |
| 323 |
continue; |
| 324 |
} |
| 325 |
|
| 326 |
$normalized_host = self::normalize_host( $host ); |
| 327 |
if ( '' !== $normalized_host ) { |
| 328 |
$normalized_hosts[] = $normalized_host; |
| 329 |
} |
| 330 |
} |
| 331 |
|
| 332 |
return array_values( array_unique( $normalized_hosts ) ); |
| 333 |
} |
| 334 |
} |
| 335 |
|