| 1 | +# Pointing SAMA v2 at a real WordPress plugin in the wild |
| 2 | + |
| 3 | +Earlier today I published [/sama/v2/example-wordpress](/sama/v2/example-wordpress) — a hypothetical event-registration plugin laid out under the v2 discipline. Hypothetical examples are easy: you design the layers and then describe a codebase that fits them. The harder question is what v2 sees when you point it at code that **wasn't** designed under any layer discipline at all. |
| 4 | + |
| 5 | +So I picked one. **Open Graph and Twitter Card Tags** by WPExperts (the plugin formerly known as Wonderm00n's, slug `wonderm00ns-simple-facebook-open-graph-tags`) — 200k+ active installs, ten-plus years of git history, the canonical "small useful plugin" shape that WordPress is full of. Downloaded the latest release (v3.4.0), unzipped, walked the source. |
| 6 | + |
| 7 | +This isn't a takedown. The plugin works. It ships. It does exactly what its name says. The question this post answers is: *if a PHP-aware SAMA v2 verifier existed, what would it report against this codebase?* |
| 8 | + |
| 9 | +## What's actually in the box |
| 10 | + |
| 11 | +Total: **6,445 lines of PHP** across 17 source files (excluding the WordPress `index.php` security stubs and the vendored Post-SMTP-recommendation library). |
| 12 | + |
| 13 | +The architecture, at a glance: |
| 14 | + |
| 15 | +``` |
| 16 | +wonderm00n-open-graph.php 83 lines # WP entry — header, includes, uninstall hook |
| 17 | +fbimg.php 224 lines # standalone helper (referenced from a URL) |
| 18 | +includes/ |
| 19 | + class-webdados-fb-open-graph.php 674 lines # CORE: hook registration, options loading |
| 20 | +admin/ |
| 21 | + class-webdados-fb-open-graph-admin.php 784 lines # ADMIN: settings save, FB debug call |
| 22 | + options-page-facebook.php 556 lines # admin UI tab (HTML + HTTP + JSON parsing) |
| 23 | + options-page-general.php 486 lines # admin UI tab |
| 24 | + options-page-3rdparty.php 386 lines # admin UI tab |
| 25 | + options-page-schema.php 261 lines # admin UI tab |
| 26 | + options-page.php 246 lines # admin UI wrapper |
| 27 | + options-page-twitter.php 188 lines # admin UI tab |
| 28 | + options-page-seo.php 123 lines # admin UI tab |
| 29 | + options-page-right.php 73 lines # admin UI sidebar |
| 30 | + options-page-tools.php 51 lines # admin UI tab |
| 31 | + options-page-recommend-post-smtp.php 34 lines # admin UI tab |
| 32 | +public/ |
| 33 | + class-webdados-fb-open-graph-public.php 1,554 lines # PUBLIC: the actual og:* + twitter:* + schema rendering |
| 34 | +``` |
| 35 | + |
| 36 | +Three god-classes (core, admin, public), each owning a vertical slice of feature work end-to-end. Around them, eleven `options-page-*.php` files that mix HTML, business logic, HTTP, and JSON parsing in the same callback. No layer separation, no test files anywhere in the source (zero `*test*.php` files in the entire archive). |
| 37 | + |
| 38 | +This is normal WordPress. Most plugins in the directory look like this. |
| 39 | + |
| 40 | +## What the v2 verifier would report |
| 41 | + |
| 42 | +Walking each of the seven §4 conformance checks against what's in the source: |
| 43 | + |
| 44 | +### #1 Sorted |
| 45 | + |
| 46 | +> *Every file carries a profile-recognised prefix; lexicographic prefix order equals layer order.* |
| 47 | + |
| 48 | +The plugin has no prefix scheme at all. File names follow `class-vendor-feature-area.php` and `options-page-tab.php` patterns; their alphabetical sort has no relation to architectural layer. **Would fail** on every non-index source file. |
| 49 | + |
| 50 | +### #2 Architecture |
| 51 | + |
| 52 | +> *Every file maps to exactly one canonical layer; no file is unprefixed or maps to two layers.* |
| 53 | + |
| 54 | +Without a profile mapping any prefix to a canonical layer, **every file would be flagged unprefixed**. Even if we tried to retrofit a profile — say `class-*-public.php` → Layer 1, `class-*-admin.php` → Layer 1 — the single file behind that pattern does enough Layer 2 work (HTTP, DB, JSON parsing) that the mapping wouldn't be honest. |
| 55 | + |
| 56 | +### #3 Modeled (tests) |
| 57 | + |
| 58 | +> *Every Layer 1 and Layer 2 behavior file has a sibling test file.* |
| 59 | + |
| 60 | +Zero test files in the archive. The plugin has no unit tests at all — `grep -r 'PHPUnit\|TestCase' /tmp/wonderm00ns-*` returns nothing. **Would fail** the moment any source file is mapped to Layer 1 or 2. |
| 61 | + |
| 62 | +### #4 Modeled (boundary) |
| 63 | + |
| 64 | +> *External input is parsed only in Layer 2.* |
| 65 | + |
| 66 | +This is where it gets interesting. Boundary patterns are scattered, not localised: |
| 67 | + |
| 68 | +- **HTTP outbound** — `wp_remote_get(...)` lives in `admin/class-webdados-fb-open-graph-admin.php:464` (calling Facebook's Graph debug API) AND in `admin/options-page-facebook.php:307` (calling Facebook's translation XML) AND in `public/class-webdados-fb-open-graph-public.php:1301` (fetching remote images). Three different files, three different layers conceptually. |
| 69 | +- **JSON parsing** — `json_decode` lives in `admin/class-webdados-fb-open-graph-admin.php:475` AND `admin/options-page-facebook.php:364`. Mixed with HTML output in the second case. |
| 70 | +- **Filesystem reads** — `file_get_contents()` against a bundled XML file in `admin/options-page-facebook.php:339`. Same file mixes this with HTTP, JSON parsing, and direct HTML echoing. |
| 71 | +- **Superglobals** — **43 raw `$_POST` / `$_GET` / `$_REQUEST` accesses** across the codebase. Most of them in admin classes, but the pattern is "wherever a hook needs to read the request, just touch the superglobal directly." |
| 72 | + |
| 73 | +A boundary-ratio metric here would be near-zero: parsing happens almost anywhere except in a dedicated adapter layer (because there isn't one). |
| 74 | + |
| 75 | +### #5 Atomic |
| 76 | + |
| 77 | +> *No file exceeds the line cap (default ~700; profile may lower, never raise). No barrel re-export files.* |
| 78 | + |
| 79 | +Three files over the 700-line cap: |
| 80 | + |
| 81 | +- `public/class-webdados-fb-open-graph-public.php` — **1,554 lines** (220% of cap) |
| 82 | +- `admin/class-webdados-fb-open-graph-admin.php` — **784 lines** (112%) |
| 83 | +- `includes/class-webdados-fb-open-graph.php` — 674 lines (just under, but close) |
| 84 | + |
| 85 | +The 1,554-line public class is a textbook example of what §4.5 was written to prevent. Inside that one file: OG tag generation, Twitter Card generation, Schema.org JSON-LD generation, image URL resolution, image dimension caching, remote image fetching via `wp_remote_get`, WooCommerce product handling, multilingual locale logic, third-party SEO plugin compatibility (Yoast, AIOSEO), and the WP filter callbacks that wire it all up. Each of those is a sublayer or a separate file under v2. |
| 86 | + |
| 87 | +### #6 The Law (§1.2) |
| 88 | + |
| 89 | +> *Imports always point to a strictly lower layer number — never upward, never sideways across a higher number, never cyclic.* |
| 90 | + |
| 91 | +PHP `require_once` does exist as an import edge, but without a profile mapping the verifier has nothing to check against. The architectural spirit of §1.2 — that lower concerns shouldn't reach into higher ones — is violated structurally: the **entry file itself** (`wonderm00n-open-graph.php`) contains direct `$wpdb->query("DELETE FROM ...")` calls in the uninstall hook. The Pure layer doesn't exist; the Adapter layer is everywhere. |
| 92 | + |
| 93 | +### #7 Consistency (§3) |
| 94 | + |
| 95 | +> *No file's imports contradict its declared layer.* |
| 96 | + |
| 97 | +Vacuous — there are no declared layers to contradict. |
| 98 | + |
| 99 | +**Summary score:** 0 of 7 checks would pass. Which is not surprising and not the point. The plugin wasn't built under v2; you can't grade an essay against rules its author never opted into. |
| 100 | + |
| 101 | +## Estimated §5 metrics |
| 102 | + |
| 103 | +A PHP-aware metrics emitter doesn't exist yet, but the values can be estimated by hand against the operational definitions on [/sama/v2 §5](/sama/v2#5-operational--core-metrics-definitions): |
| 104 | + |
| 105 | +| metric | this plugin (estimated) | tdd.md (measured) | |
| 106 | +|---|---|---| |
| 107 | +| **graphDepth** | ~3 (entry → core → public/admin classes; very flat, almost no transitive depth) | 7 | |
| 108 | +| **boundaryRatio** | <10% (no dedicated adapter sublayer — parsing is spread across admin classes, UI pages, the entry file, and the public class) | 100% | |
| 109 | +| **workingSetFit** (50 ≤ LOC ≤ 500) | ~47% (8 of 17 non-index source files in the sweet spot; three big god-classes drag it down) | 80% | |
| 110 | +| **violationCounts** (sum) | 17+ Atomic, dozens of boundary, ~17 Architecture | 0 | |
| 111 | + |
| 112 | +The interesting comparison isn't ✓ vs ✗ — that's the compliance score §5 explicitly tells us *not* to lead with. The interesting comparison is the **delta on the same metric axes**. Compliance proves the rules were followed; the delta is the variable that proves whether following them was worth the cost. |
| 113 | + |
| 114 | +## What v2 makes visible that the existing code hides |
| 115 | + |
| 116 | +Three architectural facts the v2 lens surfaces: |
| 117 | + |
| 118 | +**1. The 1,554-line public class is doing seven jobs at once.** Under v2, the rendering of `og:*` tags, Twitter Cards, Schema.org JSON-LD, image-URL resolution, third-party SEO compatibility, and WooCommerce-specific behaviour would each live in their own Core (`b*_`) file with a sibling test. Today, changing one of them risks touching all of them, and there's no test fixture to catch a regression. A PR review on this file is essentially trust-based — the LOC budget is too big to read end-to-end. |
| 119 | + |
| 120 | +**2. The 43 superglobal accesses mean every change to a settings field is a hand-traced refactor.** Without a `c3_*` controller sublayer that owns "how a settings POST becomes a typed value," the validation logic is duplicated across `options-page-*.php` files. An agent asked to add a new settings option has to find every place the related field is touched. v2's boundary rule reduces that to one file by construction. |
| 121 | + |
| 122 | +**3. `$wpdb` inside the entry file (uninstall hook).** Under v2 this is `c1_*` work in an adapter file with a sibling test that asserts what gets deleted. Today it's an inline closure that runs once per uninstall, no test, and a future change to the schema requires editing a string literal inside a `wp_*` function. The Law check would catch this in v2 land; under stock WP idioms there's nothing to push back. |
| 123 | + |
| 124 | +## The honest interpretation |
| 125 | + |
| 126 | +A v2-aware reviewer looking at this plugin doesn't conclude *"this code is bad."* They conclude: |
| 127 | + |
| 128 | +- The plugin is shaped exactly like WordPress's official patterns push you to shape it. WP teaches: register hooks at the top, do work in their callbacks, use `$wpdb` when you need to read or write data. There is no built-in pressure toward layer separation; in fact, the `add_action` / `add_filter` inversion-of-control pattern actively encourages mixing wiring with logic. |
| 129 | +- 0 of 7 checks passing is the **expected baseline** for code written under WP idioms with no external discipline. Most plugins in the repository score the same. That's not a SAMA failure — that's the data point that lets us measure the delta if we ever do build a plugin under v2 from scratch. |
| 130 | +- The PR that this post would generate — if I'd been the maintainer — wouldn't be "refactor everything." It would be one specific extraction: pull the 1,554-line public class apart into seven files of 200-300 lines each, each with a sibling unit test. The §5 metrics would shift measurably on that one refactor; the user-facing behaviour wouldn't change at all. |
| 131 | + |
| 132 | +## What this exercise actually buys us |
| 133 | + |
| 134 | +Now there are two profile snapshots in the world: this site's own `tdd-md` profile (TypeScript, scoring 7/7 ✓ on §4 with graphDepth=7, boundaryRatio=100%, workingSetFit=80%), and this hand-estimated WordPress plugin (PHP, scoring 0/7 with graphDepth≈3, boundaryRatio<10%, workingSetFit≈47%). |
| 135 | + |
| 136 | +That's still only n=2 and across different languages, so it's a long way from "v2 is empirically worth following." But it's the first data point where a real codebase that was **not** designed under v2 gets measured on the same axes. Future work — a v2-discipline plugin built from scratch, or a real refactor of one of the god-classes here — would let the delta speak for itself. |
| 137 | + |
| 138 | +The chain extended one more link today: from *"here is the spec and a hypothetical worked example"* to *"here is the spec, here is what a real plugin in the wild looks like through this lens, here is what the §5 metrics would say if a PHP-aware verifier existed."* The PHP-aware verifier itself is the next piece of cable. |
| 139 | + |
| 140 | +--- |
| 141 | + |
| 142 | +**See for yourself:** |
| 143 | + |
| 144 | +- The plugin: <https://wordpress.org/plugins/wonderm00ns-simple-facebook-open-graph-tags/> |
| 145 | +- The v2 hypothetical plugin profile: [/sama/v2/example-wordpress](/sama/v2/example-wordpress) |
| 146 | +- The §5 metric definitions: [/sama/v2#5-operational--core-metrics-definitions](/sama/v2#5-operational--core-metrics-definitions) |
| 147 | +- Today's earlier post on the §5 emitter: [Compliance proves the rules were followed. Delta proves they were worth following.](/blog/sama-v2-metrics-emitter) |
| 148 | +- Yesterday's post on building the v2 verifier: [I built the SAMA v2 verifier...](/blog/sama-v2-verifier-and-the-rename) |