PluginProbe
Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More / 2.4.1
Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More v2.4.1
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
← All changes | includes/Campaign/Services/RendererService.php +143 -42 2.3.2 → 2.4.1 View file →
@@ -5,8 +5,9 @@
5 5 use Better_Payment\Lite\Admin\DB;
6 6 use Better_Payment\Lite\Campaign\CampaignStats;
7 7 use Better_Payment\Lite\Campaign\MetaBox;
8 8 use Better_Payment\Lite\Campaign\Templates\TemplateManager;
9 +use Better_Payment\Lite\Campaign\Support\Money;
9 10 use Better_Payment\Lite\Campaign\Elements\ElementRegistry;
10 11 use Better_Payment\Lite\Campaign\Elements\ProElementPreview;
11 12
12 13 if ( ! defined( 'ABSPATH' ) ) {
@@ -318,9 +319,9 @@
318 319 $display_progress = (int) ceil( $goal > 0 ? min( 100, ( $raised / $goal ) * 100 ) : 0 );
319 320 $goal_fmt = number_format( (int) ceil( $goal ) );
320 321 } else {
321 322 $display_progress = $progress; // 1 decimal float from CampaignStats
322 - $goal_fmt = number_format( $goal, 2 );
323 + $goal_fmt = Money::format( $goal );
323 324 }
324 325
325 326 ?>
326 327 <div class="bp-campaign-progress"
@@ -374,9 +375,9 @@
374 375 <?php endif; ?>
375 376 <div class="bp-campaign-summary">
376 377 <?php if ( $show_raised ) : ?>
377 378 <div class="bp-summary-item">
378 - <strong><?php echo esc_html( $sm_currency_sym . number_format( (float) $stats['total_raised'], 2 ) ); ?></strong>
379 + <strong><?php echo esc_html( Money::with_symbol( $sm_currency_sym, $stats['total_raised'] ) ); ?></strong>
379 380 <span><?php esc_html_e( 'Raised', 'better-payment' ); ?></span>
380 381 </div>
381 382 <?php endif; ?>
382 383 <?php if ( $show_donors ) : ?>
@@ -460,9 +461,9 @@
460 461 <?php
461 462 printf(
462 463 /* translators: %s: formatted minimum amount with currency symbol */
463 464 esc_html__( 'The minimum donation for this campaign is %s.', 'better-payment' ),
464 - esc_html( $currency_symbol . number_format( $min_amount, 2 ) )
465 + esc_html( Money::with_symbol( $currency_symbol, $min_amount ) )
465 466 );
466 467 ?>
467 468 </p>
468 469 <?php endif; ?>
@@ -565,9 +566,9 @@
565 566 <?php checked( $is_default ); ?>
566 567 hidden
567 568 />
568 569 <label for="<?php echo esc_attr( $uid ); ?>" class="bp-amount-label">
569 - <?php echo esc_html( $currency_symbol . $amt ); ?>
570 + <?php echo esc_html( Money::with_symbol( $currency_symbol, $amt ) ); ?>
570 571 </label>
571 572 <?php endforeach; ?>
572 573 </div>
573 574 <?php if ( $allow_custom ) : ?>
@@ -1142,28 +1143,11 @@
1142 1143 'post_status' => 'publish',
1143 1144 'post_name' => $key,
1144 1145 ] );
1145 1146
1146 - $meta = [
1147 - 'bpc_goal_amount' => 10000,
1148 - 'bpc_color_primary' => $template['preview_color'] ?? '#6b63f6',
1149 - 'bpc_color_background' => '',
1150 - 'bpc_suggested_amounts' => [],
1151 - 'bpc_allow_custom_amount' => true,
1152 - 'bpc_minimum_amount' => '',
1153 - 'bpc_form_page_id' => 0,
1154 - 'bpc_status' => 'active',
1155 - 'bpc_template_key' => $key,
1156 - 'bpc_css_class' => '',
1157 - ];
1147 + $meta = self::template_preview_meta( $key, $template );
1148 + $stats = self::template_preview_stats();
1158 1149
1159 - $stats = [
1160 - 'total_raised' => 3750,
1161 - 'progress' => 37.5,
1162 - 'donor_count' => 42,
1163 - 'days_remaining' => 18,
1164 - ];
1165 -
1166 1150 $theme_class = isset( $template['theme_class'] ) ? ' ' . sanitize_html_class( $template['theme_class'] ) : '';
1167 1151
1168 1152 ob_start();
1169 1153 ?>
@@ -1190,8 +1174,93 @@
1190 1174 return ob_get_clean();
1191 1175 }
1192 1176
1193 1177 /**
1178 + * Demo meta for a template preview — no campaign exists behind it.
1179 + *
1180 + * Shared by the fragment renderer (render_template_preview) and the full
1181 + * document one (build_template_preview_document) so the two can never show a
1182 + * template with different figures or a different accent colour.
1183 + *
1184 + * @param string $key Template key.
1185 + * @param array $template Template definition from TemplateManager.
1186 + * @return array
1187 + */
1188 + private static function template_preview_meta( string $key, array $template ): array {
1189 + return [
1190 + 'title' => $template['default_title'] ?? $template['label'] ?? 'Campaign Preview',
1191 + 'bpc_goal_amount' => 10000,
1192 + 'bpc_color_primary' => $template['preview_color'] ?? '#6b63f6',
1193 + 'bpc_color_background' => '',
1194 + 'bpc_suggested_amounts' => [],
1195 + 'bpc_allow_custom_amount' => true,
1196 + 'bpc_minimum_amount' => '',
1197 + 'bpc_form_page_id' => 0,
1198 + 'bpc_status' => 'active',
1199 + 'bpc_template_key' => $key,
1200 + 'bpc_css_class' => '',
1201 + ];
1202 + }
1203 +
1204 + /**
1205 + * Demo stats for a template preview.
1206 + *
1207 + * A template has no transactions, so the real figures are all zero — which
1208 + * renders an empty progress bar and "$0.00 raised" on every card, i.e. the
1209 + * one state that shows least about the design. These are obviously-illustrative
1210 + * round numbers, never presented as a real campaign's record.
1211 + *
1212 + * @return array
1213 + */
1214 + private static function template_preview_stats(): array {
1215 + return [
1216 + 'total_raised' => 3750,
1217 + 'progress' => 37.5,
1218 + 'donor_count' => 42,
1219 + 'days_remaining' => 18,
1220 + ];
1221 + }
1222 +
1223 + /**
1224 + * Build a full, standalone HTML document for one template — the picker's
1225 + * "Preview" lightbox.
1226 + *
1227 + * This is the whole design, top to bottom, rendered by the same code the
1228 + * frontend uses; the lightbox scrolls it. A static screenshot cannot do that
1229 + * (it is a fixed crop of the top of the page) and drifts from the layout the
1230 + * moment a template is redesigned.
1231 + *
1232 + * Only a template with a layout of its own previews live. A locked Pro card
1233 + * (ProTemplateCatalog, free install only) carries `columns => []`, and Lite
1234 + * ships no copy of Pro's layouts, so it returns '' and the lightbox shows the
1235 + * card's screenshot — the builder does not even request it. With Pro active
1236 + * the same key resolves to Pro's real template and renders like any other.
1237 + * Falling through to `default_layout()` instead would advertise Pro's design
1238 + * as a generic campaign.
1239 + *
1240 + * @param string $key Template key from TemplateManager.
1241 + * @return string Full HTML document, or '' when the key is unknown or has no layout.
1242 + */
1243 + public static function build_template_preview_document( string $key ): string {
1244 + $templates = TemplateManager::get_all();
1245 + if ( empty( $templates[ $key ]['columns'] ) ) {
1246 + return '';
1247 + }
1248 +
1249 + $template = $templates[ $key ];
1250 +
1251 + return self::build_preview_document(
1252 + [
1253 + 'layout' => $template['layout'] ?? '1-column',
1254 + 'columns' => $template['columns'],
1255 + ],
1256 + self::template_preview_meta( $key, $template ),
1257 + 0,
1258 + self::template_preview_stats()
1259 + );
1260 + }
1261 +
1262 + /**
1194 1263 * Build a full HTML preview document for the builder's live-preview iframe.
1195 1264 *
1196 1265 * Each element is wrapped in a <div data-bp-element-id> so the JS overlay
1197 1266 * can measure positions and wire up hover/drag interactions. Works for both
@@ -1196,17 +1265,23 @@
1196 1265 * Each element is wrapped in a <div data-bp-element-id> so the JS overlay
1197 1266 * can measure positions and wire up hover/drag interactions. Works for both
1198 1267 * new unsaved campaigns (campaign_id = 0) and existing ones.
1199 1268 *
1200 - * @param array $layout_data Builder layout: { layout, columns }.
1201 - * @param array $meta_input Campaign meta from the builder store (includes 'title').
1202 - * @param int $campaign_id 0 for new campaigns; real ID to pull live stats.
1269 + * @param array $layout_data Builder layout: { layout, columns }.
1270 + * @param array $meta_input Campaign meta from the builder store (includes 'title').
1271 + * @param int $campaign_id 0 for new campaigns; real ID to pull live stats.
1272 + * @param array $stats_override Demo figures merged over the computed stats, for
1273 + * previews that have no campaign behind them at all
1274 + * (the template picker). Applied LAST, after the
1275 + * goal/end-date recomputations below, so it wins —
1276 + * that is the whole point of an override.
1203 1277 * @return string Full HTML document string.
1204 1278 */
1205 1279 public static function build_preview_document(
1206 1280 array $layout_data,
1207 1281 array $meta_input,
1208 - int $campaign_id = 0
1282 + int $campaign_id = 0,
1283 + array $stats_override = []
1209 1284 ): string {
1210 1285 // ── Post + stats ──────────────────────────────────────────────────────
1211 1286 $post = null;
1212 1287 $stats = null;
@@ -1252,8 +1327,12 @@
1252 1327 if ( $preview_goal > 0 ) {
1253 1328 $stats['progress'] = min( 100.0, round( ( $stats['total_raised'] / $preview_goal ) * 100, 1 ) );
1254 1329 }
1255 1330
1331 + if ( ! empty( $stats_override ) ) {
1332 + $stats = array_merge( $stats, $stats_override );
1333 + }
1334 +
1256 1335 // ── Meta ─────────────────────────────────────────────────────────────
1257 1336 $meta_defaults = [
1258 1337 'bpc_goal_amount' => 0,
1259 1338 'bpc_color_primary' => '#6b63f6',
@@ -1329,9 +1408,9 @@
1329 1408 // hotspot, an element that cannot be selected or removed.
1330 1409 //
1331 1410 // Show a mock preview instead so the element is visible,
1332 1411 // selectable, and demonstrates what Pro would do with the
1333 - // settings shown (disabled) in the sidebar.
1412 + // upgrade banner shown in the sidebar.
1334 1413 //
1335 1414 // This is the ONLY call site — the public render path has no
1336 1415 // reference to ProElementPreview, and ProElementPreview::render()
1337 1416 // re-checks the preview flag itself. Its data is fabricated;
@@ -1376,8 +1455,16 @@
1376 1455 $extra_link = file_exists( BETTER_PAYMENT_ASSETS_PATH . '/css/fundraising-campaign.min.css' )
1377 1456 ? '<link rel="stylesheet" href="' . esc_url( $fundraising_css ) . '?v=' . $v . '">' . "\n"
1378 1457 : '';
1379 1458
1459 + // Template theme CSS from an add-on — Pro links its themes through
1460 + // `better_payment/campaign/preview_stylesheets`. A free install links no
1461 + // Pro design. See Campaign\CampaignStyles.
1462 + $theme_links = '';
1463 + foreach ( \Better_Payment\Lite\Campaign\CampaignStyles::preview_stylesheets() as $theme_css ) {
1464 + $theme_links .= '<link rel="stylesheet" href="' . esc_url( $theme_css ) . '">' . "\n";
1465 + }
1466 +
1380 1467 return '<!DOCTYPE html>' . "\n"
1381 1468 . '<html>' . "\n"
1382 1469 . '<head>' . "\n"
1383 1470 . '<meta charset="utf-8">' . "\n"
@@ -1383,8 +1470,9 @@
1383 1470 . '<meta charset="utf-8">' . "\n"
1384 1471 . '<meta name="viewport" content="width=device-width, initial-scale=1">' . "\n"
1385 1472 . '<base href="' . esc_url( home_url( '/' ) ) . '">' . "\n"
1386 1473 . '<link rel="stylesheet" href="' . esc_url( $display_css ) . '?v=' . $v . '">' . "\n"
1474 + . $theme_links
1387 1475 . $extra_link
1388 1476 . '<style>' . "\n"
1389 1477 . '*, *::before, *::after { box-sizing: border-box; }' . "\n"
1390 1478 . 'html, body { margin: 0; padding: 0; background: #fff; }' . "\n"
@@ -1422,44 +1510,57 @@
1422 1510 . '</html>';
1423 1511 }
1424 1512
1425 1513 /**
1514 + * Per-request memo for pro_locked_types(). A property rather than a
1515 + * function-local static so tests can reset it.
1516 + *
1517 + * @var array<string, array>|null
1518 + */
1519 + private static $pro_locked_types_cache = null;
1520 +
1521 + /**
1426 1522 * Element types that are Pro-only AND currently locked (Pro inactive).
1427 1523 *
1428 1524 * Returns a map of type => schema (label/icon) for elements that carry the
1429 1525 * `pro` flag in the filtered registry while `better_payment/pro_enabled` is
1430 - * false. When Pro is active these are overridden by the full schema (no `pro`
1431 - * flag) so the map is empty and nothing is locked. Result is cached per
1432 - * request.
1526 + * false. With Pro active the map is empty and nothing is locked. Result is
1527 + * cached per request.
1433 1528 *
1434 1529 * @return array<string, array>
1435 1530 */
1436 1531 private static function pro_locked_types(): array {
1437 - static $cache = null;
1438 -
1439 - if ( null !== $cache ) {
1440 - return $cache;
1532 + if ( null !== self::$pro_locked_types_cache ) {
1533 + return self::$pro_locked_types_cache;
1441 1534 }
1442 1535
1443 - $cache = [];
1536 + self::$pro_locked_types_cache = [];
1444 1537
1445 1538 if ( apply_filters( 'better_payment/pro_enabled', false ) ) {
1446 - return $cache;
1539 + return self::$pro_locked_types_cache;
1447 1540 }
1448 1541
1449 1542 foreach ( ElementRegistry::get_all() as $type => $schema ) {
1450 1543 if ( ! empty( $schema['pro'] ) ) {
1451 - $cache[ $type ] = $schema;
1544 + self::$pro_locked_types_cache[ $type ] = $schema;
1452 1545 }
1453 1546 }
1454 1547
1455 - return $cache;
1548 + return self::$pro_locked_types_cache;
1456 1549 }
1457 1550
1458 1551 /**
1459 - * Editor-only placeholder banner shown in the builder preview for a
1460 - * Pro-locked element (a Pro element left over from when Pro was active).
1461 - * Never emitted on the public frontend.
1552 + * Generic builder-only "PRO" banner for a locked element that has no mock.
1553 + *
1554 + * Lite's own three Pro elements always get a ProElementPreview mock, so this
1555 + * is reached only by some other `pro`-flagged type — a third party's, added
1556 + * through `better_payment/campaign_elements`, or a future Pro element whose
1557 + * mock has not been written. It is lock chrome, not Pro functionality: it
1558 + * renders a label and an upgrade line, nothing of the element itself. Without
1559 + * it the element's canvas wrapper would be empty and zero-height, so the canvas
1560 + * could place no hotspot and the element could not be selected or deleted.
1561 + * Only build_preview_document() calls it, only while Pro is inactive
1562 + * (pro_locked_types() is empty otherwise). Never emitted on the public frontend.
1462 1563 *
1463 1564 * @param array $schema Element schema (label, icon).
1464 1565 * @return string
1465 1566 */