| @@ -733,21 +733,38 @@ | ||
| 733 | 733 | betterdocs()->kbmigration->migrate(); |
| 734 | 734 | } |
| 735 | 735 | $_settings = wp_parse_args( $_normalized_settings, $_old_settings ); |
| 736 | 736 | |
| 737 | - // Check if there are actual changes before saving. | |
| 738 | - // update_option returns false when values serialize identically, which can happen | |
| 739 | - // due to object caching or type normalization even when user made changes. | |
| 740 | - $_has_changes = $_settings != $_old_settings; | |
| 737 | + // Detect whether this save actually changes the effective settings. | |
| 738 | + // | |
| 739 | + // The stored option and the submitted payload are normalized differently: | |
| 740 | + // an optional field can be ABSENT from storage yet arrive as '' (e.g. | |
| 741 | + // Feedback URL), and array fields can be stored empty ( [] ) while their | |
| 742 | + // normalized/default form is non-empty (e.g. Instant Answer's | |
| 743 | + // display_ia_texonomy defaults to ['all']). Comparing the raw arrays | |
| 744 | + // ( $_settings != $_old_settings ) therefore reported a phantom change on | |
| 745 | + // every save, leaving the tab perpetually "dirty" and always toasting | |
| 746 | + // "Changes Saved Successfully." instead of "There are no changes to be | |
| 747 | + // saved." — see WPDevelopers/betterdocs-pro#78. | |
| 748 | + // | |
| 749 | + // Compare like-for-like instead: fill defaults on both sides and run both | |
| 750 | + // through the same normalization, so semantically-equal states (absent vs | |
| 751 | + // '', [] vs ['all'], 'on' vs true) collapse to identical values and only a | |
| 752 | + // real edit registers. This is also more reliable than update_option()'s | |
| 753 | + // return, which is false whenever values serialize identically under object | |
| 754 | + // caching or type coercion even when the user did change something (#49). | |
| 755 | + $_defaults = array_merge( $this->get_default(), $this->get_pro_defaults() ); | |
| 756 | + $_old_normalized = $this->get_normalized_values( wp_parse_args( $_old_settings, $_defaults ), $_defaults ); | |
| 757 | + $_new_normalized = $this->get_normalized_values( wp_parse_args( $_settings, $_defaults ), $_defaults ); | |
| 758 | + $_has_changes = $_new_normalized != $_old_normalized; | |
| 741 | 759 | |
| 742 | 760 | $_saved = $this->database->save( $this->base_key, $_settings ); |
| 743 | 761 | |
| 744 | 762 | do_action_ref_array( 'betterdocs::settings::saved', array( $_saved, $_settings, $_old_settings, &$this ) ); |
| 745 | 763 | |
| 746 | - // Return true if save succeeded OR if there were changes to attempt saving. | |
| 747 | - // This handles cases where update_option returns false due to identical serialization | |
| 748 | - // (e.g., object caching, type coercion during serialization). | |
| 749 | - return $_saved || $_has_changes; | |
| 764 | + // The success / no-changes toast reflects whether the user made a real | |
| 765 | + // change, not update_option()'s (unreliable) return value. | |
| 766 | + return $_has_changes; | |
| 750 | 767 | } |
| 751 | 768 | |
| 752 | 769 | public function views( $hook ) { |
| 753 | 770 | return betterdocs()->views->get( 'admin/settings' ); |