diff --git a/escalated.php b/escalated.php index daef0de..395df0c 100644 --- a/escalated.php +++ b/escalated.php @@ -62,5 +62,6 @@ static function (string $class): void { register_deactivation_hook(__FILE__, [\Escalated\Deactivator::class, 'deactivate']); add_action('plugins_loaded', function () { + \Escalated\Activator::maybe_upgrade(); \Escalated\Escalated::instance()->boot(); }); diff --git a/includes/class-activator.php b/includes/class-activator.php index c7ffa64..44046ed 100644 --- a/includes/class-activator.php +++ b/includes/class-activator.php @@ -23,6 +23,23 @@ public static function activate(): void flush_rewrite_rules(); } + /** + * Re-run `activate()` when the stored plugin version differs from the current one. + * + * WordPress does not fire activation hooks on auto-update or on manual + * upload-overwrite upgrades, so existing installs can end up on new code + * without the schema/permission seed having run. Every step inside + * `activate()` is idempotent (dbDelta, upsert loops, existence guards), + * so re-running on version change is safe. + */ + public static function maybe_upgrade(): void + { + if (get_option('escalated_version') === ESCALATED_VERSION) { + return; + } + self::activate(); + } + /** * Create all 21 database tables using dbDelta. */ diff --git a/tests/Test_Activator.php b/tests/Test_Activator.php index cdde51a..1a691b5 100644 --- a/tests/Test_Activator.php +++ b/tests/Test_Activator.php @@ -315,4 +315,63 @@ public function test_version_option_set(): void { $this->assertEquals(ESCALATED_VERSION, get_option('escalated_version')); } + + /** + * maybe_upgrade() is a no-op when the stored version matches current. + * + * Proof: if activate() re-ran, insert_default_settings() would re-insert + * the 'ticket_reference_prefix' row. Deleting it and then calling + * maybe_upgrade() with a matching version must leave it deleted. + */ + public function test_maybe_upgrade_noop_when_version_matches(): void + { + global $wpdb; + $settings_table = $wpdb->prefix.'escalated_settings'; + + $wpdb->delete($settings_table, ['option_key' => 'ticket_reference_prefix']); + $this->assertNull(\Escalated\Models\Setting::get('ticket_reference_prefix')); + + Activator::maybe_upgrade(); + + $this->assertNull( + \Escalated\Models\Setting::get('ticket_reference_prefix'), + 'activate() must not have re-run' + ); + } + + /** + * maybe_upgrade() re-runs activate() when the stored version is stale. + */ + public function test_maybe_upgrade_reactivates_when_version_differs(): void + { + global $wpdb; + $settings_table = $wpdb->prefix.'escalated_settings'; + + update_option('escalated_version', '0.0.0-stale'); + $wpdb->delete($settings_table, ['option_key' => 'ticket_reference_prefix']); + $this->assertNull(\Escalated\Models\Setting::get('ticket_reference_prefix')); + + Activator::maybe_upgrade(); + + $this->assertEquals('ESC', \Escalated\Models\Setting::get('ticket_reference_prefix')); + $this->assertEquals(ESCALATED_VERSION, get_option('escalated_version')); + } + + /** + * maybe_upgrade() reactivates on a fresh install where the option is missing. + */ + public function test_maybe_upgrade_reactivates_when_version_missing(): void + { + global $wpdb; + $settings_table = $wpdb->prefix.'escalated_settings'; + + delete_option('escalated_version'); + $wpdb->delete($settings_table, ['option_key' => 'ticket_reference_prefix']); + $this->assertNull(\Escalated\Models\Setting::get('ticket_reference_prefix')); + + Activator::maybe_upgrade(); + + $this->assertEquals('ESC', \Escalated\Models\Setting::get('ticket_reference_prefix')); + $this->assertEquals(ESCALATED_VERSION, get_option('escalated_version')); + } }