diff --git a/TODO.md b/TODO.md index 86cd29fca..e7ee80c38 100644 --- a/TODO.md +++ b/TODO.md @@ -1,138 +1,97 @@ + + + # TODO -## Tasks - -- [x] t374 Add support for creating new domains in Cloudflare @superdav42 #374 ~4h started:2026-03-24T00:00:00Z pr:#377 completed:2026-03-25 -- [x] t450 Positioning and growth plan to attract more customers ref:GH#450 ~8h pr:#452 completed:2026-03-25 -- [x] t451 Fix WP Performance Metrics CI ECONNREFUSED failure (WordPress server not starting on port 9400) ref:GH#475 ~2h #bug #auto-dispatch pr:#476 completed:2026-03-25 -- [x] t452 feat(tax): implement universal tax fallback as "Apply to all countries" dropdown option on Tax Rates page ref:GH#505 ~3h #enhancement #auto-dispatch pr:#506 completed:2026-03-26 -- [x] t509 fix: template_selection validation never fires — field ID maps to wrong rule key ref:GH#799 ~1h #bug #auto-dispatch pr:#800 completed:2026-04-12 - -## Unit Test Coverage Tasks - -Overall coverage: **35%** (20,720 / 59,212 statements). 90 files at 0% coverage. - -### Priority 1 — Business-Critical Code (low coverage, high risk) - -- [x] t453 test(checkout): write unit tests for Checkout class (inc/checkout/class-checkout.php — 7.9% coverage, 960 uncovered stmts) #testing #auto-dispatch ~6h ref:GH#555 pr:#569 completed:2026-03-27 -- [x] t454 test(cart): improve Cart test coverage (inc/checkout/class-cart.php — 61.1% coverage, 382 uncovered stmts) #testing #auto-dispatch ~4h ref:GH#556 pr:#583 completed:2026-03-28 -- [x] t455 test(stripe): write unit tests for Base_Stripe_Gateway (inc/gateways/class-base-stripe-gateway.php — 28.6% coverage, 1093 uncovered stmts) #testing #auto-dispatch ~8h ref:GH#624 pr:#648 completed:2026-03-28 -- [x] t456 test(stripe): improve Stripe_Gateway test coverage (inc/gateways/class-stripe-gateway.php — 55% coverage, 200 uncovered stmts) #testing ~4h pr:#623 completed:2026-03-28 -- [x] t457 test(paypal): write unit tests for PayPal_Gateway (inc/gateways/class-paypal-gateway.php — 1.1% coverage, 783 uncovered stmts) #testing #auto-dispatch ~6h ref:GH#627 pr:#649 completed:2026-03-28 -- [x] t458 test(paypal): improve PayPal_REST_Gateway test coverage (inc/gateways/class-paypal-rest-gateway.php — 29.8% coverage, 683 uncovered stmts) #testing #auto-dispatch ~5h ref:GH#629 pr:#636 completed:2026-03-28 -- [x] t459 test(paypal): write tests for PayPal_OAuth_Handler (inc/gateways/class-paypal-oauth-handler.php — 15% coverage, 238 uncovered stmts) #testing #auto-dispatch ~3h ref:GH#549 pr:#554 completed:2026-03-27 -- [x] t460 test(gateway): improve Gateway_Manager test coverage (inc/managers/class-gateway-manager.php — 34.7% coverage, 177 uncovered stmts) #testing #auto-dispatch ~3h ref:GH#550 pr:#552 completed:2026-03-27 -- [x] t461 test(checkout-pages): improve Checkout_Pages test coverage (inc/checkout/class-checkout-pages.php — 33.7% coverage, 203 uncovered stmts) #testing #auto-dispatch ~3h ref:GH#551 pr:#553 completed:2026-03-27 - -### Priority 2 — Core Domain Logic (moderate coverage gaps) - -- [x] t462 test(site-manager): improve Site_Manager test coverage (inc/managers/class-site-manager.php — 23.5% coverage, 433 uncovered stmts) #testing #auto-dispatch ~4h ref:GH#557 pr:#568 completed:2026-03-27 -- [x] t463 test(domain-manager): improve DNS_Record_Manager test coverage (inc/managers/class-dns-record-manager.php — 14.4% coverage, 393 uncovered stmts) #testing #auto-dispatch ~4h ref:GH#558 pr:#361 completed:2026-03-27 -- [x] t464 test(event-manager): improve Event_Manager test coverage (inc/managers/class-event-manager.php — 33% coverage, 240 uncovered stmts) #testing #auto-dispatch ~3h ref:GH#559 pr:#566 completed:2026-03-27 -- [x] t465 test(form-manager): improve Form_Manager test coverage (inc/managers/class-form-manager.php — 9.7% coverage, 251 uncovered stmts) #testing #auto-dispatch ~3h ref:GH#560 pr:#362 completed:2026-03-27 -- [x] t466 test(notes-manager): improve Notes_Manager test coverage (inc/managers/class-notes-manager.php — 9.9% coverage, 283 uncovered stmts) #testing #auto-dispatch ~3h ref:GH#561 pr:#287 completed:2026-03-27 -- [x] t467 test(sso): improve SSO test coverage (inc/sso/class-sso.php — 36.7% coverage, 210 uncovered stmts) #testing #auto-dispatch ~4h ref:GH#570 pr:#584 completed:2026-03-28 -- [x] t468 test(domain-mapping): write tests for Domain_Mapping (inc/class-domain-mapping.php — 13.8% coverage, 168 uncovered stmts) #testing #auto-dispatch ~3h ref:GH#571 pr:#579 completed:2026-03-28 -- [x] t469 test(membership-functions): improve membership function tests (inc/functions/membership.php — 28.3% coverage, 152 uncovered stmts) #testing #auto-dispatch ~2h ref:GH#572 pr:#577 completed:2026-03-28 -- [x] t470 test(checkout-form-model): improve Checkout_Form model tests (inc/models/class-checkout-form.php — 65.7% coverage, 286 uncovered stmts) #testing #auto-dispatch ~3h ref:GH#573 pr:#582 completed:2026-03-28 -- [x] t471 test(mcp-abilities): improve MCP abilities trait tests (inc/apis/trait-mcp-abilities.php — 69.3% coverage, 162 uncovered stmts) #testing #auto-dispatch ~2h ref:GH#574 pr:#581 completed:2026-03-28 -- [x] t472 test(rest-api): improve REST API trait tests (inc/apis/trait-rest-api.php — 31.6% coverage, 160 uncovered stmts) #testing #auto-dispatch ~3h ref:GH#575 pr:#578 completed:2026-03-28 - -### Priority 3 — Admin Pages (0% coverage, UI-heavy but testable logic) - -- [x] t473 test(admin): write unit tests for Membership_Edit_Admin_Page (inc/admin-pages/class-membership-edit-admin-page.php — 5% coverage, 1042 uncovered stmts) #testing #auto-dispatch ~6h ref:GH#626 pr:#634 completed:2026-03-28 -- [x] t474 test(admin): write unit tests for Payment_Edit_Admin_Page (inc/admin-pages/class-payment-edit-admin-page.php — 0% coverage, 913 uncovered stmts) #testing ~5h pr:#632 completed:2026-03-28 -- [x] t475 test(admin): write unit tests for Checkout_Form_Edit_Admin_Page (inc/admin-pages/class-checkout-form-edit-admin-page.php — 0% coverage, 901 uncovered stmts) #testing ~5h ref:GH#630 pr:#637 completed:2026-03-28 -- [x] t476 test(admin): write unit tests for Product_Edit_Admin_Page (inc/admin-pages/class-product-edit-admin-page.php — 0% coverage, 869 uncovered stmts) #testing ~5h pr:#633 completed:2026-03-28 -- [x] t477 test(admin): write unit tests for Customer_Edit_Admin_Page (inc/admin-pages/class-customer-edit-admin-page.php — 0% coverage, 784 uncovered stmts) #testing ~5h pr:#622 completed:2026-03-28 -- [x] t478 test(admin): write unit tests for Discount_Code_Edit_Admin_Page (inc/admin-pages/class-discount-code-edit-admin-page.php — 1.5% coverage, 526 uncovered stmts) #testing ~4h pr:#618 completed:2026-03-28 -- [x] t479 test(admin): write unit tests for Edit_Admin_Page base class (inc/admin-pages/class-edit-admin-page.php — 2.8% coverage, 375 uncovered stmts) #testing #auto-dispatch ~4h ref:GH#576 pr:#580 completed:2026-03-28 - -### Priority 4 — Infrastructure & Integration Code - -- [x] t480 test(migrator): write unit tests for Migrator (inc/installers/class-migrator.php — 0% coverage, 1057 uncovered stmts) #testing ~6h pr:#620 completed:2026-03-28 -- [x] t481 test(debug): improve Debug test coverage (inc/debug/class-debug.php — 24.3% coverage, 383 uncovered stmts) #testing #auto-dispatch ~3h ref:GH#585 pr:#593 completed:2026-03-28 -- [x] t482 test(wp-ultimo): improve WP_Ultimo main class tests (inc/class-wp-ultimo.php — 5.7% coverage, 394 uncovered stmts) #testing #auto-dispatch ~4h ref:GH#586 pr:#592 completed:2026-03-28 -- [x] t483 test(ajax): write unit tests for Ajax class (inc/class-ajax.php — 1.8% coverage, 213 uncovered stmts) #testing #auto-dispatch ~3h ref:GH#587 pr:#596 completed:2026-03-28 -- [x] t484 test(api): improve API class test coverage (inc/class-api.php — 6.6% coverage, 199 uncovered stmts) #testing #auto-dispatch ~3h ref:GH#588 pr:#591 completed:2026-03-28 -- [x] t485 test(host-providers): write tests for Cloudflare host provider (inc/integrations/host-providers/class-cloudflare-host-provider.php — 19.2% coverage, 307 uncovered stmts) #testing ~4h pr:#619 completed:2026-03-28 -- [x] t486 test(host-providers): write tests for cPanel host provider (inc/integrations/host-providers/class-cpanel-host-provider.php — 0% coverage, 279 uncovered stmts) #testing ~3h pr:#621 completed:2026-03-28 -- [x] t487 test(host-providers): write tests for Hestia host provider (inc/integrations/host-providers/class-hestia-host-provider.php — 5.2% coverage, 254 uncovered stmts) #testing ~3h pr:#617 completed:2026-03-28 -- [x] t488 test(host-providers): write tests for Base_Host_Provider (inc/integrations/host-providers/class-base-host-provider.php — 16.9% coverage, 148 uncovered stmts) #testing #auto-dispatch ~2h ref:GH#589 pr:#594 completed:2026-03-28 -- [x] t489 test(default-content): write tests for Default_Content_Installer (inc/installers/class-default-content-installer.php — 0.5% coverage, 206 uncovered stmts) #testing #auto-dispatch ~3h ref:GH#590 pr:#595 completed:2026-03-28 - -### Priority 5 — List Tables & Signup Fields - -- [x] t490 test(list-tables): write unit tests for Base_List_Table (inc/list-tables/class-base-list-table.php — 4.4% coverage, 461 uncovered stmts) #testing #auto-dispatch ~4h ref:GH#600 pr:#609 completed:2026-03-28 -- [x] t491 test(signup-fields): write tests for Base_Signup_Field (inc/checkout/signup-fields/class-base-signup-field.php — 16% coverage, 199 uncovered stmts) #testing #auto-dispatch ~3h ref:GH#601 pr:#608 completed:2026-03-28 -- [x] t492 test(signup-fields): write tests for Template_Selection field (inc/checkout/signup-fields/class-signup-field-template-selection.php — 1.1% coverage, 174 uncovered stmts) #testing #auto-dispatch ~2h ref:GH#602 pr:#607 completed:2026-03-28 -- [x] t493 test(signup-fields): write tests for Period_Selection field (inc/checkout/signup-fields/class-signup-field-period-selection.php — 1.3% coverage, 156 uncovered stmts) #testing #auto-dispatch ~2h ref:GH#603 pr:#606 completed:2026-03-28 - -### Priority 6 — API Schemas (0% coverage, data-validation code) - -- [x] t494 test(api-schemas): write tests for API schema validation files (inc/apis/schemas/ — 0% coverage across 24 files, 2436 uncovered stmts) #testing #auto-dispatch ~6h ref:GH#604 pr:#605 completed:2026-03-28 - -### Priority 7 — More Admin Pages (0% coverage) - -- [x] t496 test(admin): write unit tests for Domain_Edit_Admin_Page (inc/admin-pages/class-domain-edit-admin-page.php — 0% coverage, ~932 lines) #testing #auto-dispatch ~6h ref:GH#638 pr:#653 completed:2026-03-28 -- [x] t497 test(admin): write unit tests for Email_Edit_Admin_Page (inc/admin-pages/class-email-edit-admin-page.php — 0% coverage, ~576 lines) #testing #auto-dispatch ~4h ref:GH#639 pr:#650 completed:2026-03-28 -- [x] t498 test(admin): write unit tests for Broadcast_Edit_Admin_Page (inc/admin-pages/class-broadcast-edit-admin-page.php — 0% coverage, ~513 lines) #testing #auto-dispatch ~4h ref:GH#640 pr:#655 completed:2026-03-28 -- [x] t499 test(admin): write unit tests for Base_Admin_Page (inc/admin-pages/class-base-admin-page.php — 0% coverage, ~800 lines) #testing #auto-dispatch ~5h ref:GH#641 pr:#651 completed:2026-03-28 -- [x] t500 test(admin): write unit tests for Addons_Admin_Page (inc/admin-pages/class-addons-admin-page.php — 0% coverage, ~513 lines) #testing #auto-dispatch ~4h ref:GH#642 pr:#667 completed:2026-03-28 -- [x] t501 test(admin): write unit tests for Dashboard_Admin_Page (inc/admin-pages/class-dashboard-admin-page.php — 0% coverage) #testing #auto-dispatch ~4h ref:GH#643 pr:#657 completed:2026-03-28 -- [x] t502 test(admin): write unit tests for Customer_List_Admin_Page (inc/admin-pages/class-customer-list-admin-page.php — 0% coverage) #testing #auto-dispatch ~3h ref:GH#644 pr:#663 completed:2026-03-28 -- [x] t503 test(admin): write unit tests for Discount_Code_List_Admin_Page (inc/admin-pages/class-discount-code-list-admin-page.php — 0% coverage) #testing #auto-dispatch ~3h ref:GH#645 pr:#454 verified:2026-03-28 -- [x] t504 test(admin): write unit tests for Domain_List_Admin_Page (inc/admin-pages/class-domain-list-admin-page.php — 0% coverage) #testing #auto-dispatch ~3h ref:GH#646 pr:#654 completed:2026-03-28 -- [x] t505 test(admin): write unit tests for Email_List_Admin_Page (inc/admin-pages/class-email-list-admin-page.php — 0% coverage) #testing #auto-dispatch ~3h ref:GH#647 pr:#656 completed:2026-03-28 - -### Priority 8 — Remaining Admin Pages (0% coverage) - -- [x] t506 test(admin): write unit tests for Settings_Admin_Page (inc/admin-pages/class-settings-admin-page.php — 0% coverage, ~980 lines) #testing #auto-dispatch ~6h ref:GH#658 pr:#664 completed:2026-03-28 -- [x] t507 test(admin): write unit tests for Setup_Wizard_Admin_Page (inc/admin-pages/class-setup-wizard-admin-page.php — 0% coverage, ~947 lines) #testing #auto-dispatch ~6h ref:GH#659 pr:#665 completed:2026-03-28 -- [x] t508 test(admin): write unit tests for System_Info_Admin_Page (inc/admin-pages/class-system-info-admin-page.php — 0% coverage, ~778 lines) #testing #auto-dispatch ~5h ref:GH#660 pr:#669 completed:2026-03-28 -- [x] t509 test(admin): write unit tests for Email_Template_Customize_Admin_Page (inc/admin-pages/class-email-template-customize-admin-page.php — 0% coverage, ~692 lines) #testing #auto-dispatch ~4h ref:GH#661 pr:#668 completed:2026-03-28 -- [x] t510 test(admin): write unit tests for Template_Library_Admin_Page (inc/admin-pages/class-template-library-admin-page.php — 0% coverage, ~673 lines) #testing #auto-dispatch ~4h ref:GH#662 pr:#666 completed:2026-03-28 - -### Priority 9 — Remaining Admin Pages & List Tables (0% coverage) - -- [x] t511 test(admin): write unit tests for Multisite_Setup_Admin_Page (inc/admin-pages/class-multisite-setup-admin-page.php — 0% coverage, ~520 lines) #testing #auto-dispatch ~4h ref:GH#672 pr:#682 completed:2026-03-28 -- [x] t512 test(admin): write unit tests for Wizard_Admin_Page (inc/admin-pages/class-wizard-admin-page.php — 0% coverage, ~445 lines) #testing #auto-dispatch ~3h ref:GH#673 pr:#686 completed:2026-03-28 -- [x] t513 test(admin): write unit tests for Invoice_Template_Customize_Admin_Page (inc/admin-pages/class-invoice-template-customize-admin-page.php — 0% coverage, ~435 lines) #testing #auto-dispatch ~3h ref:GH#674 pr:#680 completed:2026-03-28 -- [x] t514 test(admin): write unit tests for Hosting_Integration_Wizard_Admin_Page (inc/admin-pages/class-hosting-integration-wizard-admin-page.php — 0% coverage, ~420 lines) #testing #auto-dispatch ~3h ref:GH#675 pr:#687 completed:2026-03-28 -- [x] t515 test(admin): write unit tests for External_Cron_Admin_Page (inc/admin-pages/class-external-cron-admin-page.php — 0% coverage, ~417 lines) #testing #auto-dispatch ~3h ref:GH#676 pr:#683 completed:2026-03-28 -- [x] t516 test(list-tables): write unit tests for remaining list table classes (15 files, 0% coverage) #testing #auto-dispatch ~6h ref:GH#677 pr:#684 completed:2026-03-28 -- [x] t517 test(admin): write unit tests for View_Logs and Tax_Rates admin pages (0% coverage) #testing #auto-dispatch ~3h ref:GH#678 pr:#681 completed:2026-03-28 -- [x] t518 test(admin): write unit tests for Template_Previewer, Checkout_Form_List, and Event_View admin pages (0% coverage) #testing #auto-dispatch ~3h ref:GH#679 pr:#685 completed:2026-03-28 - -### Fix: Test suite exits early at 56% - -- [x] t495 fix(tests): Form_Manager_Test::test_handle_model_delete_form_requires_confirmation calls exit() killing test runner at test 2533/4411 #bug #auto-dispatch ~1h ref:GH#562 pr:#563 completed:2026-03-27 - -## New Tasks - -- [x] t519 fix(security): replace wp_redirect with wp_safe_redirect in class-primary-domain.php #bug #auto-dispatch ~1h ref:GH#689 pr:#694 completed:2026-03-29 -- [x] t520 feat(addon): create multisite-ultimate-fluentaffiliate addon for recurring commission tracking #enhancement #auto-dispatch ~12h ref:GH#690 completed:2026-03-29 -- [x] t521 chore: remove stale @todo comments for already-implemented methods #enhancement #auto-dispatch ~1h ref:GH#691 pr:#693 completed:2026-03-29 -- [x] t522 test(integrations): write unit tests for integration provider classes (bunnynet, cloudways, enhance, laravel-forge, plesk, rocket, serverpilot, wpengine, wpmudev) #testing #auto-dispatch ~4h ref:GH#697 pr:#698 completed:2026-03-29 -- [x] t523 feat(paypal): PayPal PPCP integration review compliance — disconnect disclaimer, onboarding failure UI, merchant status validation, payee field, debug ID logging @superdav42 #paypal #compliance ~8h ref:GH#725 pr:#726 completed:2026-04-01 -- [x] t524 feat(checkout): add simple checkout form template with auto-generated credentials (re-implement PR #740 which was closed due to merge conflicts) #enhancement #auto-dispatch ~4h ref:GH#746 pr:#737 completed:2026-04-04 -- [x] t525 fix(dashboard): enqueue wu-styling on network admin dashboard for activity-stream widget #bug #auto-dispatch ~1h ref:GH#767 pr:#768 completed:2026-04-08 -- [x] GH#808 test: add autoload-order regression tests for mpdf PSR HTTP message shim ref:GH#808 pr:#818 completed:2026-04-13 -- [x] GH#813 fix: guard false return from get_available_site_templates() in switch_template() — TypeError on PHP 8.0+ ref:GH#813 pr:#819 completed:2026-04-13 -- [x] GH#814 test(reactivation): add unit tests verifying all PR #751 review findings were addressed ref:GH#814 pr:#817 completed:2026-04-13 - -## Production Bugs (2026-04-28) - -- [ ] t531 fix(checkout): password fields and strength meter stop working after inline login popup + email change — Vue 2 recycles login-prompt DOM node as password-div, detaching jQuery bindings on #field-password #bug #auto-dispatch ~3h ref:GH#973 logged:2026-04-28 - -## Production Bugs (2026-04-22) - -- [ ] t529 fix(membership): orphan pending_site after membership cancellation — watchdog cancels membership but pending_site meta stays, customer retries checkout and gets no site #bug #auto-dispatch ~4h ref:GH#902 -- [ ] t528 fix(checkout): duplicate WP users created on checkout retry — wp_insert_user called without get_user_by email check #bug #auto-dispatch ~2h ref:GH#903 -- [ ] t530 fix(checkout): reclaim orphan pending_site on WC order completion — no code picks up transferable pending_site when payment succeeds #bug #auto-dispatch ~4h ref:GH#904 -- [ ] t526 fix(i18n): JS checkout validator missing i18n strings — field_required etc. hardcoded English in checkout.min.js #bug #auto-dispatch ~2h ref:GH#905 -- [ ] t527 fix(ux): default minimum_password_strength too strict — super_strong rejects reasonable passwords, default to strong #enhancement #auto-dispatch ~1h ref:GH#906 - -## Production Bugs (2026-05-05) - -- [x] t532 fix(admin): white page after site deletion in Edit Site screen — post-delete redirect lands on a blank page instead of the site list #bugfix #auto-dispatch ~1h logged:2026-05-05 pr:#1125 completed:2026-05-05 \ No newline at end of file +Project task tracking with time estimates, dependencies, and TOON-enhanced parsing. + +Compatible with [todo-md](https://github.com/todo-md/todo-md), [todomd](https://github.com/todomd/todo.md), [taskell](https://github.com/smallhadroncollider/taskell), and [Beads](https://github.com/steveyegge/beads). + +## Format + +**Human-readable:** + + + +Format: `- [ ] tNNN Description @owner #tag ~estimate risk:level logged:date` + +**Task IDs:** +- `t001` - Top-level task +- `t001.1` - Subtask of t001 +- `t001.1.1` - Sub-subtask + +**Dependencies:** +- `blocked-by:t001` - This task waits for t001 +- `blocked-by:t001,t002` - Waits for multiple tasks +- `blocks:t003` - This task blocks t003 + +**Time fields:** +- `~estimate` - AI-assisted execution time (~15m trivial, ~30m small, ~1h medium, ~2h large, ~4h major — see `reference/planning-detail.md`) +- `actual:` - Actual active time spent (from session-time-helper.sh) +- `logged:` - When task was added +- `started:` - When branch was created +- `completed:` - When task was marked done + +**Risk (human oversight needed):** +- `risk:low` - Autonomous: fire-and-forget, review PR after +- `risk:med` - Supervised: check in mid-task, review before merge +- `risk:high` - Engaged: stay present, test thoroughly, potential regressions + + + +## Ready + + + + + +## Backlog + + + +## In Progress + + + +## In Review + + + + + +## Done + + + +## Declined + + + + + + + + + + + + + + diff --git a/inc/ui/class-field.php b/inc/ui/class-field.php index 4efaa741b..fac1bb314 100644 --- a/inc/ui/class-field.php +++ b/inc/ui/class-field.php @@ -298,7 +298,7 @@ public function __get($att) { } if ('wrapper_classes' === $att) { - $attr = is_string($attr) ? $attr : ''; + $attr = is_string($attr) ? $attr : ''; $wrapper_html_attr = $this->resolve_attribute_value($this->atts['wrapper_html_attr'] ?? []); if (is_array($wrapper_html_attr) && isset($wrapper_html_attr['v-show']) && ! str_contains($attr, 'wu-requires-other')) { @@ -463,7 +463,6 @@ public function sanitize(): void { * @return int|float */ protected function validate_number_field($value) { - /* * An empty string means the field was submitted without a value * (e.g. the browser sends an empty ). @@ -493,13 +492,28 @@ protected function validate_number_field($value) { /** * Cleans the value submitted via a textarea or wp_editor field for database insertion. * + * Defensive against non-string inputs: arrays, objects (including Closures and + * stdClass), and other scalar types can reach this validator when: + * - A form posts `name="field[]"` syntax that PHP parses into an array. + * - A previously-stored corrupted value (array / "[object Object]" / Closure) + * is read back from the database as the existing fallback in + * Settings::save_settings() for fields not in the current form step. + * - A filter on `wu_settings_fields_sanitization_rules` routes a non-textarea + * field type here. + * + * Without this guard, `addslashes()` / `stripslashes()` raise a TypeError on + * PHP 8+ ("Argument #1 ($string) must be of type string, array given"), + * which fatals the Settings save / Setup Wizard step. + * * @since 2.0.0 * - * @param string $value Value of the settings being represented by this field. + * @param mixed $value Value of the settings being represented by this field. * @return string */ protected function validate_textarea_field($value) { + $value = $this->coerce_textarea_value($value); + if ($this->allow_html) { return stripslashes(wp_filter_post_kses(addslashes($value))); } @@ -507,6 +521,33 @@ protected function validate_textarea_field($value) { return wp_strip_all_tags(stripslashes($value)); } + /** + * Coerces a textarea field value to a safe string. + * + * Returns an empty string for arrays/objects/null and casts scalars + * to string. This prevents TypeError fatals downstream in + * addslashes()/stripslashes() and matches the historical behaviour of + * silently dropping malformed values rather than propagating corruption. + * + * @since 2.5.2 + * + * @param mixed $value Raw value to coerce. + * @return string + */ + private function coerce_textarea_value($value): string { + + if (is_string($value)) { + return $value; + } + + if (is_null($value) || is_array($value) || is_object($value)) { + return ''; + } + + // Booleans, ints, floats — cast to their string form. + return (string) $value; + } + /** * Echo HTML attributes for the field. * diff --git a/tests/WP_Ultimo/UI/Field_Test.php b/tests/WP_Ultimo/UI/Field_Test.php index f83f45dcd..ebaa57adb 100644 --- a/tests/WP_Ultimo/UI/Field_Test.php +++ b/tests/WP_Ultimo/UI/Field_Test.php @@ -265,4 +265,97 @@ public function test_title_fallback_to_name(): void { // Accessing title should fallback to name $this->assertEquals('Field Name', $field->title); } + + /** + * Textarea sanitization must accept a plain string and round-trip safely. + */ + public function test_textarea_sanitization_string_value(): void { + $field = new Field('test_field', [ + 'type' => 'textarea', + 'default' => '', + ]); + + $field->set_value("Line one\nLine two"); + + $this->assertSame("Line one\nLine two", $field->get_value()); + } + + /** + * Regression: textarea sanitization must NOT fatal when given an array value. + * + * This reproduces the historical TypeError raised by addslashes() / stripslashes() + * on PHP 8+ when a stored DB value (or a malformed POST payload) reaches the + * textarea validator as an array. Expected behaviour is to coerce to an empty + * string rather than fatal the Settings save / Setup Wizard step. + */ + public function test_textarea_sanitization_coerces_array_to_empty_string(): void { + $field = new Field('test_field', [ + 'type' => 'textarea', + 'allow_html' => false, + 'default' => '', + ]); + + $field->set_value(['unexpected', 'array', 'value']); + + $this->assertSame('', $field->get_value()); + } + + /** + * Regression: wp_editor (allow_html=true) must also tolerate array input. + */ + public function test_wp_editor_sanitization_coerces_array_to_empty_string(): void { + $field = new Field('test_field', [ + 'type' => 'wp_editor', + 'allow_html' => true, + 'default' => '', + ]); + + $field->set_value(['

