From 45ba0b6a909ba36af0f6411da8f326ec5495ce36 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 10 May 2026 07:08:49 +0000 Subject: [PATCH 1/5] add StripeBuyLinks column template Renders all buy.stripe.* URLs found inside the `buttons` sub-repeater of a page's `elements` RepeaterMatrix as clickable anchors, using the button's `label` field as link text. Accepts a page id, Page object or the RepeaterMatrix value directly. https://claude.ai/code/session_01HzmUPyxHVhMNXo1Fi5xgwF --- fieldtype_templates/StripeBuyLinks.column.php | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 fieldtype_templates/StripeBuyLinks.column.php diff --git a/fieldtype_templates/StripeBuyLinks.column.php b/fieldtype_templates/StripeBuyLinks.column.php new file mode 100644 index 0000000..204e25c --- /dev/null +++ b/fieldtype_templates/StripeBuyLinks.column.php @@ -0,0 +1,47 @@ +get('elements'); + } elseif (is_numeric($value)) { + $p = wire('pages')->get((int) $value); + if ($p && $p->id) $matrix = $p->get('elements'); + } else { + $matrix = $value; + } + + if (!$matrix || !is_iterable($matrix)) return ''; + + $links = []; + foreach ($matrix as $item) { + $buttons = $item->get('buttons'); + if (!$buttons || !is_iterable($buttons)) continue; + foreach ($buttons as $btn) { + $url = trim((string) $btn->get('link')); + if ($url === '' || stripos($url, 'https://buy.stripe') !== 0) continue; + $label = trim((string) $btn->get('label')); + if ($label === '') $label = $url; + $links[] = '' + . htmlspecialchars($label) . ''; + } + } + + return implode('
', $links); +}; From 26528a9f9920627251f50ce8b8bd9c3d4b926083 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 10 May 2026 07:13:06 +0000 Subject: [PATCH 2/5] preserve full URL with query/fragment in StripeBuyLinks column Use getUnformatted() to bypass any FieldtypeURL output formatter that could strip query strings or fragments, and explicitly pass UTF-8 to htmlspecialchars so multibyte labels render correctly. https://claude.ai/code/session_01HzmUPyxHVhMNXo1Fi5xgwF --- fieldtype_templates/StripeBuyLinks.column.php | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/fieldtype_templates/StripeBuyLinks.column.php b/fieldtype_templates/StripeBuyLinks.column.php index 204e25c..ac615fb 100644 --- a/fieldtype_templates/StripeBuyLinks.column.php +++ b/fieldtype_templates/StripeBuyLinks.column.php @@ -33,13 +33,19 @@ $buttons = $item->get('buttons'); if (!$buttons || !is_iterable($buttons)) continue; foreach ($buttons as $btn) { - $url = trim((string) $btn->get('link')); + $url = method_exists($btn, 'getUnformatted') + ? (string) $btn->getUnformatted('link') + : (string) $btn->get('link'); + $url = trim($url); if ($url === '' || stripos($url, 'https://buy.stripe') !== 0) continue; - $label = trim((string) $btn->get('label')); + $label = method_exists($btn, 'getUnformatted') + ? (string) $btn->getUnformatted('label') + : (string) $btn->get('label'); + $label = trim($label); if ($label === '') $label = $url; - $links[] = '' - . htmlspecialchars($label) . ''; + . htmlspecialchars($label, ENT_QUOTES, 'UTF-8') . ''; } } From bbeac8caaefe22309027eb807b5f57c18991a2d5 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 10 May 2026 07:46:01 +0000 Subject: [PATCH 3/5] dedupe stripe buy links by URL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Track URLs in a $seen map so that subsequent buttons linking to the same URL are skipped — only the first occurrence is rendered. https://claude.ai/code/session_01HzmUPyxHVhMNXo1Fi5xgwF --- fieldtype_templates/StripeBuyLinks.column.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fieldtype_templates/StripeBuyLinks.column.php b/fieldtype_templates/StripeBuyLinks.column.php index ac615fb..0313e69 100644 --- a/fieldtype_templates/StripeBuyLinks.column.php +++ b/fieldtype_templates/StripeBuyLinks.column.php @@ -29,6 +29,7 @@ if (!$matrix || !is_iterable($matrix)) return ''; $links = []; + $seen = []; foreach ($matrix as $item) { $buttons = $item->get('buttons'); if (!$buttons || !is_iterable($buttons)) continue; @@ -38,6 +39,8 @@ : (string) $btn->get('link'); $url = trim($url); if ($url === '' || stripos($url, 'https://buy.stripe') !== 0) continue; + if (isset($seen[$url])) continue; + $seen[$url] = true; $label = method_exists($btn, 'getUnformatted') ? (string) $btn->getUnformatted('label') : (string) $btn->get('label'); From 6d361d5a112afbd22b8cce527ad0f9b5787882cd Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 10 May 2026 07:54:02 +0000 Subject: [PATCH 4/5] add Status column template Standalone stub that decodes the ProcessWire page status bitmask into "published"/"unpublished" plus an optional "hidden" tag. Accepts either the raw int status or a Page object. https://claude.ai/code/session_01HzmUPyxHVhMNXo1Fi5xgwF --- fieldtype_templates/Status.column.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 fieldtype_templates/Status.column.php diff --git a/fieldtype_templates/Status.column.php b/fieldtype_templates/Status.column.php new file mode 100644 index 0000000..0000251 --- /dev/null +++ b/fieldtype_templates/Status.column.php @@ -0,0 +1,18 @@ +status will be used. + * + * Available variable: $value + */ +return function($value, $config = []) { + $status = $value instanceof Page ? (int) $value->status : (int) $value; + + $labels = []; + $labels[] = ($status & Page::statusUnpublished) ? 'unpublished' : 'published'; + if ($status & Page::statusHidden) $labels[] = 'hidden'; + + return htmlspecialchars(implode(', ', $labels), ENT_QUOTES, 'UTF-8'); +}; From 6eef613fc08d4bc2a83fb7edbf54ddd00615bcb1 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 10 May 2026 07:56:37 +0000 Subject: [PATCH 5/5] drop dead value-coercion branches in column stubs The ProcessDataTables module always passes \$value as \$page->get(\$col['realName']), so: - Status: \$value is the int bitmask, not a Page - StripeBuyLinks: \$value is the RepeaterMatrixPageArray, never a page id Remove the unreachable Page/numeric handling and document the actual \$value shape in each stub header. https://claude.ai/code/session_01HzmUPyxHVhMNXo1Fi5xgwF --- fieldtype_templates/Status.column.php | 10 ++----- fieldtype_templates/StripeBuyLinks.column.php | 29 +++++-------------- 2 files changed, 10 insertions(+), 29 deletions(-) diff --git a/fieldtype_templates/Status.column.php b/fieldtype_templates/Status.column.php index 0000251..3978732 100644 --- a/fieldtype_templates/Status.column.php +++ b/fieldtype_templates/Status.column.php @@ -2,17 +2,13 @@ /** * Output template for column: Status (published / unpublished / hidden) * - * Use for a column whose realName is the page property "status" (an int - * bitmask). Also accepts a Page object — its ->status will be used. - * - * Available variable: $value + * Column realName must be the page property "status". + * $value is the int bitmask returned by $page->get('status'). */ return function($value, $config = []) { - $status = $value instanceof Page ? (int) $value->status : (int) $value; - + $status = (int) $value; $labels = []; $labels[] = ($status & Page::statusUnpublished) ? 'unpublished' : 'published'; if ($status & Page::statusHidden) $labels[] = 'hidden'; - return htmlspecialchars(implode(', ', $labels), ENT_QUOTES, 'UTF-8'); }; diff --git a/fieldtype_templates/StripeBuyLinks.column.php b/fieldtype_templates/StripeBuyLinks.column.php index 0313e69..45ac4a4 100644 --- a/fieldtype_templates/StripeBuyLinks.column.php +++ b/fieldtype_templates/StripeBuyLinks.column.php @@ -2,35 +2,20 @@ /** * Output template for column: Stripe Buy Links * - * Expects $value to resolve to a page reference with the RepeaterMatrix - * field "elements". Accepts: - * - a page id (int / numeric string) - * - a Page object - * - the RepeaterMatrix value (PageArray) of the "elements" field directly + * Column realName must be the RepeaterMatrix field "elements". + * $value is the RepeaterMatrixPageArray returned by $page->get('elements'). * * Iterates matrix items, finds a sub-repeater named "buttons" containing - * fields "link" and "label", filters URLs that start with - * "https://buy.stripe" and renders them as anchor tags using "label" as - * the link text. - * - * Available variable: $value + * fields "link" and "label", keeps only URLs that start with + * "https://buy.stripe", deduplicates by URL (first occurrence wins) and + * renders them as anchor tags using "label" as link text. */ return function($value, $config = []) { - $matrix = null; - if ($value instanceof Page) { - $matrix = $value->get('elements'); - } elseif (is_numeric($value)) { - $p = wire('pages')->get((int) $value); - if ($p && $p->id) $matrix = $p->get('elements'); - } else { - $matrix = $value; - } - - if (!$matrix || !is_iterable($matrix)) return ''; + if (!$value || !is_iterable($value)) return ''; $links = []; $seen = []; - foreach ($matrix as $item) { + foreach ($value as $item) { $buttons = $item->get('buttons'); if (!$buttons || !is_iterable($buttons)) continue; foreach ($buttons as $btn) {