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