not

', '

a

', '

string

']); + + $this->assertSame('', $field->get_value()); + } + + /** + * Object values (Closures, stdClass) must coerce to empty string, not fatal. + */ + public function test_textarea_sanitization_coerces_object_to_empty_string(): void { + $field = new Field('test_field', [ + 'type' => 'textarea', + 'default' => '', + ]); + + $field->set_value((object) ['foo' => 'bar']); + + $this->assertSame('', $field->get_value()); + } + + /** + * Null values must coerce to empty string. + */ + public function test_textarea_sanitization_coerces_null_to_empty_string(): void { + $field = new Field('test_field', [ + 'type' => 'textarea', + 'default' => '', + ]); + + $field->set_value(null); + + $this->assertSame('', $field->get_value()); + } + + /** + * Scalar non-strings (int, float, bool) must cast to their string form. + */ + public function test_textarea_sanitization_casts_scalar_to_string(): void { + $field = new Field('test_field', [ + 'type' => 'textarea', + 'default' => '', + ]); + + $field->set_value(42); + $this->assertSame('42', $field->get_value()); + + $field->set_value(3.14); + $this->assertSame('3.14', $field->get_value()); + } } diff --git a/todo/PLANS.md b/todo/PLANS.md index b8c3f3051..70b2490bb 100644 --- a/todo/PLANS.md +++ b/todo/PLANS.md @@ -1,11 +1,50 @@ -# Plans -## t523: PayPal PPCP Integration Review Compliance + + +# Execution Plans -**Status:** Planning -**Estimate:** ~8h (code) + manual deliverables -**Priority:** High — blocks PayPal partnership approval -**Tags:** #paypal #gateway #compliance +Complex, multi-session work requiring research, design decisions, and detailed tracking. + +Based on [OpenAI's PLANS.md](https://cookbook.openai.com/articles/codex_exec_plans) with TOON-enhanced parsing and [Beads](https://github.com/steveyegge/beads) integration for dependency visualization. + + + +## Format + +Each plan includes: +- **Plan ID**: `p001`, `p002`, etc. (for cross-referencing) +- **Status**: Planning / In Progress (Phase X/Y) / Blocked / Completed +- **Time Estimate**: `~2w (ai:1w test:0.5w read:0.5w)` +- **Timestamps**: `logged:`, `started:`, `completed:` +- **Dependencies**: `blocked-by:p001` or `blocks:p003` +- **Linkage (The Pin)**: File:line references for search hit-rate (see below) +- **Progress**: Timestamped checkboxes with estimates and actuals +- **Decision Log**: Key decisions with rationale +- **Surprises & Discoveries**: Unexpected findings +- **Outcomes & Retrospective**: Results and lessons (when complete) + +### Linkage (The Pin) + +Based on [Loom's spec-as-lookup-table pattern](https://ghuntley.com/ralph/), each plan should include a Linkage section that functions as a lookup table for AI search: + +| Concept | Files | Lines | Synonyms | +|---------|-------|-------|----------| +| {concept} | {file path} | {line range} | {related terms} | + +**Why this matters:** +- Reduces hallucination by providing explicit anchors +- Improves search hit-rate with synonyms +- Points to exact file hunks for context +- Prevents AI from inventing when it should reference + +## Active Plans + + + + ### Context from Discussion @@ -105,3 +144,82 @@ validation, order/capture process, BN code, and debug ID submission. - `inc/gateways/class-paypal-rest-gateway.php` (main changes) - `inc/gateways/class-paypal-oauth-handler.php` (may need status check helper) - Proxy server code (separate repo/server) + +## Completed Plans + + + + + +## Archived Plans + + + + + +--- + +## Plan Template + +```markdown +### p00X: Plan Title + +**Status:** Planning +**Owner:** @username +**Tags:** #tag1 #tag2 +**Estimate:** ~Xd (ai:Xd test:Xd read:Xd) +**Dependencies:** blocked-by:p001 (if any) +**PRD:** [todo/tasks/prd-{slug}.md](tasks/prd-{slug}.md) +**Tasks:** [todo/tasks/tasks-{slug}.md](tasks/tasks-{slug}.md) +**Logged:** YYYY-MM-DD + +#### Purpose + +Brief description of why this work matters. + +#### Development Environment + + + +| Item | Value | +|------|-------| +| Language/runtime | e.g. Python 3.12, Node 20 | +| Venv/install | e.g. `python3 -m venv .venv && pip install -e ".[dev]"` | +| Tests | e.g. `source .venv/bin/activate && pytest` | +| Do NOT | e.g. install globally; run `pip install -e` from worktree using canonical venv | + +#### Linkage (The Pin) + +| Concept | Files | Lines | Synonyms | +|---------|-------|-------|----------| +| {main concept} | src/path/file.ts | 45-120 | {term1}, {term2} | +| {related concept} | src/path/other.ts | 12-89 | {term3}, {term4} | + +#### Progress + +- [ ] (YYYY-MM-DD HH:MMZ) Phase 1: Description ~Xh +- [ ] (YYYY-MM-DD HH:MMZ) Phase 2: Description ~Xh + +#### Decision Log + +(Decisions recorded during implementation) + +#### Surprises & Discoveries + +(Unexpected findings during implementation) +``` + +--- + +## Analytics + + + + + +