PluginProbe ʕ •ᴥ•ʔ
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz / 2.12.6
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz v2.12.6
2.12.6 2.12.5 2.12.4 2.12.3 2.12.2 2.12.1 2.12.0 2.11.1 2.11.0 2.10.1 2.10.0 2.9.1 2.9.0 2.8.2 2.8.1 2.7.0 2.7.1 2.8.0 trunk 0.0.10 0.0.11 0.0.12 0.0.13 0.0.2 0.0.3 0.0.4 0.0.5 0.0.6 0.0.7 0.0.8 0.0.9 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.1.0 1.1.1 1.1.2 1.10.0 1.10.1 1.11.0 1.12.0 1.12.1 1.12.2 1.12.3 1.13.0 1.13.1 1.13.2 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.3.0 1.3.1 1.3.2 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.5.0 1.5.1 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.7.0 1.7.1 1.7.2 1.7.3 1.7.4 1.8.0 1.9.0 1.9.1 2.0.0 2.0.1 2.0.2 2.1.0 2.1.1 2.2.0 2.2.1 2.2.2 2.3.0 2.4.0 2.5.0 2.5.2 2.6.0
sureforms / inc / database / register.php
sureforms / inc / database Last commit date
tables 2 days ago base.php 2 days ago register.php 2 days ago
register.php
312 lines
1 <?php
2 /**
3 * SureForms Database Tables Register Class.
4 *
5 * @link https://sureforms.com
6 * @since 0.0.10
7 * @package SureForms
8 * @author SureForms <https://sureforms.com/>
9 */
10
11 namespace SRFM\Inc\Database;
12
13 use SRFM\Inc\Database\Tables\Entries;
14 use SRFM\Inc\Database\Tables\Payments;
15 use SRFM\Inc\Helper;
16
17 // Exit if accessed directly.
18 defined( 'ABSPATH' ) || exit;
19
20 /**
21 * SureForms Database Tables Register Class
22 *
23 * @since 0.0.13
24 */
25 class Register {
26 /**
27 * Transient caching a healthy entries-table check.
28 *
29 * Only the healthy answer is ever cached. A missing table is re-queried every
30 * time, so the warning disappears the moment the table is back rather than
31 * lingering for the rest of the TTL.
32 *
33 * @since 2.12.6
34 */
35 public const ENTRIES_TABLE_CHECK_TRANSIENT = 'srfm_entries_table_present';
36
37 /**
38 * Per-request memo for the entries-table check.
39 *
40 * The check is consulted from three places in one admin page load (notice
41 * registration, notice rendering, analytics), and this keeps that to a single
42 * transient read.
43 *
44 * @var bool|null
45 * @since 2.12.6
46 */
47 private static $entries_table_present = null;
48
49 /**
50 * Init database registration.
51 *
52 * @since 0.0.13
53 * @return void
54 */
55 public static function init() {
56 /*
57 * ### Here, order is important. ###
58 * 1. Start the DB upgrade which also manages the internal versioning of each tables.
59 * 2. Init create method, which create the table if the table does not exists.
60 * 3. Init maybe_add_new_columns, it only runs if we have new columns definition and DB is upgradable ( has new version ).
61 * 4. Init maybe_rename_columns, it only runs if got any columns to rename and DB is upgradable ( has new version ).
62 * 5. Finally, stop the DB upgrade and update the current version in option table.
63 *
64 * Replaced self::get_db_tables() to static::get_db_tables() for allowing overrides.
65 * @since 1.13.0
66 */
67 foreach ( static::get_db_tables() as $instance ) {
68 $instance->start_db_upgrade();
69
70 if ( $instance->is_db_upgradable() ) {
71 // Only execute below methods if DB is upgradable.
72 $instance->create( $instance->get_columns_definition() );
73 $instance->maybe_add_new_columns( $instance->get_new_columns_definition() );
74 $instance->maybe_rename_columns( $instance->get_columns_to_rename() );
75 }
76
77 // Stop the upgrade process of current table and move to next.
78 $instance->stop_db_upgrade();
79 }
80 }
81
82 /**
83 * Returns an array of instances/objects of our custom tables.
84 *
85 * @since 0.0.13
86 * @return array<string,\SRFM\Inc\Database\Base>
87 */
88 public static function get_db_tables() {
89 return [
90 'entries' => Entries::get_instance(),
91 'payments' => Payments::get_instance(),
92 ];
93 }
94
95 /**
96 * Whether the entries table is missing from the database.
97 *
98 * A dropped table is never recreated on its own: `srfm_database_table_versions`
99 * still records the current version, so start_db_upgrade() marks the table
100 * non-upgradable and create() early-returns. Nothing else in the plugin notices,
101 * and every submission silently fails to save. This is what surfaces that.
102 *
103 * Returns false while the stored version is absent — Register::init() is going
104 * to create the table on this very request, so reporting it missing would show
105 * a "your database needs updating" notice during a perfectly normal install.
106 *
107 * @param bool $force Skip both caches and re-query.
108 * @since 2.12.6
109 * @return bool
110 */
111 public static function is_entries_table_missing( $force = false ) {
112 if ( ! $force && null !== self::$entries_table_present ) {
113 return ! self::$entries_table_present;
114 }
115
116 if ( ! $force && get_transient( self::ENTRIES_TABLE_CHECK_TRANSIENT ) ) {
117 self::$entries_table_present = true;
118 return false;
119 }
120
121 $versions = Helper::get_array_value( get_option( 'srfm_database_table_versions', [] ) );
122
123 // No recorded version means this is a fresh install or an upgrade in
124 // progress; init() creates the table this request. Nothing to warn about.
125 if ( empty( $versions['entries'] ) ) {
126 self::$entries_table_present = true;
127 return false;
128 }
129
130 $present = Entries::get_instance()->table_exists();
131
132 // Cache only the healthy answer. See the transient's docblock.
133 if ( $present ) {
134 set_transient( self::ENTRIES_TABLE_CHECK_TRANSIENT, 1, DAY_IN_SECONDS );
135 // Backfill the owner signature once, so a table created before this plugin
136 // wrote signatures can still be proven ours if the prefix later changes.
137 if ( ! get_option( 'srfm_entries_owner_stamped' ) ) {
138 Entries::get_instance()->stamp_owner_signature();
139 update_option( 'srfm_entries_owner_stamped', 1, false );
140 }
141 } else {
142 delete_transient( self::ENTRIES_TABLE_CHECK_TRANSIENT );
143 }
144
145 self::$entries_table_present = $present;
146
147 return ! $present;
148 }
149
150 /**
151 * Recreate the entries table when it is missing.
152 *
153 * Clearing the stored version is the whole repair: start_db_upgrade() then
154 * treats the table as new, and init() runs the same ordered steps it runs on a
155 * fresh install. Reusing init() rather than re-implementing those steps is
156 * deliberate — the repair path cannot drift from the install path.
157 *
158 * Only the `entries` key is removed. The option is shared with the payments
159 * table and, when Pro is active, three more; clearing the whole option would
160 * force an unrelated re-upgrade pass across all of them from free-plugin code.
161 *
162 * The result is a fresh existence check, never create()'s return value. A
163 * repair that reported success on the strength of create() alone could record
164 * the version again while the table was still missing — which would hide the
165 * problem permanently and be worse than doing nothing.
166 *
167 * @since 2.12.6
168 * @return bool True when the table exists afterwards.
169 */
170 public static function repair_entries_table() {
171 $entries = Entries::get_instance();
172
173 // Prefer adopting the site's own data over manufacturing an empty table.
174 // A changed table prefix leaves the rows behind under the old name, and
175 // creating a fresh table would strand every stored entry while looking like
176 // a successful repair.
177 $adoptable = $entries->find_adoptable_table();
178
179 if ( '' !== $adoptable ) {
180 if ( $entries->table_belongs_to_site( $adoptable ) ) {
181 // Provenance confirms this site owns the table: safe to rename it in.
182 if ( $entries->adopt_table( $adoptable ) ) {
183 self::run_entries_schema_catchup();
184 delete_option( 'srfm_entries_unrecovered' );
185 return ! self::is_entries_table_missing( true );
186 }
187
188 // The table is ours but the rename failed — e.g. the DB user lacks the
189 // privilege RENAME needs, or a metadata-lock timeout. Manufacturing an
190 // empty table over recoverable data and reporting success would be worse
191 // than doing nothing, so refuse and leave a durable trail for support.
192 self::record_unrecovered_entries_table( $adoptable, 'adopt_failed' );
193 return false;
194 }
195
196 // A single same-schema candidate exists but its ownership cannot be
197 // proven. On shared hosting it may belong to an unrelated install, and
198 // renaming it in would destroy that site's data. Leave it untouched,
199 // record it so it can be recovered by hand, then fall back to a fresh
200 // table below.
201 self::record_unrecovered_entries_table( $adoptable, 'unverified' );
202 }
203
204 $versions = Helper::get_array_value( get_option( 'srfm_database_table_versions', [] ) );
205
206 unset( $versions['entries'] );
207 update_option( 'srfm_database_table_versions', $versions );
208
209 static::init();
210
211 return ! self::is_entries_table_missing( true );
212 }
213
214 /**
215 * Drop the cached entries-table detection so the next check re-queries.
216 *
217 * Called when a live entries write fails: a table cached as present earlier in
218 * the day may have just been dropped, and trusting the stale healthy answer
219 * would hide the notice for up to the transient's lifetime.
220 *
221 * @since 2.12.6
222 * @return void
223 */
224 public static function flush_entries_table_cache() {
225 self::$entries_table_present = null;
226 delete_transient( self::ENTRIES_TABLE_CHECK_TRANSIENT );
227 }
228
229 /**
230 * The entries table sitting under a different prefix, if there is one.
231 *
232 * Exposed so the notice can say whether the repair will bring existing entries
233 * back with it or start from empty — the difference matters a great deal to
234 * whoever is about to click the button.
235 *
236 * @since 2.12.6
237 * @return string Full table name, or '' when there is nothing to adopt.
238 */
239 public static function get_adoptable_entries_table() {
240 if ( ! self::is_entries_table_missing() ) {
241 return '';
242 }
243
244 $entries = Entries::get_instance();
245 $candidate = $entries->find_adoptable_table();
246
247 // Only report a candidate the repair would actually adopt — one we can prove
248 // belongs to this site. An unverifiable table is left untouched and a fresh
249 // table created instead, so the notice must not promise to keep its entries.
250 if ( '' === $candidate || ! $entries->table_belongs_to_site( $candidate ) ) {
251 return '';
252 }
253
254 return $candidate;
255 }
256
257 /**
258 * Re-run the install-time schema steps against a just-adopted table.
259 *
260 * An adopted table can carry an older revision of this plugin's schema (a
261 * superset of column names still passes the adoption guard), so reuse the same
262 * ordered create/add-column/rename pass the install path runs rather than
263 * assuming the adopted schema is already current. create() is CREATE TABLE IF
264 * NOT EXISTS, so it is a no-op on the table that is now in place.
265 *
266 * @since 2.12.6
267 * @return void
268 */
269 private static function run_entries_schema_catchup() {
270 $versions = Helper::get_array_value( get_option( 'srfm_database_table_versions', [] ) );
271
272 unset( $versions['entries'] );
273 update_option( 'srfm_database_table_versions', $versions );
274
275 static::init();
276 }
277
278 /**
279 * Record a table that holds recoverable entries but was not adopted.
280 *
281 * Fired when a candidate is found that either cannot be proven to belong to
282 * this site or could not be renamed in. Persists the name and reason so a
283 * support engineer can find and reconnect it by hand, rather than letting it
284 * be discarded silently the moment a fresh empty table exists.
285 *
286 * @param string $table Full name of the table left in place.
287 * @param string $reason 'unverified' or 'adopt_failed'.
288 * @since 2.12.6
289 * @return void
290 */
291 private static function record_unrecovered_entries_table( $table, $reason ) {
292 update_option(
293 'srfm_entries_unrecovered',
294 [
295 'table' => $table,
296 'reason' => $reason,
297 'time' => time(),
298 ],
299 false
300 );
301
302 /**
303 * Fires when entries data was found but not automatically recovered.
304 *
305 * @param string $table Full name of the table left in place.
306 * @param string $reason 'unverified' or 'adopt_failed'.
307 * @since 2.12.6
308 */
309 do_action( 'srfm_entries_table_unrecovered', $table, $reason );
310 }
311 }
312