# WB Listora (Free) — Full QA Audit

**Date:** 2026-04-14
**Version:** 1.0.0
**Overall:** Maturity BASIC (66/100) • Code Quality C+ (69/100)
**Site tested:** http://directory.local

## Summary

| Severity | Count |
|---|---|
| Critical / Blockers | **82** |
| High / Important | 175 |
| Recommended | 375 |
| Total actions | **632** |
| Code checks run | 709 (229 pass, 201 fail) |

## Inventory (what ships)
- 2 CPTs, 6 taxonomies, 29 REST routes, 11 blocks, 6 admin pages
- 15 custom DB tables, 3 cron jobs, 1 CLI command, 50 templates
- 164 custom hooks provided (excellent extensibility)

## Code-quality matrix

| Check | Status | Errors | Warnings |
|---|---|---|---|
| PHPCS | FAIL | 68 | 166 |
| PHPStan | PASS | 0 | 0 |
| PHPCompat | PASS | 0 | 0 |
| PHP-Lint | PASS | 0 | 0 |
| Security scan | **FAIL** | **48** | 50 |
| PCP-Deep | **FAIL** | **47** | 86 |
| A11y | FAIL | 4 | 21 |
| A11y-grep | FAIL | 1 | 2 |
| Performance | FAIL | 1 | 0 |
| Composer audit | PASS | 0 | 0 |
| i18n | PASS | 0 | 0 |
| REST API (live) | **FAIL** | **29** | 0 |
| Marketing | FAIL | 3 | 5 |

---

## BLOCKERS (must fix before release)

### 1. Unprepared `$wpdb` queries — SQL injection risk (widespread)
48 security-scan errors + 47 PCP-deep errors. Every direct `$wpdb->query()`/`$wpdb->get_*()` with interpolation must use `$wpdb->prepare()`.

**Hotspots:**
- `includes/admin/class-admin.php` — lines 450, 452, 454, 517, 703–707, 942–945, 1270–1273
- `includes/class-cli-commands.php` — lines 62, 64, 66
- `includes/rest/class-listings-controller.php`
- `includes/search/class-search-indexer.php`

**Fix pattern:**
```php
// BEFORE
$wpdb->query("DELETE FROM $table WHERE id = $id");
// AFTER
$wpdb->query($wpdb->prepare("DELETE FROM {$table} WHERE id = %d", $id));
```

### 2. REST API returning HTTP 500 on all 29 routes
Live test from `directory.local` returned 500 for every endpoint, including `/listings/(?P<listing_id>[\d]+)/reviews`. Root causes to investigate:
- Missing `permission_callback` (WP_REST_Server throws in strict mode)
- Fatal inside callback (check `wp-content/debug.log` — `WP_DEBUG` currently off)
- Namespace registration timing issue after recent refactor

**Action:** Enable WP_DEBUG, hit each route, capture fatal traces. Add `permission_callback` to every route.

### 3. Missing lifecycle hooks (data integrity)
- No `register_activation_hook` — 15 custom tables have no `dbDelta()` creation path
- No `register_deactivation_hook` — 3 cron jobs never cleared
- No `uninstall.php` / `register_uninstall_hook` — tables and settings orphaned forever on uninstall

### 4. XSS via unescaped output (37 echoed variables)
Flagged in security scan + PCP-deep late-escaping. Audit all `echo $var` / `<?= $var ?>` — escape with `esc_html()`, `esc_attr()`, `esc_url()`, or `wp_kses_post()` at the point of output.

### 5. `extract()` usage
`extract($atts)` overwrites variables unpredictably. Replace with explicit array access.

---

## HIGH priority

### Accessibility (WCAG 2.1 AA)
- **31 form inputs without labels** — add `<label for="id">` or `aria-label`
- **2 images without alt** — add `alt` (use `alt=""` if decorative)
- **6 icon-only buttons/links without accessible names** — add `aria-label` or `.screen-reader-text`
- `aria-hidden="true"` on a focusable element — add `tabindex="-1"` or remove
- `outline:none` in CSS removes focus indicator — use `:focus-visible`

### Cross-layer wiring issues
JS ↔ PHP AJAX wiring has broken selectors / nonce mismatches / orphan handlers (flagged by scan). Run `action-audit` skill across `blocks/listing-submission`, `blocks/user-dashboard`, `src/interactivity/store.js`.

### REST namespace inconsistency
Multiple versioned namespaces in use. Consolidate under `wb-listora/v1`.

### Performance
- `posts_per_page = -1` unbounded query detected — paginate or cap at 100.

### Marketing assets (blocks wp.org listing if applicable)
- Missing `.wordpress-org/banner-{772x250,1544x500}.png`
- Missing `.wordpress-org/icon-{128x128,256x256}.png`
- Missing `screenshot-N.png`

---

## MEDIUM priority
- PHPCS: 55+ indentation / brace / multi-item array issues (run `phpcbf` to auto-fix many)
- PHPCS: `All output should be run through an escaping function` — repeated `$opt` in settings page (~40 instances)
- Settings stored in `wp_options` with no uninstall cleanup
- 2 CPTs missing `show_in_rest => true` (blocks block-editor + API)

---

## LOW priority
- CPTs have minimal `supports` (<3 features)
- No block patterns provided (11 blocks could ship starter patterns)
- No meta boxes for CPTs
- Feature gap: **Claim / Verify Listing** (directory-category should-have)
- Feature gap: **Search & Filtering** flagged as PARTIAL — faceted filters expected by directory users

---

## Files with heaviest issue density
1. `includes/admin/class-admin.php` — >20 unprepared queries
2. `includes/admin/class-settings-page.php` — ~40 `$opt` unescaped
3. `includes/rest/class-listings-controller.php` — REST 500s + prepare
4. `includes/class-cli-commands.php` — unprepared queries
5. `includes/search/class-search-indexer.php` — unprepared queries

---

## Recommended next actions (in order)
1. **Turn on `WP_DEBUG` + `WP_DEBUG_LOG`** and reproduce the 29 REST 500s; fix fatals.
2. **Sweep `$wpdb` prepare** across the 5 hotspot files above — single batched PR.
3. **Add lifecycle hooks** (`activation`/`deactivation`/`uninstall.php`) — one PR, covers tables + crons + options.
4. **Escape output pass** — `class-settings-page.php` + echoes in admin + blocks.
5. **A11y pass** on blocks + admin forms — labels, alt, focus.
6. **Auto-fix PHPCS** where safe: `phpcbf --standard=WordPress includes/ blocks/`.

See `wb-listora-pro/docs/audit/audit-2026-04-14.md` for pro-plugin findings.
