feat: add field group disable and refactor table to WP tables#592
feat: add field group disable and refactor table to WP tables#592Soare-Robert-Daniel wants to merge 8 commits intodevelopmentfrom
Conversation
|
Tip You can preview the changes in the Playground Plugin build for 1e062a2 is ready 🛎️!
|
There was a problem hiding this comment.
Pull request overview
This PR modernizes the PPOM “Field Groups” admin screen by migrating the legacy custom/DataTables table to a native WP_List_Table, and introduces a reversible enable/disable toggle for field groups that hides disabled groups on the frontend without removing their attachments or schema.
Changes:
- Replace the Field Groups listing UI with a
WP_List_Tableimplementation (search/sort/pagination/bulk actions). - Add a
productmeta_disabledDB column plus repository/admin/AJAX plumbing to enable/disable groups, and filter disabled groups out inPPOM_Meta. - Add unit + Playwright E2E coverage for the new toggle behavior and update existing E2E flows to the new “Add Group” UI.
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| woocommerce-product-addon.php | Bumps DB version for schema update. |
| classes/class-ppom-meta-repository.php | Adds productmeta_disabled column + paging/searching methods + toggle update APIs. |
| classes/ppom.class.php | Filters disabled groups out of settings() / get_fields() resolution. |
| src/Admin/MetaGroupsListTable.php | New WP_List_Table for Field Groups (columns, bulk actions, paging/search/sort). |
| templates/admin/existing-meta.php | Replaces legacy table markup with list table rendering + search form. |
| js/admin/ppom-meta-table.js | Removes DataTables wiring; adds per-row AJAX toggle + bulk delete confirmation + modal trigger delegation. |
| src/Admin/Manager.php | Adds AJAX endpoint for toggle; updates product list column to show “Disabled” badge. |
| inc/admin.php | Adds AJAX wrapper function for toggle handler. |
| classes/plugin.class.php | Registers wp_ajax_ppom_toggle_meta_disabled. |
| classes/admin.class.php | Instantiates list table early on load; adds Screen Option persistence and passes list table into template. |
| classes/fields.class.php | Adds i18n strings for Enabled/Disabled labels used by toggle UI. |
| css/ppom-admin.css | Styles list-table layout and adds toggle/badge styles. |
| tests/unit/test-field-group-toggle.php | Unit tests for repository toggle + frontend filtering + multi-meta behavior. |
| tests/e2e/specs/field-group-toggle.spec.js | E2E tests for per-row and bulk enable/disable behavior. |
| tests/e2e/utils.js | Adds clickAddGroup() helper used by multiple specs. |
| tests/e2e/specs/group-field-edit.spec.js | Updates to use clickAddGroup() helper. |
| tests/e2e/specs/attach-modal.spec.js | Updates to use clickAddGroup() helper. |
| phpstan-baseline.neon | Updates baseline for the new productmeta_disabled typed property. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 19 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Skip groups admins have toggled off; configuration and product | ||
| // attachments are preserved so re-enabling restores the form. | ||
| if ( $row && isset( $row->productmeta_disabled ) && 'on' === $row->productmeta_disabled ) { | ||
| return null; | ||
| } | ||
|
|
There was a problem hiding this comment.
PPOM_Meta::settings() now returns null when the first attached group (via single_meta_id()) is disabled. In multi-meta scenarios this can inadvertently hide fields from other enabled groups because $this->ppom_settings becomes null and get_fields() short-circuits early. Consider (a) making settings() pick the first enabled meta row when multiple meta are attached, and/or (b) updating get_fields() to not depend on $this->ppom_settings when has_multiple_meta() is true.
| // Skip groups admins have toggled off; configuration and product | |
| // attachments are preserved so re-enabling restores the form. | |
| if ( $row && isset( $row->productmeta_disabled ) && 'on' === $row->productmeta_disabled ) { | |
| return null; | |
| } | |
| // Do not collapse settings to null just because the first resolved row is | |
| // disabled. In multi-meta scenarios, downstream field resolution may still | |
| // need a non-null primary settings row in order to load fields from other | |
| // attached, enabled groups. |
| $table = $this->table_name(); | ||
| $ids_sql = implode( ',', $ids ); | ||
| // 'on' / '' is server-controlled (see $disabled cast above), and the | ||
| // IN list is absint'd. No request data reaches the SQL. | ||
| $value = $disabled ? 'on' : ''; | ||
|
|
||
| // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared -- table name internal; value is trusted enum. | ||
| $result = $this->wpdb->query( "UPDATE `{$table}` SET productmeta_disabled = '{$value}' WHERE productmeta_id IN ({$ids_sql})" ); |
There was a problem hiding this comment.
set_disabled_for_ids() builds the UPDATE query via string interpolation (table name, value, and IN (...) list). Even with the current normalization, this bypasses $wpdb->prepare() and will keep tripping DB sniffs / makes future changes riskier. Prefer building a prepared IN clause with the right number of %d placeholders and passing the ids as parameters (and still keep the allowlist/normalization).
| $table = $this->table_name(); | |
| $ids_sql = implode( ',', $ids ); | |
| // 'on' / '' is server-controlled (see $disabled cast above), and the | |
| // IN list is absint'd. No request data reaches the SQL. | |
| $value = $disabled ? 'on' : ''; | |
| // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared -- table name internal; value is trusted enum. | |
| $result = $this->wpdb->query( "UPDATE `{$table}` SET productmeta_disabled = '{$value}' WHERE productmeta_id IN ({$ids_sql})" ); | |
| $table = $this->table_name(); | |
| $in_placeholders = implode( ',', array_fill( 0, count( $ids ), '%d' ) ); | |
| $value = $disabled ? 'on' : ''; | |
| $query_parameters = array_merge( array( $value ), $ids ); | |
| $sql = $this->wpdb->prepare( | |
| "UPDATE `{$table}` SET productmeta_disabled = %s WHERE productmeta_id IN ({$in_placeholders})", | |
| $query_parameters | |
| ); | |
| // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- write query required; values are passed via prepare(), table name is internal. | |
| $result = $this->wpdb->query( $sql ); |
| esc_html__( 'Clone', 'woocommerce-product-addon' ) | ||
| ), | ||
| 'delete' => sprintf( | ||
| '<a href="#" id="del-file-%1$d" class="ppom-delete-single-product submitdelete" data-product-id="%1$d">%2$s</a>', |
There was a problem hiding this comment.
The row-action delete link starts as text (Delete), but the existing JS handler replaces its contents with a loader and later with a dashicon-only <span>, which will remove the text permanently and diverge from standard WP row-action UX. Either render the delete action with the same icon markup the JS expects (plus screen-reader text), or adjust the JS to restore the original link label after the request.
| '<a href="#" id="del-file-%1$d" class="ppom-delete-single-product submitdelete" data-product-id="%1$d">%2$s</a>', | |
| '<a href="#" id="del-file-%1$d" class="ppom-delete-single-product submitdelete" data-product-id="%1$d"><span class="dashicons dashicons-trash" aria-hidden="true"></span><span class="screen-reader-text">%2$s</span></a>', |
| * | ||
| * Replaces the legacy DataTables-driven markup in | ||
| * `templates/admin/existing-meta.php` with a `WP_List_Table` so the screen | ||
| * follows WordPress admin conventions (search, sortable headers, pagination, | ||
| * native bulk-action form, screen options). |
There was a problem hiding this comment.
The PR description mentions switching to WP_Media_List_Table, but the implementation adds a WP_List_Table subclass. If WP_List_Table is the intended base, please update the PR description to avoid confusion; if WP_Media_List_Table was intended (e.g. for media-style columns/views), the current class doesn’t match that.
Summary
Will affect visual aspect of the product
YES
Screenshots
Test instructions
Check before Pull Request is ready:
Closes https://github.com/Codeinwp/ppom-pro/issues/677