PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / trunk
Search Atlas SEO – OTTO AI SEO Automation for WordPress vtrunk
2.6.26 2.6.25 2.6.24 2.6.23 2.6.22 2.6.21 2.6.20 2.6.19 2.6.18 2.6.17 2.6.16 2.6.15 2.6.14 2.6.13 2.6.12 2.6.11 2.6.10 2.6.9 2.6.8 2.6.7 2.6.6 2.6.5 2.6.4 2.6.3 2.5.23 All 138 releases
← All changes | MetaSyncDebug.php +485 -44 2.6.23trunk View file →
@@ -77,8 +77,13 @@
77 77 */
78 78 const ANCHOR_EOF = 'EOF';
79 79
80 80 /**
81 + * Age in seconds before a stranded .metasync-tmp-* copy is safe to delete.
82 + */
83 + const TEMP_MAX_AGE = 300;
84 +
85 + /**
81 86 * Path to the wp-config.php file.
82 87 *
83 88 * @var string
84 89 */
@@ -412,10 +417,20 @@
412 417 return $configs;
413 418 }
414 419
415 420 /**
416 - * Saves new contents to the wp-config.php file.
421 + * Saves new contents to the wp-config.php file via an atomic temp-file + rename() write.
417 422 *
423 + * No persistent backup copy of wp-config.php is ever left in the web root. The new
424 + * contents are written to a non-guessable, 0600 temp file alongside the real config
425 + * file and then rename()'d over it, which is atomic on the same filesystem. The
426 + * original file is never modified until that rename succeeds, so on any failure it is
427 + * left intact and no on-disk backup or rollback write is needed.
428 + *
429 + * Symlinks are resolved first so a symlinked wp-config.php keeps pointing at its real
430 + * target instead of being replaced by a regular file, and the original mode and
431 + * ownership are reapplied to the newly renamed file.
432 + *
418 433 * @throws WPConfigFileEmptyException If the config file content provided is empty.
419 434 * @throws WPConfigSaveException If there is a failure when saving the wp-config.php file.
420 435 *
421 436 * @param string $contents New config contents.
@@ -431,52 +446,199 @@
431 446 if ($contents === $this->wpConfigSrc) {
432 447 return false;
433 448 }
434 449
435 - // Create backup before modifying wp-config.php
436 - $backupPath = $this->wpConfigPath . '.metasync-backup-' . time();
437 - if (!copy($this->wpConfigPath, $backupPath)) {
438 - throw new WPConfigSaveException('Failed to create backup of wp-config.php');
450 + // Resolve symlinks and write to the real file. Replacing the path directly
451 + // would swap a symlinked wp-config.php for a regular file, which on sites that
452 + // deliberately keep the real config outside the web root would drop a full
453 + // credentials file into the web root - the opposite of the intent here.
454 + // Resolving also keeps the temp file on the same filesystem as the target,
455 + // which rename() needs in order to stay atomic.
456 + $targetPath = @realpath($this->wpConfigPath);
457 + if (false === $targetPath) {
458 + $targetPath = $this->wpConfigPath;
439 459 }
440 460
441 - $result = file_put_contents($this->wpConfigPath, $contents, LOCK_EX);
461 + $dir = dirname($targetPath);
442 462
443 - if (false === $result) {
444 - // Restore from backup on failure
445 - copy($backupPath, $this->wpConfigPath);
446 - unlink($backupPath);
447 - throw new WPConfigSaveException('Failed to update the config file.');
463 + // The atomic path needs a writable directory, while the constructor only
464 + // guarantees a writable file. WordPress' recommended "wp-config.php one level
465 + // above the web root" layout commonly has exactly that shape, and earlier
466 + // versions wrote fine there, so fall back rather than refusing to save.
467 + if (!is_writable($dir)) {
468 + // Logged because the fallback gives up atomicity: a crash mid-write can
469 + // leave wp-config.php short, so affected hosts should be identifiable.
470 + error_log('MetaSync: ' . $dir . ' is not writable; writing wp-config.php in place'
471 + . ' (non-atomic) instead of via an atomic replace.');
472 + return $this->saveInPlace($targetPath, $contents);
448 473 }
449 474
450 - // Clean up old backups (keep last 5)
451 - $this->cleanupOldBackups();
475 + // Reap copies stranded by an earlier hard kill. A fatal, OOM or timeout between
476 + // fopen() and rename() skips the finally block below and leaves a 0600 copy of
477 + // wp-config.php next to it; nothing else would ever remove it.
478 + $this->reapStaleTempFiles($dir);
452 479
480 + $originalPerms = @fileperms($targetPath) & 0777;
481 + $originalOwner = @fileowner($targetPath);
482 + $originalGroup = @filegroup($targetPath);
483 +
484 + // Non-guessable temp path in the same directory so rename() stays atomic.
485 + $tempFile = $dir . '/.metasync-tmp-' . bin2hex(random_bytes(8));
486 +
487 + try {
488 + // 'x' mode fails if the path already exists, so we never clobber another file.
489 + $handle = @fopen($tempFile, 'x');
490 + if (false === $handle) {
491 + // The directory looked writable but the create failed anyway.
492 + return $this->saveInPlace($targetPath, $contents);
493 + }
494 +
495 + // Lock the temp file down before any sensitive content is written to it.
496 + $permissionsLocked = @chmod($tempFile, 0600);
497 + $permissionsWrong = PHP_OS_FAMILY !== 'Windows'
498 + && ((@fileperms($tempFile) & 0777) !== 0600);
499 + if (!$permissionsLocked || $permissionsWrong) {
500 + @fclose($handle);
501 + throw new WPConfigSaveException('Could not secure the temporary config file.');
502 + }
503 +
504 + $written = @fwrite($handle, $contents);
505 + $flushed = @fflush($handle);
506 +
507 + if (function_exists('fsync')) {
508 + @fsync($handle);
509 + }
510 +
511 + $closed = @fclose($handle);
512 + clearstatcache(true, $tempFile);
513 +
514 + // fwrite() can report a full write while the error only surfaces later at
515 + // flush or close time (a full disk, NFS, EIO). Confirm the bytes actually
516 + // landed before this file is allowed to replace a working wp-config.php.
517 + if (
518 + false === $written
519 + || strlen($contents) !== $written
520 + || false === $flushed
521 + || false === $closed
522 + || @filesize($tempFile) !== strlen($contents)
523 + ) {
524 + throw new WPConfigSaveException('Failed to update the config file.');
525 + }
526 +
527 + // Atomically move the temp file over the config file. The original is only
528 + // ever replaced by this single call; on failure it remains untouched.
529 + if (!@rename($tempFile, $targetPath)) {
530 + throw new WPConfigSaveException('Failed to update the config file.');
531 + }
532 + } finally {
533 + // A thrown failure, fatal or timeout must never leave a readable copy of
534 + // wp-config.php sitting next to it.
535 + clearstatcache(true, $tempFile);
536 + if (is_file($tempFile)) {
537 + @unlink($tempFile);
538 + }
539 + }
540 +
541 + // rename() installs a new inode, so the original ownership and mode do not
542 + // carry over. Restore both - ownership best-effort, since only a privileged
543 + // process may change it - so the file keeps the identity the host expects.
544 + if (PHP_OS_FAMILY !== 'Windows' && false !== $originalOwner && !@chown($targetPath, $originalOwner)) {
545 + // Common on hosts where wp-config.php is owned by a deploy user and only
546 + // group-writable by the web user: the file is now owned by the web user and
547 + // an unprivileged process cannot hand it back. Deploy tooling may lose write
548 + // access, so make it findable rather than silent.
549 + error_log('MetaSync: wp-config.php is no longer owned by uid ' . $originalOwner
550 + . ' after being rewritten, and ownership could not be restored.');
551 + }
552 +
553 + if (PHP_OS_FAMILY !== 'Windows' && false !== $originalGroup) {
554 + @chgrp($targetPath, $originalGroup);
555 + }
556 +
557 + if ($originalPerms) {
558 + @chmod($targetPath, $originalPerms);
559 + }
560 +
453 561 return true;
454 562 }
455 563
456 564 /**
457 - * Clean up old wp-config backup files, keeping only the last 5
565 + * Deletes .metasync-tmp-* files older than the safety window.
458 566 *
567 + * Each one is a full copy of wp-config.php, so they must not accumulate. Only files
568 + * older than TEMP_MAX_AGE are touched, so a save running concurrently in another
569 + * request never has its temp file pulled out from under it.
570 + *
571 + * @param string $dir Directory holding the config file.
572 + *
459 573 * @return void
460 574 */
461 - protected function cleanupOldBackups()
575 + protected function reapStaleTempFiles($dir)
462 576 {
463 - $configDir = dirname($this->wpConfigPath);
464 - $backupPattern = basename($this->wpConfigPath) . '.metasync-backup-*';
465 - $backups = glob($configDir . '/' . $backupPattern);
577 + foreach ((array) glob($dir . '/.metasync-tmp-*') as $temp) {
578 + if (!is_file($temp)) {
579 + continue;
580 + }
466 581
467 - if (count($backups) > 5) {
468 - // Sort by modification time (oldest first)
469 - usort($backups, function ($a, $b) {
470 - return filemtime($a) - filemtime($b);
471 - });
582 + $mtime = @filemtime($temp);
583 + if (false === $mtime || abs(time() - $mtime) < self::TEMP_MAX_AGE) {
584 + continue;
585 + }
472 586
473 - // Delete oldest backups, keep last 5
474 - $toDelete = array_slice($backups, 0, count($backups) - 5);
475 - foreach ($toDelete as $oldBackup) {
476 - unlink($oldBackup);
587 + @unlink($temp);
588 + }
589 + }
590 +
591 + /**
592 + * Writes the config file in place, keeping its inode, ownership and mode.
593 + *
594 + * Used when the config file is writable but its directory is not, so the atomic
595 + * temp-file + rename() path is unavailable. No second copy of the file is created,
596 + * so this does not reintroduce the web-root credential exposure; it trades
597 + * atomicity for still working on hosts where the directory cannot be written.
598 + *
599 + * Because an in-place write can genuinely truncate the file, this is the one path
600 + * where restoring the original contents from memory is the correct recovery.
601 + *
602 + * @throws WPConfigSaveException If the write fails or the file is short afterwards.
603 + *
604 + * @param string $targetPath Path to the config file.
605 + * @param string $contents New config contents.
606 + *
607 + * @return bool
608 + */
609 + protected function saveInPlace($targetPath, $contents)
610 + {
611 + $written = @file_put_contents($targetPath, $contents, LOCK_EX);
612 + clearstatcache(true, $targetPath);
613 +
614 + if (
615 + false === $written
616 + || strlen($contents) !== $written
617 + || @filesize($targetPath) !== strlen($contents)
618 + ) {
619 + // The file may have been left short, so put the known-good source back.
620 + if ('' !== trim($this->wpConfigSrc)) {
621 + $restored = @file_put_contents($targetPath, $this->wpConfigSrc, LOCK_EX);
622 + clearstatcache(true, $targetPath);
623 +
624 + // A false return also fails this comparison, so it covers both cases.
625 + if (strlen($this->wpConfigSrc) !== $restored) {
626 + // Both the write and the rollback failed, so the file on disk is
627 + // very likely truncated and the site will not boot. Say so plainly -
628 + // there is deliberately no backup copy to restore from.
629 + error_log('MetaSync: wp-config.php may be truncated at ' . $targetPath
630 + . ' - restore it manually.');
631 + throw new WPConfigSaveException(
632 + 'Failed to update the config file, and wp-config.php may now be incomplete. Please check it.'
633 + );
634 + }
477 635 }
636 +
637 + throw new WPConfigSaveException('Failed to update the config file.');
478 638 }
639 +
640 + return true;
479 641 }
480 642 }
481 643
482 644 class ConfigControllerMetaSync
@@ -486,8 +648,35 @@
486 648
487 649 protected $optionKey = 'debuglogconfigtool_updated_constant';
488 650 public $debugConstants = ['WP_DEBUG', 'WP_DEBUG_LOG', 'SCRIPT_DEBUG'];
489 651 protected $configFileManager;
652 +
653 + /**
654 + * Reason wp-config.php cannot be updated, empty string when it can.
655 + *
656 + * @var string
657 + */
658 + protected $configError = '';
659 +
660 + /**
661 + * True when no anchor could be found to insert new constants after.
662 + *
663 + * @var bool
664 + */
665 + protected $anchorMissing = false;
666 +
667 + /**
668 + * True when the last store() changed the file but did not apply every constant.
669 + *
670 + * Each constant is written by its own save(), so a run can land one and fail the
671 + * next. Callers must not roll their tracking state back to "off" in that case: the
672 + * constants that did land are live, and a state of "off" hides the dashboard widget
673 + * and makes the auto-disable cron skip, leaving debug on with nothing to clear it.
674 + *
675 + * @var bool
676 + */
677 + protected $partialWrite = false;
678 +
490 679 private static $configArgs = [
491 680 'normalize' => true,
492 681 'raw' => true,
493 682 'add' => true,
@@ -497,38 +686,133 @@
497 686 {
498 687 $this->initialize();
499 688 }
500 689
690 + /**
691 + * Prepares the config file manager.
692 + *
693 + * Deliberately never throws. Callers construct this class without a try/catch, so
694 + * anything escaping here would be an uncaught fatal that takes the admin page down.
695 + * Any problem is recorded instead and every public method degrades gracefully.
696 + *
697 + * @return void
698 + */
501 699 private function initialize()
502 700 {
503 701 self::$configfilePath = $this->getConfigFilePath();
702 +
703 + // $configArgs is static, so clear any anchor a previous instance left behind.
704 + // A stale anchor from a differently-shaped config file silently suppresses
705 + // writes to this one.
706 + unset(self::$configArgs['anchor'], self::$configArgs['placement']);
707 + $this->anchorMissing = false;
708 +
709 + if (!is_string(self::$configfilePath) || '' === self::$configfilePath || !file_exists(self::$configfilePath)) {
710 + $this->setConfigError('wp-config.php could not be located.');
711 + return;
712 + }
713 +
504 714 // Set anchor for the constants to write
505 - $configContents = file_get_contents(self::$configfilePath);
715 + $configContents = @file_get_contents(self::$configfilePath);
716 + if (!is_string($configContents)) {
717 + $this->setConfigError('wp-config.php could not be read.');
718 + return;
719 + }
720 +
506 721 if (false === strpos($configContents, "/* That's all, stop editing!")) {
507 722 preg_match('@\$table_prefix = (.*);@', $configContents, $matches);
508 - self::$configArgs['anchor'] = $matches[0] ?? '';
723 + $anchor = $matches[0] ?? '';
724 +
725 + // With no anchor, add() cannot position a new constant and silently changes
726 + // nothing. Existing constants still update fine, so record it for the
727 + // failure message rather than refusing to work at all.
728 + $this->anchorMissing = ('' === $anchor);
729 +
730 + self::$configArgs['anchor'] = $anchor;
509 731 self::$configArgs['placement'] = 'after';
510 732 }
511 733
512 734 if (!is_writable(self::$configfilePath)) {
513 - add_action('admin_notices', function () {
514 - $class = 'notice notice-error is-dismissible';
515 - $message = 'Config file not writable';
516 - printf('<div class="%1$s"><p>%2$s</p></div>', esc_attr($class), $message);
517 - });
735 + $this->setConfigError('Config file not writable');
518 736 return;
519 737 }
520 738
521 - $this->configFileManager = new WPConfigTransformerMetaSync(self::$configfilePath);
739 + try {
740 + $this->configFileManager = new WPConfigTransformerMetaSync(self::$configfilePath);
741 + } catch (\Throwable $e) {
742 + $this->configFileManager = null;
743 + $this->setConfigError($e->getMessage());
744 + }
522 745 }
523 746
747 + /**
748 + * Records why wp-config.php cannot be updated and surfaces it in the admin.
749 + *
750 + * @param string $message Reason to display.
751 + *
752 + * @return void
753 + */
754 + protected function setConfigError($message)
755 + {
756 + $this->configError = (string) $message;
757 +
758 + if (!function_exists('add_action')) {
759 + return;
760 + }
761 +
762 + $notice = $this->configError;
763 + add_action('admin_notices', function () use ($notice) {
764 + printf(
765 + '<div class="%1$s"><p>%2$s</p></div>',
766 + esc_attr('notice notice-error is-dismissible'),
767 + esc_html($notice)
768 + );
769 + });
770 + }
771 +
772 + /**
773 + * Whether wp-config.php can be updated.
774 + *
775 + * @return bool
776 + */
777 + public function isReady()
778 + {
779 + return $this->configFileManager instanceof WPConfigTransformerMetaSync;
780 + }
781 +
782 + /**
783 + * Reason wp-config.php cannot be updated, empty string when it can.
784 + *
785 + * @return string
786 + */
787 + public function getConfigError()
788 + {
789 + return $this->configError;
790 + }
791 +
792 + /**
793 + * Writes the debug constants into wp-config.php.
794 + *
795 + * @return bool True when the constants were written, false when they could not be.
796 + */
524 797 public function store()
525 798 {
799 + $this->partialWrite = false;
800 +
801 + if (!$this->isReady()) {
802 + error_log('MetaSync: skipped wp-config.php debug constants - '
803 + . ($this->configError !== '' ? $this->configError : 'the file is not writable.'));
804 + return false;
805 + }
806 +
807 + $contentsBefore = @file_get_contents(self::$configfilePath);
808 +
526 809 try {
527 810 // Whitelist of allowed constants to prevent arbitrary constant modification
528 811 $allowedConstants = ['WP_DEBUG', 'WP_DEBUG_LOG', 'WP_DEBUG_DISPLAY'];
529 812
530 813 $updatedConstants = [];
814 + $unwritten = [];
531 815 $wpDebugEnabled = get_option('wp_debug_enabled', 'false');
532 816 $wpDebugLogEnabled = get_option('wp_debug_log_enabled', 'false');
533 817 $wpDebugDisplayEnabled = get_option('wp_debug_display_enabled', 'false');
534 818 $constants = [
@@ -569,33 +853,185 @@
569 853 $value = $value ? 'true' : 'false';
570 854
571 855 $this->configFileManager->update('constant', $key, $value, self::$configArgs);
572 856 $updatedConstants[] = $constant;
857 +
858 + // update() can no-op silently - most notably when no anchor was found, so
859 + // a new constant has nowhere to be inserted - so confirm the file really
860 + // holds the wanted value rather than assuming the call worked.
861 + if (!$this->constantReflectsState($key, $value)) {
862 + $unwritten[] = $key;
863 + }
573 864 }
574 - } catch (\Exception $e) {
865 +
866 + if (!empty($unwritten)) {
867 + // Each constant has its own save(), so an earlier one may already be on
868 + // disk. Record that so the caller keeps its state consistent with the file
869 + // instead of reporting "off" over live constants.
870 + $contentsAfter = @file_get_contents(self::$configfilePath);
871 + $this->partialWrite = ($contentsBefore !== $contentsAfter);
872 +
873 + // Bytes only answer "did we change the file". The question that matters is
874 + // "is debug live in it": a constant already at the enabling value makes
875 + // update() a no-op, so nothing changes on disk yet debug is still on. The
876 + // caller must not report "off" over that either.
877 + if (!$this->partialWrite) {
878 + foreach ($constants as $liveCheck) {
879 + $liveKey = strtoupper(sanitize_key($liveCheck['name']));
880 + if ($this->constantReflectsState($liveKey, 'true')) {
881 + $this->partialWrite = true;
882 + break;
883 + }
884 + }
885 + }
886 +
887 + $message = 'wp-config.php was not updated for: ' . implode(', ', $unwritten) . '.';
888 + if ($this->anchorMissing) {
889 + $message .= ' No place to insert new constants was found in wp-config.php.';
890 + }
891 + if ($this->partialWrite) {
892 + $message .= ' Debug constants are still live in wp-config.php, so it is'
893 + . ' partially updated.';
894 + }
895 +
896 + error_log('MetaSync: ' . $message);
897 + $this->setConfigError($message);
898 + return false;
899 + }
900 +
901 + return true;
902 + } catch (\Throwable $e) {
903 + // Throwable rather than Exception: an Error here would otherwise be fatal.
904 + // Both callers are ordinary form/REST requests, so report the failure back
905 + // to them instead of emitting JSON and dying part-way through the page.
906 + $contentsAfter = @file_get_contents(self::$configfilePath);
907 + $this->partialWrite = ($contentsBefore !== $contentsAfter);
908 +
909 + // A failed update may happen after an earlier constant was already live, or
910 + // after update() no-oped because a constant was already enabled. Bytes alone
911 + // cannot distinguish those cases, so keep the caller's state aligned with the
912 + // constants that are actually active in wp-config.php.
913 + if (!$this->partialWrite) {
914 + foreach (['WP_DEBUG', 'WP_DEBUG_LOG', 'WP_DEBUG_DISPLAY'] as $liveKey) {
915 + if ($this->constantReflectsState($liveKey, 'true')) {
916 + $this->partialWrite = true;
917 + break;
918 + }
919 + }
920 + }
921 +
575 922 error_log('MetaSync: Error updating wp-config.php - ' . $e->getMessage());
576 - wp_send_json_error([
577 - 'message' => $e->getMessage(),
578 - 'success' => false
579 - ]);
923 + $this->setConfigError('Could not update wp-config.php: ' . $e->getMessage());
924 + return false;
580 925 }
581 926 }
582 927
928 + /**
929 + * Whether the last store() left wp-config.php partially updated.
930 + *
931 + * @return bool
932 + */
933 + public function hadPartialWrite()
934 + {
935 + return $this->partialWrite;
936 + }
937 +
938 + /**
939 + * Whether wp-config.php now reflects the wanted state for a constant.
940 + *
941 + * Re-reads the file so the result is what was actually persisted rather than what the
942 + * writer was asked to do. Note the writer expresses "off" by removing the constant
943 + * rather than defining it false, so an absent constant satisfies a 'false' target.
944 + *
945 + * @param string $name Constant name.
946 + * @param string $expected Expected raw value, 'true' or 'false'.
947 + *
948 + * @return bool
949 + */
950 + protected function constantReflectsState($name, $expected)
951 + {
952 + if (!$this->isReady()) {
953 + return false;
954 + }
955 +
956 + $wantEnabled = ('true' === strtolower(trim($expected)));
957 +
958 + try {
959 + if (!$this->configFileManager->exists('constant', $name)) {
960 + return !$wantEnabled;
961 + }
962 +
963 + $actual = $this->configFileManager->getValue('constant', $name);
964 + } catch (\Throwable $e) {
965 + return false;
966 + }
967 +
968 + if (!is_scalar($actual)) {
969 + return false;
970 + }
971 +
972 + return strtolower(trim((string) $actual)) === strtolower(trim($expected));
973 + }
974 +
975 + /**
976 + * Whether a constant is defined in wp-config.php.
977 + *
978 + * @param string $constant Constant name.
979 + *
980 + * @return bool False when wp-config.php cannot be read.
981 + */
583 982 public function exists($constant)
584 983 {
585 - return $this->configFileManager->exists('constant', strtoupper($constant));
984 + if (!$this->isReady()) {
985 + return false;
986 + }
987 +
988 + try {
989 + return $this->configFileManager->exists('constant', strtoupper($constant));
990 + } catch (\Throwable $e) {
991 + error_log('MetaSync: Error reading wp-config.php - ' . $e->getMessage());
992 + return false;
993 + }
586 994 }
587 995
996 + /**
997 + * Reads a constant's value from wp-config.php.
998 + *
999 + * @param string $constant Constant name.
1000 + *
1001 + * @return mixed|null Null when absent or wp-config.php cannot be read.
1002 + */
588 1003 public function getValue($constant)
589 1004 {
590 - if ($this->exists(strtoupper($constant))) {
591 - return $this->configFileManager->getValue('constant', strtoupper($constant));
1005 + if (!$this->isReady()) {
1006 + return null;
592 1007 }
1008 +
1009 + try {
1010 + if ($this->configFileManager->exists('constant', strtoupper($constant))) {
1011 + return $this->configFileManager->getValue('constant', strtoupper($constant));
1012 + }
1013 + } catch (\Throwable $e) {
1014 + error_log('MetaSync: Error reading wp-config.php - ' . $e->getMessage());
1015 + }
1016 +
593 1017 return null;
594 1018 }
595 1019
1020 + /**
1021 + * Updates a single constant in wp-config.php.
1022 + *
1023 + * @param string $key Constant name.
1024 + * @param mixed $value Constant value.
1025 + *
1026 + * @return bool False when the constant could not be written.
1027 + */
596 1028 public function update($key, $value)
597 1029 {
1030 + if (!$this->isReady()) {
1031 + return false;
1032 + }
1033 +
598 1034 try {
599 1035 // By default, when attempting to update a config that doesn't exist, one will be added.
600 1036 $option = self::$configArgs;
601 1037 if (is_bool($value)) {
@@ -601,9 +1037,10 @@
601 1037 if (is_bool($value)) {
602 1038 $value = $value ? 'true' : 'false';
603 1039 }
604 1040 return $this->configFileManager->update('constant', strtoupper($key), $value, $option);
605 - } catch (\Exception $e) {
1041 + } catch (\Throwable $e) {
1042 + error_log('MetaSync: Error updating wp-config.php - ' . $e->getMessage());
606 1043 return false;
607 1044 }
608 1045 }
609 1046
@@ -626,8 +1063,12 @@
626 1063 * @return void
627 1064 */
628 1065 protected function maybeRemoveDeletedConstants($constants)
629 1066 {
1067 + if (!$this->isReady()) {
1068 + return;
1069 + }
1070 +
630 1071 $deletedConstant = array_diff(array_column($constants, 'name'), array_column($constants, 'name'));
631 1072
632 1073 foreach ($deletedConstant as $item) {
633 1074 $this->configFileManager->remove('constant', strtoupper($item));