PluginProbe ʕ •ᴥ•ʔ
WP STAGING – WordPress Backups, Restore, Migration & Clone / 4.9.5
WP STAGING – WordPress Backups, Restore, Migration & Clone v4.9.5
4.9.5 4.9.4 4.9.3 4.9.2 4.9.1 4.9.0 4.8.1 trunk 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 3.10.0 3.2.0 3.3.1 3.3.2 3.3.3 3.4.1 3.4.3 3.5.0 3.6.0 3.7.1 3.8.0 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.8.6 3.8.7 3.9.0 3.9.1 3.9.2 3.9.3 3.9.4 4.0.0 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4 4.2.0 4.2.1 4.3.0 4.3.1 4.3.2 4.4.0 4.5.0 4.6.0 4.7.0 4.7.1 4.7.2 4.7.3 4.8.0
wp-staging / Framework / SiteInfo.php
wp-staging / Framework Last commit date
Adapter 2 days ago Analytics 1 month ago Assets 2 days ago BackgroundProcessing 3 weeks ago CloningProcess 2 days ago Collection 3 years ago Command 5 years ago Component 9 months ago DI 2 days ago Database 2 days ago DependencyResolver 2 years ago Exceptions 2 years ago Facades 3 months ago Filesystem 2 days ago Interfaces 5 years ago Job 1 week ago Language 2 days ago Logger 2 months ago Mails 4 months ago Network 2 days ago Newsfeed 5 months ago Notices 4 weeks ago Performance 3 months ago Permalinks 1 year ago Queue 5 months ago Rest 3 months ago Security 3 months ago Settings 3 months ago TemplateEngine 8 months ago ThirdParty 2 days ago Traits 2 days ago Upgrade 2 months ago Utils 2 days ago AnalyticsServiceProvider.php 1 month ago AssetServiceProvider.php 1 year ago CommonServiceProvider.php 3 months ago ErrorHandler.php 1 month ago NoticeServiceProvider.php 1 year ago SettingsServiceProvider.php 3 months ago SiteInfo.php 2 days ago Url.php 5 months ago
SiteInfo.php
420 lines
1 <?php
2
3 namespace WPStaging\Framework;
4
5 use WPStaging\Framework\Facades\Hooks;
6 use WPStaging\Framework\Facades\Sanitize;
7 use WPStaging\Staging\CloneOptions;
8
9 /**
10 * Class SiteInfo
11 *
12 * Provides information about the current site.
13 *
14 * @package WPStaging\Site
15 */
16 class SiteInfo
17 {
18 /**
19 * The key used in DB to store is cloneable feature in clone options
20 * @var string
21 */
22 const IS_CLONEABLE_KEY = 'isCloneable';
23
24 /**
25 * The file which make staging site cloneable
26 * This way is depreciated
27 * @var string
28 */
29 const CLONEABLE_FILE = '.wp-staging-cloneable';
30
31 /**
32 * The key used in DB to store whether site is staging or not
33 * @var string
34 */
35 const IS_STAGING_KEY = 'wpstg_is_staging_site';
36
37 /**
38 * The file which makes a site a staging site
39 * @var string
40 */
41 const STAGING_FILE = '.wp-staging';
42
43 /** @var string */
44 const HOSTED_ON_WP = 'wp.com';
45
46 /** @var string */
47 const HOSTED_ON_FLYWHEEL = 'flywheel';
48
49 /** @var string */
50 const HOSTED_ON_BITNAMI = 'bitnami';
51
52 /** @var string */
53 const OTHER_HOST = 'other';
54
55 /**
56 * `.dev` is a public gTLD as well as a local convention, hence suffix-only:
57 * example.dev is local, foo.dev.example.com is not.
58 *
59 * @var string[]
60 */
61 const LOCAL_HOSTNAME_SUFFIXES = [
62 '.local',
63 '.test',
64 '.localhost',
65 '.dev',
66 ];
67
68 /** @var string[] Local only when they are the entire host. */
69 const LOCAL_HOSTNAMES = [
70 'localhost',
71 ];
72
73 /** @var string[] */
74 const LOCAL_IP_PREFIXES = [
75 '10.0.0.',
76 '172.16.0.',
77 '192.168.0.',
78 ];
79
80 /**
81 * @var CloneOptions
82 */
83 private $cloneOptions;
84
85 /**
86 * @var array
87 */
88 private $errors = [];
89
90 public function __construct()
91 {
92 // TODO: inject using DI
93 $this->cloneOptions = new CloneOptions();
94 }
95
96 /**
97 * @return bool True if it is staging site. False otherwise.
98 */
99 public function isStagingSite(): bool
100 {
101 // Single source of truth lives in the early bootstrap, before the autoloader.
102 return wpstgIsStagingSite(self::STAGING_FILE, self::IS_STAGING_KEY);
103 }
104
105 /**
106 * @return bool True if it is staging site. False otherwise.
107 */
108 public function isCloneable(): bool
109 {
110 // Site should be cloneable if not staging i.e. production site
111 if (!$this->isStagingSite()) {
112 return true;
113 }
114
115 // Old condition to check if staging site is cloneable
116 if (file_exists(ABSPATH . self::CLONEABLE_FILE)) {
117 return true;
118 }
119
120 // New condition for checking whether staging is cloneable or not
121 return $this->cloneOptions->get(self::IS_CLONEABLE_KEY, false);
122 }
123
124 /**
125 * Check if WP is installed in subdirectory
126 * If siteurl and home are not identical we assume the site is located in a subdirectory
127 * related to that instruction https://wordpress.org/support/article/giving-wordpress-its-own-directory/
128 *
129 * @return bool
130 */
131 public function isInstalledInSubDir(): bool
132 {
133 $siteUrl = get_option('siteurl');
134 $homeUrl = get_option('home');
135
136 //Get URL path e.g.https://example.com/path will return /path
137 $siteUrlPath = wp_parse_url($siteUrl, PHP_URL_PATH);
138 $homeUrlPath = wp_parse_url($homeUrl, PHP_URL_PATH);
139
140 if ($siteUrlPath === null && $homeUrlPath === null || $siteUrlPath === $homeUrlPath) {
141 return false;
142 }
143
144 if ($siteUrlPath === null && $homeUrlPath !== null) {
145 return true;
146 }
147
148 return false;
149 }
150
151 /**
152 * Enable the cloning for current staging site.
153 *
154 * @return bool
155 */
156 public function enableStagingSiteCloning(): bool
157 {
158 // Early Bail: if site is not staging
159 if (!$this->isStagingSite()) {
160 return false;
161 }
162
163 // Early Bail: if cloning already enabled
164 if ($this->isCloneable()) {
165 return true;
166 }
167
168 return $this->cloneOptions->set(self::IS_CLONEABLE_KEY, true);
169 }
170
171 /**
172 * Enable the cloning for current staging site.
173 *
174 * @return bool
175 */
176 public function disableStagingSiteCloning(): bool
177 {
178 // Early Bail: if site is not staging
179 if (!$this->isStagingSite()) {
180 return false;
181 }
182
183 // Early Bail: if cloning already disabled
184 if (!$this->isCloneable()) {
185 return true;
186 }
187
188 // First try disabling if cloneable feature exist due to old way.
189 $cloneableFile = trailingslashit(ABSPATH) . self::CLONEABLE_FILE;
190 if (file_exists($cloneableFile) && !unlink($cloneableFile)) {
191 // Error if files exists but unable to unlink
192 return false;
193 }
194
195 // Staging site may have been made cloneable through both ways
196 // So now try disabling through new way
197 return (!file_exists($cloneableFile) && $this->cloneOptions->delete(self::IS_CLONEABLE_KEY));
198 }
199
200 /**
201 * @return bool True if "short_open_tags" is enabled, false if disabled.
202 */
203 public function isPhpShortTagsEnabled(): bool
204 {
205 return in_array(strtolower(ini_get('short_open_tags')), ['1', 'on', 'true']);
206 }
207
208 /**
209 * Is WP Bakery plugin active?
210 *
211 * @return bool
212 */
213 public function isWpBakeryActive(): bool
214 {
215 return defined('WPB_VC_VERSION');
216 }
217
218 /**
219 * Is Jetpack plugin active?
220 *
221 * @return bool
222 */
223 public function isJetpackActive(): bool
224 {
225 return class_exists('Jetpack');
226 }
227
228 /**
229 * @return string[]
230 */
231 public function getErrors(): array
232 {
233 return $this->errors;
234 }
235
236 /**
237 * @return bool
238 */
239 public function isBitnami(): bool
240 {
241 return ABSPATH === '/opt/bitnami/wordpress/';
242 }
243
244 /**
245 * @return bool
246 */
247 public function isWpContentOutsideAbspath(): bool
248 {
249 $wpContentDir = wp_normalize_path(WP_CONTENT_DIR);
250 $abspath = wp_normalize_path(ABSPATH);
251
252 return !(strpos($wpContentDir, $abspath) === 0);
253 }
254
255 /**
256 * @return bool
257 */
258 public function isUploadsOutsideAbspath(): bool
259 {
260 $uploadDir = wp_normalize_path(wp_upload_dir()['basedir']);
261 $abspath = wp_normalize_path(ABSPATH);
262
263 return strpos($uploadDir, $abspath) !== 0;
264 }
265
266 /**
267 * @return bool
268 */
269 public function isFlywheel(): bool
270 {
271 if (!$this->isWpContentOutsideAbspath()) {
272 return false;
273 }
274
275 return file_exists(trailingslashit(wp_normalize_path(ABSPATH)) . '.fw-config.php');
276 }
277
278 /**
279 * @return bool
280 */
281 public function isHostedOnWordPressCom(): bool
282 {
283 if (!$this->isWpContentOutsideAbspath()) {
284 return false;
285 }
286
287 $parentDirectory = dirname(trailingslashit(wp_normalize_path(WP_CONTENT_DIR)));
288 $wpcomDetection = trailingslashit($parentDirectory) . '__wp__';
289 if (!is_link($wpcomDetection)) {
290 return false;
291 }
292
293 return true;
294 }
295
296 /**
297 * @return string
298 */
299 public function getHostingType(): string
300 {
301 if ($this->isFlywheel()) {
302 return self::HOSTED_ON_FLYWHEEL;
303 }
304
305 if ($this->isHostedOnWordPressCom()) {
306 return self::HOSTED_ON_WP;
307 }
308
309 if ($this->isBitnami()) {
310 return self::HOSTED_ON_BITNAMI;
311 }
312
313 return self::OTHER_HOST;
314 }
315
316 public function getPhpArchitecture(): string
317 {
318 return PHP_INT_SIZE === 8 ? '64-bit' : '32-bit';
319 }
320
321 public function getOsArchitecture(): string
322 {
323 try {
324 if (!function_exists('php_uname')) {
325 return 'N/A';
326 }
327
328 if (in_array('php_uname', explode(',', ini_get('disable_functions')))) {
329 return 'N/A';
330 }
331
332 return strpos(php_uname('m'), '64') !== false ? '64-bit' : '32-bit';
333 } catch (\Throwable $ex) {
334 return 'N/A';
335 }
336 }
337
338 /**
339 * @return bool
340 */
341 public function isHostedOnElementorCloud(): bool
342 {
343 $httpHost = !empty($_SERVER['HTTP_HOST']) ? Sanitize::sanitizeString($_SERVER['HTTP_HOST']) : '';
344 if (strpos($httpHost, 'elementor.cloud') !== false) {
345 return true;
346 }
347
348 $headers = headers_list();
349 foreach ($headers as $header) {
350 if (stripos($header, 'ec-source') !== false || stripos($header, 'ec-coldstart') !== false || stripos($header, 'EC-LB-OP-STATUS') !== false) {
351 return true;
352 }
353 }
354
355 return false;
356 }
357
358 /**
359 * Check if the website is installed locally.
360 *
361 * Matched anchored on the host: two licence gates short-circuit on this,
362 * so a false positive unlocks paid features on a public site.
363 *
364 * @return bool
365 */
366 public function isLocal(): bool
367 {
368 $host = strtolower((string)wp_parse_url(get_site_url(), PHP_URL_HOST));
369
370 return apply_filters('wpstg.tests.is_local_site', $this->isLocalHost($host));
371 }
372
373 private function isLocalHost(string $host): bool
374 {
375 if ($host === '') {
376 return false;
377 }
378
379 if (in_array($host, self::LOCAL_HOSTNAMES, true)) {
380 return true;
381 }
382
383 foreach (self::LOCAL_HOSTNAME_SUFFIXES as $suffix) {
384 if (substr($host, -strlen($suffix)) === $suffix) {
385 return true;
386 }
387 }
388
389 return $this->isPrivateIp($host);
390 }
391
392 /**
393 * Guarded on a real address: 10.0.0.example.com is a valid public hostname
394 * that matches the 10.0.0. prefix.
395 */
396 private function isPrivateIp(string $host): bool
397 {
398 if (filter_var($host, FILTER_VALIDATE_IP) === false) {
399 return false;
400 }
401
402 foreach (self::LOCAL_IP_PREFIXES as $prefix) {
403 if (strpos($host, $prefix) === 0) {
404 return true;
405 }
406 }
407
408 return false;
409 }
410
411 /**
412 * Wrapper around is_multisite()
413 * @return bool
414 */
415 public function isMultisite(): bool
416 {
417 return is_multisite();
418 }
419 }
420