PluginProbe ʕ •ᴥ•ʔ
WP STAGING – WordPress Backup, Restore, Migration & Clone / 4.9.2
WP STAGING – WordPress Backup, Restore, Migration & Clone v4.9.2
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 / Notices / Notices.php
wp-staging / Framework / Notices Last commit date
BackupPluginsNotice.php 1 year ago BooleanNotice.php 2 years ago CliIntegrationNotice.php 1 day ago DisabledItemsNotice.php 2 years ago DismissNotice.php 1 day ago NextGenEngineNotice.php 1 day ago Notices.php 1 day ago NoticesHandler.php 11 months ago ObjectCacheNotice.php 11 months ago OutdatedWpStagingNotice.php 1 year ago WarningsNotice.php 2 years ago WpVersionCompatNotice.php 3 months ago
Notices.php
602 lines
1 <?php
2
3 namespace WPStaging\Framework\Notices;
4
5 use Exception;
6 use wpdb;
7 use WPStaging\Core\Utils\Logger;
8 use WPStaging\Core\WPStaging;
9 use WPStaging\Framework\Adapter\Directory;
10 use WPStaging\Framework\Assets\Assets;
11 use WPStaging\Framework\CloningProcess\ExcludedPlugins;
12 use WPStaging\Framework\Database\WpOptionsInfo;
13 use WPStaging\Framework\Facades\Hooks;
14 use WPStaging\Framework\Security\Capabilities;
15 use WPStaging\Staging\CloneOptions;
16 use WPStaging\Staging\FirstRun;
17 use WPStaging\Framework\ThirdParty\FreemiusScript;
18 use WPStaging\Framework\ThirdParty\Jetpack;
19 use WPStaging\Framework\ThirdParty\WordFence;
20 use WPStaging\Framework\Traits\NoticesTrait;
21 use WPStaging\Staging\Sites;
22 use WPStaging\Framework\SiteInfo;
23 use WPStaging\Framework\Utils\ServerVars;
24 use WPStaging\Backup\Service\Database\DatabaseImporter;
25 use WPStaging\Framework\Utils\Cache\Cache;
26 use WPStaging\Framework\ThirdParty\Aios;
27
28 /**
29 * Show Admin Notices | Warnings | Messages
30 *
31 * Class Notices
32 * @package WPStaging\Framework\Notices
33 * @todo maybe split this class into multiple classes like staging notices, permission notices etc
34 * to avoid dependency hell without using service locator?
35 */
36 class Notices
37 {
38 use NoticesTrait;
39
40 /** @var string */
41 const ACTION_PRO_NOTICES = 'wpstg.notices.show_pro_notices';
42
43 /** @var string */
44 const ACTION_BASIC_NOTICES = 'wpstg.notices.show_basic_notices';
45
46 /** @var string */
47 const ACTION_INJECT_ANALYTICS_CONSENT_ASSETS = 'wpstg.assets.inject_analytics_consent_assets';
48
49 /** @var string */
50 const ACTION_ADMIN_NOTICES = 'wpstg.admin_notices';
51
52 /** @var string */
53 const ACTION_NETWORK_ADMIN_NOTICES = 'wpstg.network_admin_notices';
54
55 /** @var string */
56 const ACTION_ALL_ADMIN_NOTICES = 'wpstg.all_admin_notices';
57
58 /** @var string */
59 const FILTER_NOTICES_HIDE_DIRECTORY_LISTING_WARNINGS = 'wpstg.notices.hideDirectoryListingWarnings';
60
61 const FILTER_NOTICES_HIDE_MISSING_PRIMARY_KEY_NOTICE = 'wpstg.notices.hideMissingPrimaryKeyNotice';
62
63 /** @var Assets */
64 private $assets;
65
66 /** @var Directory */
67 private $dirUtil;
68
69 /** @var Cache */
70 private $cache;
71
72 /** @var Logger */
73 private $logger;
74
75 /** @var CloneOptions */
76 private $cloneOptions;
77
78 /** @var ExcludedPlugins */
79 private $excludedPlugins;
80
81 /** @var FreemiusScript */
82 private $freemiusScript;
83
84 /** @var WordFence */
85 private $wordfence;
86
87 /** @var DisabledItemsNotice */
88 private $disabledItemsNotice;
89
90 /** @var WarningsNotice */
91 private $warningsNotice;
92
93 /** @var OutdatedWpStagingNotice */
94 private $outdatedWpStagingNotice;
95
96 /** @var ObjectCacheNotice */
97 private $objectCacheNotice;
98
99 /** @var wpdb */
100 private $db;
101
102 /** For testing all notices */
103 const SHOW_ALL_NOTICES = false;
104
105 /**
106 * @var string The key that holds directory listing errors in the container.
107 */
108 public static $directoryListingErrors = 'directoryListingErrors';
109
110 /** @var SiteInfo */
111 private $siteInfo;
112
113 /** @var string */
114 private $viewsNoticesPath;
115
116 /** @var false|mixed|null */
117 private $settings;
118
119 /** @var ServerVars */
120 private $serverVars;
121
122 /** @var bool */
123 private $isWpComSite;
124
125 /** @var WpOptionsInfo */
126 private $wpOptionsInfo;
127
128 /**
129 * @param Assets $assets
130 */
131 public function __construct(Assets $assets)
132 {
133 $this->assets = $assets;
134 $this->viewsNoticesPath = WPSTG_VIEWS_DIR . "notices/";
135
136 // To avoid dependency hell and smooth transition we will be using service locator for below dependencies
137 $this->dirUtil = WPStaging::make(Directory::class);
138 $this->wordfence = WPStaging::make(WordFence::class);
139 $this->cloneOptions = WPStaging::make(CloneOptions::class);
140 $this->freemiusScript = WPStaging::make(FreemiusScript::class);
141 $this->excludedPlugins = WPStaging::make(ExcludedPlugins::class);
142 $this->logger = WPStaging::make("logger");
143 $this->cache = WPStaging::make("cache");
144 $this->db = WPStaging::make('wpdb');
145 $this->wpOptionsInfo = WPStaging::make(WpOptionsInfo::class);
146
147 // Notices
148 $this->disabledItemsNotice = WPStaging::make(DisabledItemsNotice::class);
149 $this->warningsNotice = WPStaging::make(WarningsNotice::class);
150 $this->outdatedWpStagingNotice = WPStaging::make(OutdatedWpStagingNotice::class);
151 $this->objectCacheNotice = WPStaging::make(ObjectCacheNotice::class);
152 $this->siteInfo = WPStaging::make(SiteInfo::class);
153 $this->serverVars = WPStaging::make(ServerVars::class);
154
155 $this->isWpComSite = $this->siteInfo->isHostedOnWordPressCom();
156 }
157
158 /**
159 * Check whether the plugin is pro version
160 *
161 * @return bool
162 */
163 protected function isPro(): bool
164 {
165 return WPStaging::isPro();
166 }
167
168 /**
169 * Load admin notices
170 * @throws Exception
171 */
172 public function renderNotices()
173 {
174 if (!current_user_can(WPStaging::make(Capabilities::class)->manageWPSTG())) {
175 return;
176 }
177
178 $this->settings = get_option('wpstg_settings', []);
179
180 $this->renderNoticesBasicVersion();
181 $this->renderNoticesProVersion();
182 $this->renderNoticesOnAllWpAdminPages();
183 $this->renderNoticesOnWpStagingAdminPages();
184 }
185
186 /**
187 * @return void
188 */
189 private function renderNoticesOnAllWpAdminPages()
190 {
191 $this->noticeListItemsDisabledOnStagingSite();
192 $this->noticeDbHasMissingOrUnexpectedPrimaryKeys();
193 $this->noticeWordFenceHasBeenDisabled();
194 $this->noticeSettingsAreCorrupted();
195 $this->noticeStagingUploadsFolderIsSymlinked();
196 $this->noticeTableTmpPrefixConflictNotice();
197 $this->noticeNextGenEngineStagingSites();
198 $this->showAnalyticsModal();
199 }
200
201 /**
202 * Warn users who may have created staging sites with the now-disabled
203 * Next-Gen engine that those sites can be corrupted (issue #5346).
204 *
205 * @return void
206 */
207 private function noticeNextGenEngineStagingSites()
208 {
209 if (self::SHOW_ALL_NOTICES || WPStaging::make(NextGenEngineNotice::class)->isEnabled()) {
210 require $this->viewsNoticesPath . "next-gen-engine-notice.php";
211 }
212 }
213
214 /**
215 * @return void
216 */
217 private function renderNoticesBasicVersion()
218 {
219 if (!$this->isPro()) {
220 do_action(self::ACTION_BASIC_NOTICES);
221 }
222 }
223
224 /**
225 * @return void
226 */
227 private function renderNoticesProVersion()
228 {
229 if ($this->isPro()) {
230 // This hook is for internal use only. Used in PRO version to display PRO version related notices.
231 do_action(self::ACTION_PRO_NOTICES);
232 }
233 }
234
235 /**
236 * @return void
237 */
238 private function renderNoticesOnWpStagingAdminPages()
239 {
240 if (!current_user_can("update_plugins") || !$this->isWPStagingAdminPage()) {
241 return;
242 }
243
244 $this->noticeUploadsDirIsOutsideAbspath();
245 $this->noticeWpStagingVersionIsOutdated();
246 $this->noticeObjectCachePluginNotRestored();
247 $this->noticeCacheDirectoryNotWriteable();
248 $this->noticeLoggerDirectoryNotWriteable();
249 $this->noticeAbspathDirectoryNotWriteable();
250 $this->noticeHomeAndSiteurlHaveDifferentScheme();
251 $this->noticeWpStagingHooksPluginIsOutdated();
252 $this->noticeMuPluginDirNotWriteable();
253 $this->noticeOptimizerIsDisabled();
254 $this->noticeShowDirectoryListingWarning($this->viewsNoticesPath);
255 $this->noticeDbPrefixDoesNotExist();
256 $this->noticeWPEnginePermalinkWarning();
257 $this->noticeAiosSaltPostfixEnabled();
258 }
259
260 /**
261 * @return void
262 */
263 private function noticeStagingUploadsFolderIsSymlinked()
264 {
265 $uploadsPath = wp_upload_dir()['basedir'];
266 if (self::SHOW_ALL_NOTICES || (is_link($uploadsPath) && $this->siteInfo->isStagingSite())) {
267 require_once $this->viewsNoticesPath . "staging-symlink-enabled-notice.php";
268 }
269 }
270
271 /**
272 * Show warning notice if current site prefix is equal to one of the WPSTG temporary prefixes wpstgtmp_ or wpstgbak_
273 */
274 private function noticeTableTmpPrefixConflictNotice()
275 {
276 $disallowedPrefixes = [DatabaseImporter::TMP_DATABASE_PREFIX_TO_DROP, DatabaseImporter::TMP_DATABASE_PREFIX];
277 if (self::SHOW_ALL_NOTICES || in_array($this->db->prefix, $disallowedPrefixes, true)) {
278 require $this->viewsNoticesPath . "table-tmp-prefix-conflict-notice.php";
279 }
280 }
281
282 /**
283 * Displays the notice that we could not prevent
284 * directory listing on a sensitive folder for some reason.
285 *
286 * @param string $viewsNoticesPath The path to the views folder.
287 * @see \WPStaging\Framework\Filesystem\Filesystem::mkdir The place where all errors are enqueued
288 * to be displayed as a single notice here.
289 *
290 * Note: When refactoring this, keep in mind this code should be
291 * called only once, otherwise the message would be enqueued multiple times.
292 *
293 */
294 private function noticeShowDirectoryListingWarning(string $viewsNoticesPath)
295 {
296 $directoryListingErrors = WPStaging::getInstance()->getContainer()->getFromArray(static::$directoryListingErrors);
297
298 // Early bail: No errors to show
299 if (!self::SHOW_ALL_NOTICES && empty($directoryListingErrors)) {
300 return;
301 }
302
303 // Early bail: These warnings were disabled by the user.
304 if (Hooks::applyFilters(self::FILTER_NOTICES_HIDE_DIRECTORY_LISTING_WARNINGS, false)) {
305 return;
306 }
307
308 require_once "{$viewsNoticesPath}directory-listing-could-not-be-prevented.php";
309 }
310
311 /**
312 * Check if the url scheme of siteurl and home is identical
313 * @return bool
314 */
315 private function isDifferentScheme(): bool
316 {
317 $siteurlScheme = parse_url(get_option('siteurl'), PHP_URL_SCHEME);
318 $homeScheme = parse_url(get_option('home'), PHP_URL_SCHEME);
319
320 return !($siteurlScheme === $homeScheme);
321 }
322
323 /**
324 * Check if the user is using an outdated version of WP Staging Hooks plugin
325 * @return bool
326 */
327 private function isUsingOutdatedWpstgHooksPlugin(): bool
328 {
329 // Minimum version to check
330 $versionToCheck = '0.0.4';
331
332 // Path to WP Staging Hooks plugins in a directory
333 $wpstgHooksPath = 'wp-staging-hooks/wp-staging-hooks.php';
334
335 // Only show notice if plugin exists for above path
336 if (file_exists(WP_PLUGIN_DIR . '/' . $wpstgHooksPath)) {
337 $wpstgHooksData = get_plugin_data(WP_PLUGIN_DIR . '/' . $wpstgHooksPath);
338 // Only show notice if current version is below required min version.
339 return version_compare($wpstgHooksData['Version'], $versionToCheck, '>=') ? false : true;
340 }
341
342 // Path to WP Staging Hooks plugins directly in plugins dir
343 $wpstgHooksPath = 'wp-staging-hooks.php';
344
345 // Only show notice if plugin exists for above path
346 if (file_exists(WP_PLUGIN_DIR . '/' . $wpstgHooksPath)) {
347 $wpstgHooksData = get_plugin_data(WP_PLUGIN_DIR . '/' . $wpstgHooksPath);
348 // Only show notice if current version is below required min version.
349 return version_compare($wpstgHooksData['Version'], $versionToCheck, '>=') ? false : true;
350 }
351
352 return false;
353 }
354
355 /**
356 * Render the notice dismiss action
357 *
358 * @param string $viewsNoticesPath
359 * @param string $wpstgNotice
360 * @param string $cssClassSelectorDismiss
361 * @param string $cssClassSelectorNotice
362 *
363 * @todo Convert to Facade for testability?
364 */
365 public static function renderNoticeDismissAction(string $viewsNoticesPath, $wpstgNotice, $cssClassSelectorDismiss, $cssClassSelectorNotice)
366 {
367 require "{$viewsNoticesPath}_partial/notice_dismiss_action.php";
368 }
369
370 /**
371 * @return void
372 */
373 public function maybeShowElementorCloudNotice()
374 {
375 if (self::SHOW_ALL_NOTICES || ($this->isWPStagingClonePage() && $this->siteInfo->isHostedOnElementorCloud())) {
376 require_once "{$this->viewsNoticesPath}elementor-cloud-notice.php";
377 }
378 }
379
380 /**
381 * @param $settings
382 * @return bool
383 */
384 private function isSettingsCorrupt(): bool
385 {
386 if (!is_array($this->settings) && !is_object($this->settings)) {
387 return true;
388 }
389
390 return false;
391 }
392
393 /**
394 * @return void
395 */
396 private function noticeDbHasMissingOrUnexpectedPrimaryKeys()
397 {
398 if (Hooks::applyFilters(self::FILTER_NOTICES_HIDE_MISSING_PRIMARY_KEY_NOTICE, false)) {
399 return;
400 }
401
402 $optionTable = $this->db->prefix . 'options';
403 $isPrimaryKeyMissing = $this->wpOptionsInfo->isOptionTablePrimaryKeyMissing($optionTable);
404 $isPrimaryKeyIsOptionName = $this->wpOptionsInfo->isPrimaryKeyIsOptionName($optionTable);
405 if (self::SHOW_ALL_NOTICES || (current_user_can("manage_options") && ($isPrimaryKeyMissing || $isPrimaryKeyIsOptionName) && $this->isWPStagingAdminPage())) {
406 require $this->viewsNoticesPath . "wp-options-missing-pk.php";
407 }
408 }
409
410 /**
411 * @return void
412 */
413 private function noticeDbPrefixDoesNotExist()
414 {
415 if (self::SHOW_ALL_NOTICES || empty($this->db->prefix)) {
416 require_once $this->viewsNoticesPath . "no-db-prefix-notice.php";
417 }
418 }
419
420 /**
421 * @return void
422 */
423 private function noticeWPEnginePermalinkWarning()
424 {
425 if (self::SHOW_ALL_NOTICES || class_exists('WPE_API')) {
426 require_once $this->viewsNoticesPath . "wpe-permalink-issue-notice.php";
427 }
428 }
429
430 /**
431 * @param $wpstgSettings
432 * @return void
433 */
434 private function noticeOptimizerIsDisabled()
435 {
436 $wpstgSettings = (object)$this->settings;
437 if (self::SHOW_ALL_NOTICES || empty($wpstgSettings->optimizer)) {
438 require_once $this->viewsNoticesPath . "disabled-optimizer-notice.php";
439 }
440 }
441
442 /**
443 * @return void
444 */
445 private function noticeMuPluginDirNotWriteable()
446 {
447 $varsDirectory = defined('WPMU_PLUGIN_DIR') ? WPMU_PLUGIN_DIR : trailingslashit(WP_CONTENT_DIR) . 'mu-plugins';
448 $wpstgSettings = (object)$this->settings;
449 if (
450 self::SHOW_ALL_NOTICES || (!is_writable($varsDirectory) || !is_readable($varsDirectory))
451 && isset($wpstgSettings->optimizer) && $wpstgSettings->optimizer
452 ) {
453 require $this->viewsNoticesPath . "mu-plugin-directory-permission-problem.php";
454 }
455 }
456
457 /**
458 * @return void
459 */
460 private function noticeWpStagingHooksPluginIsOutdated()
461 {
462 if (self::SHOW_ALL_NOTICES || ($this->isUsingOutdatedWpstgHooksPlugin())) {
463 require_once $this->viewsNoticesPath . "outdated-wp-staging-hooks.php";
464 }
465 }
466
467 /**
468 * @return void
469 */
470 private function noticeHomeAndSiteurlHaveDifferentScheme()
471 {
472 if (self::SHOW_ALL_NOTICES || ($this->isDifferentScheme())) {
473 require_once $this->viewsNoticesPath . "wrong-scheme.php";
474 }
475 }
476
477 /**
478 * @return void
479 */
480 private function noticeAbspathDirectoryNotWriteable()
481 {
482 // Don't show this notice on WP Com Sites
483 if (self::SHOW_ALL_NOTICES || ((!is_writable(ABSPATH)) && !$this->isWpComSite)) {
484 require_once $this->viewsNoticesPath . "staging-directory-permission-problem.php";
485 }
486 }
487
488 /**
489 * @return void
490 */
491 private function noticeLoggerDirectoryNotWriteable()
492 {
493 $logsDir = $this->logger->getLogDir();
494 if (self::SHOW_ALL_NOTICES || (!is_dir($logsDir) || !is_writable($logsDir))) {
495 require_once $this->viewsNoticesPath . "logs-directory-permission-problem.php";
496 }
497 }
498
499 /**
500 * @return void
501 */
502 private function noticeCacheDirectoryNotWriteable()
503 {
504 $cacheDir = $this->cache->getPath();
505 if (self::SHOW_ALL_NOTICES || (!is_dir($cacheDir) || !is_writable($cacheDir))) {
506 require_once $this->viewsNoticesPath . "cache-directory-permission-problem.php";
507 }
508 }
509
510 /**
511 * @return void
512 */
513 private function noticeObjectCachePluginNotRestored()
514 {
515 if (self::SHOW_ALL_NOTICES || ($this->objectCacheNotice->isEnabled())) {
516 require_once $this->viewsNoticesPath . "object-cache-skipped.php";
517 }
518 }
519
520 /**
521 * @return void
522 */
523 private function noticeWpStagingVersionIsOutdated()
524 {
525 /**
526 * Display outdated WP Staging version notice (Free Only)
527 */
528 $this->outdatedWpStagingNotice->showNotice($this->viewsNoticesPath);
529 }
530
531 /**
532 * @return void
533 */
534 private function noticeUploadsDirIsOutsideAbspath()
535 {
536 if (self::SHOW_ALL_NOTICES || (!$this->dirUtil->isPathInWpRoot($this->dirUtil->getUploadsDirectory()) && !$this->siteInfo->isFlywheel() && !$this->isWpComSite)) {
537 require $this->viewsNoticesPath . "uploads-outside-wp-root.php";
538 }
539 }
540
541 /**
542 * @return void
543 */
544 private function noticeSettingsAreCorrupted()
545 {
546 if (self::SHOW_ALL_NOTICES || ($this->isSettingsCorrupt())) {
547 require $this->viewsNoticesPath . "settings_option_corrupt.php";
548 }
549 }
550
551 /**
552 * @return void
553 */
554 private function noticeWordFenceHasBeenDisabled()
555 {
556 $this->wordfence->showNotice($this->viewsNoticesPath);
557 }
558
559 /**
560 * @return void
561 */
562 private function noticeListItemsDisabledOnStagingSite()
563 {
564 // free version has no option to disable outgoing mails
565 $outgoingMailsDisabled = false;
566
567 if ($this->isPro()) {
568 // Check mails disabled against both the old and new way of emails disabled option
569 $outgoingMailsDisabled = $this->cloneOptions->get(FirstRun::MAILS_DISABLED_KEY) || (get_option(FirstRun::MAILS_DISABLED_KEY, false));
570 }
571
572 // Show notice about what disabled in the staging site. (Show only on staging site)
573 if (self::SHOW_ALL_NOTICES || $this->disabledItemsNotice->isEnabled()) {
574 $excludedPlugins = (array)$this->excludedPlugins->getExcludedPlugins();
575 // Show freemius notice if freemius options were deleted during cloning.
576 $freemiusOptionsCleared = $this->freemiusScript->isNoticeEnabled();
577 // Show jetpack staging mode notice if the constant is set on staging site
578 $isJetpackStagingModeActive = defined(Jetpack::STAGING_MODE_CONST) && constant(Jetpack::STAGING_MODE_CONST) === true;
579 $excludedFiles = get_option(Sites::STAGING_EXCLUDED_FILES_OPTION, []);
580 $excludedGoDaddyFiles = get_option(Sites::STAGING_EXCLUDED_GD_FILES_OPTION, []);
581 // use require here instead of require_once otherwise unit tests will always fail,
582 // as this notice is tested multiple times.
583 require $this->viewsNoticesPath . "disabled-items-notice.php";
584 }
585 }
586
587 /**
588 * @return void
589 */
590 private function noticeAiosSaltPostfixEnabled()
591 {
592 $aios = WPStaging::make(Aios::class);
593
594 // Execute it here to prevent this from being executed on each page request and to save db calls.
595 $aios->optimizerWhitelistUpdater();
596
597 if (self::SHOW_ALL_NOTICES || $aios->isSaltPostfixOptionEnabled()) {
598 require $this->viewsNoticesPath . "aios-salt-postfix-enabled.php";
599 }
600 }
601 }
602