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