| @@ -5,8 +5,10 @@ | ||
| 5 | 5 | |
| 6 | 6 | use Jet_Form_Builder\Classes\Instance_Trait; |
| 7 | 7 | use Jet_Form_Builder\Db_Queries\Execution_Builder; |
| 8 | 8 | use Jet_Form_Builder\Migrations\Versions\Version_3_6_5_2; |
| 9 | +use Jet_Form_Builder\Migrations\Versions\Version_3_6_5_3; | |
| 10 | +use Jet_Form_Builder\Migrations\Versions\Version_3_6_6; | |
| 9 | 11 | |
| 10 | 12 | // If this file is called directly, abort. |
| 11 | 13 | if ( ! defined( 'WPINC' ) ) { |
| 12 | 14 | die; |
| @@ -27,12 +29,18 @@ | ||
| 27 | 29 | * The selected migration classes stay in this registry permanently. Therefore a client |
| 28 | 30 | * may skip the release that introduced `Version_3_6_5_2` and update directly to 3.7.0 (or |
| 29 | 31 | * later): its stored DB version is still older than the running plugin, the auto-migrator |
| 30 | 32 | * runs, and `Base_Migration::install()` executes every selected migration that is not yet |
| 31 | - * recorded in the migrations table. `Version_3_6_5_2` performs only one bounded batch in | |
| 32 | - * this request; a capable admin page processes the remainder through short AJAX requests | |
| 33 | - * using persisted progress, while per-form lazy initialization keeps frontend submissions | |
| 34 | - * working in the meantime. | |
| 33 | + * recorded in the migrations table. `Version_3_6_5_2` and `Version_3_6_5_3` each run a | |
| 34 | + * time-boxed batch loop per request and persist a resume cursor when their time budget is | |
| 35 | + * exceeded; an incomplete pass throws so the migration stays "not installed" and the next | |
| 36 | + * capable admin's `admin_init` request continues it automatically from that cursor — no | |
| 37 | + * AJAX or manual admin-page step is involved. A migration signaling an incomplete batch also | |
| 38 | + * issues its own intermediate `COMMIT` before throwing (to persist the resume cursor), which | |
| 39 | + * ends the transaction this class started; `install_migrations()` therefore stops immediately | |
| 40 | + * on that signal rather than attempting a later migration outside any transaction. The next | |
| 41 | + * capable admin's request retries from the beginning of `migration_instances()`, skipping | |
| 42 | + * whatever already recorded itself as installed, until every migration completes. | |
| 35 | 43 | * |
| 36 | 44 | * @since 3.6.5.2 |
| 37 | 45 | */ |
| 38 | 46 | class Auto_Migrator { |
| @@ -88,8 +96,23 @@ | ||
| 88 | 96 | |
| 89 | 97 | return version_compare( $stored, JET_FORM_BUILDER_VERSION, '<' ); |
| 90 | 98 | } |
| 91 | 99 | |
| 100 | + /** | |
| 101 | + * Whether the auto-migration set (`migration_instances()`) has not yet fully completed | |
| 102 | + * for this site. On a small site this is true for at most a single `admin_init` request; | |
| 103 | + * on a large site it can span several requests while a time-boxed migration resumes | |
| 104 | + * itself batch by batch. Exposed so UI that depends on the migrated data being complete | |
| 105 | + * (e.g. the "Allowed Server-Side Callbacks" settings tab, whose `import_trusted_callbacks()` | |
| 106 | + * only runs once the whole scan finishes) can disable editing and show a wait state | |
| 107 | + * instead of racing a save against a migration batch still in flight. | |
| 108 | + * | |
| 109 | + * @since 3.6.5.3 | |
| 110 | + */ | |
| 111 | + public function is_migration_in_progress(): bool { | |
| 112 | + return $this->needs_upgrade() && ! $this->all_installed(); | |
| 113 | + } | |
| 114 | + | |
| 92 | 115 | protected function run() { |
| 93 | 116 | // Nothing outstanding (e.g. fresh install already stamped by table creation) → |
| 94 | 117 | // just record the version and skip touching the DB in a transaction. |
| 95 | 118 | if ( $this->all_installed() ) { |
| @@ -132,9 +155,20 @@ | ||
| 132 | 155 | * migration is skipped internally if it is already recorded as installed, so keeping |
| 133 | 156 | * an old migration in this list is both safe and required for clients that skip the |
| 134 | 157 | * release where it was introduced. |
| 135 | 158 | * |
| 159 | + * A migration signaling an incomplete batch (`Migration_Incomplete_Exception`) stops the | |
| 160 | + * loop immediately instead of continuing to the next migration. Time-boxed migrations | |
| 161 | + * (e.g. `Version_3_6_5_2`, `Version_3_6_5_3`) persist their own progress by issuing an | |
| 162 | + * intermediate `COMMIT` before throwing, so the outer transaction `run()` started is no | |
| 163 | + * longer intact once one of them yields — a later migration run in that same request | |
| 164 | + * would execute outside any transaction, and a genuine failure in it could no longer be | |
| 165 | + * rolled back. Re-throwing right away still leaves `needs_upgrade()` true (via `run()`'s | |
| 166 | + * catch block skipping `stamp_version()`) so the next capable admin request resumes the | |
| 167 | + * interrupted migration, and only then attempts the ones after it. | |
| 168 | + * | |
| 136 | 169 | * @throws \Jet_Form_Builder\Migrations\Migration_Exception |
| 170 | + * @throws \Jet_Form_Builder\Migrations\Migration_Incomplete_Exception | |
| 137 | 171 | */ |
| 138 | 172 | protected function install_migrations() { |
| 139 | 173 | foreach ( $this->migration_instances() as $migration ) { |
| 140 | 174 | $migration->install(); |
| @@ -160,8 +194,10 @@ | ||
| 160 | 194 | */ |
| 161 | 195 | protected function migration_instances(): array { |
| 162 | 196 | return array( |
| 163 | 197 | new Version_3_6_5_2(), |
| 198 | + new Version_3_6_5_3(), | |
| 199 | + new Version_3_6_6(), | |
| 164 | 200 | ); |
| 165 | 201 | } |
| 166 | 202 | |
| 167 | 203 | protected function stamp_version() { |