PluginProbe
Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More / 2.3.3
Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More v2.3.3
2.4.1 2.4.0 2.3.4 2.3.3 2.3.2 2.3.1 2.3.0 2.2.2 2.2.1 2.2.0 2.1.2 2.1.1 trunk 0.0.1 0.0.2 0.0.3 0.0.4 0.0.5 0.0.6 0.0.7 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 All 68 releases
better-payment / includes / Campaign / Templates / ProTemplatePreviews.php

ProTemplatePreviews.php in Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More 2.3.3, at includes/Campaign/Templates/ProTemplatePreviews.php

141 lines 5.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Better_Payment\Lite\Campaign\Templates;
4
5 if ( ! defined( 'ABSPATH' ) ) {
6 exit;
7 }
8
9 /**
10 * Preview-only layouts for Better Payment Pro's templates.
11 *
12 * `ProTemplateCatalog` gives a free install the four Pro templates as locked
13 * CARDS — labels, artwork, copy, no layout. This class is what lets the picker's
14 * Preview lightbox render those cards live, exactly like a free template, instead
15 * of showing a cropped screenshot of a design nobody can scroll.
16 *
17 * ── The enforcement is unchanged, and that is the point ──────────────────────
18 * `TemplateManager::get_all()` still reports `columns => []` for these keys.
19 * Nothing here is ever merged into the registry, localized to the builder, or
20 * returned by `GET /campaigns/templates`. The browser never receives a Pro
21 * layout — only rendered HTML from
22 * `RendererService::build_template_preview_document()`, which cannot be applied.
23 * So a client that ignores the crown, the CTA and the click handler and
24 * dispatches `APPLY_TEMPLATE` on a locked card still builds an empty campaign,
25 * never a free copy of a Pro design. **Do not "simplify" this by folding these
26 * layouts into `ProTemplateCatalog::catalog()`** — that is the whole gate.
27 *
28 * ── Why the data file is generated ───────────────────────────────────────────
29 * `data/pro-preview-layouts.php` is exported from Pro by
30 * `scripts/export-pro-preview-layouts.php`. Four large layouts maintained by hand
31 * in two plugins is the drift `ProElementSchemas` exists to prevent: the free
32 * preview would misrepresent the product the first time Pro redesigned a
33 * template, and nothing would report it. Regenerating is one command, and Pro's
34 * `BuilderTemplatesRegistrationTest` fails when the two disagree.
35 *
36 * With Pro ACTIVE none of this runs: Pro registers the real templates, they carry
37 * their own `columns`, and `build_template_preview_document()` never asks.
38 */
39 class ProTemplatePreviews {
40
41 /**
42 * Per-request memo of the resolved layouts.
43 *
44 * @var array<string, array>|null
45 */
46 private static $cache = null;
47
48 /**
49 * Layout for one Pro template, ready to render.
50 *
51 * @param string $key Template key.
52 * @return array `[ 'layout' => string, 'columns' => array ]`, or `[]` when the
53 * key has no mirrored layout.
54 */
55 public static function layout( string $key ): array {
56 $all = self::all();
57
58 return isset( $all[ $key ] ) ? $all[ $key ] : [];
59 }
60
61 /**
62 * Every mirrored layout, keyed by template key.
63 *
64 * @return array<string, array>
65 */
66 public static function all(): array {
67 if ( null !== self::$cache ) {
68 return self::$cache;
69 }
70
71 $file = __DIR__ . '/data/pro-preview-layouts.php';
72
73 if ( ! file_exists( $file ) ) {
74 self::$cache = [];
75 return self::$cache;
76 }
77
78 $raw = include $file;
79
80 self::$cache = is_array( $raw ) ? self::resolve( $raw ) : [];
81
82 return self::$cache;
83 }
84
85 /**
86 * Reset the memo. Tests only.
87 */
88 public static function flush(): void {
89 self::$cache = null;
90 }
91
92 /**
93 * Fill in the two values the generated file deliberately does not carry.
94 *
95 * Both are install-specific, so baking either into a shipped file would make
96 * every site render the exporting developer's own: a photo `src` pointing at
97 * the Pro plugin on the machine it was exported from, and an Organizer
98 * pointing at a user id that means someone else here (or nobody).
99 *
100 * @param array $layouts Raw layouts from the data file.
101 * @return array
102 */
103 private static function resolve( array $layouts ): array {
104 $assets = defined( 'BETTER_PAYMENT_ASSETS' ) ? BETTER_PAYMENT_ASSETS : '';
105
106 // Same rule as TemplateManager: the site's first user, falling back to
107 // whoever is looking. One query for all four layouts, not one per photo.
108 $users = get_users( [ 'fields' => [ 'ID' ], 'number' => 1 ] );
109 $organizer = ! empty( $users ) ? (int) $users[0]->ID : get_current_user_id();
110
111 return self::walk( $layouts, '', $assets, $organizer );
112 }
113
114 /**
115 * @param mixed $value Node.
116 * @param string $key Key the node sits under.
117 * @param string $assets Lite's assets URL.
118 * @param int $organizer Default organizer user id.
119 * @return mixed
120 */
121 private static function walk( $value, string $key, string $assets, int $organizer ) {
122 if ( is_array( $value ) ) {
123 $out = [];
124 foreach ( $value as $k => $v ) {
125 $out[ $k ] = self::walk( $v, (string) $k, $assets, $organizer );
126 }
127 return $out;
128 }
129
130 if ( 'creator_user_id' === $key ) {
131 return $organizer;
132 }
133
134 if ( is_string( $value ) && 0 === strpos( $value, '@bp-assets/' ) ) {
135 return $assets . '/' . substr( $value, strlen( '@bp-assets/' ) );
136 }
137
138 return $value;
139 }
140 }
141