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.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 1.0.5 1.0.6 All 66 releases
better-payment / includes / WooCommerce / MyAccount.php

MyAccount.php in Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More 2.3.3, at includes/WooCommerce/MyAccount.php

736 lines 29.0 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\WooCommerce;
4
5 use Better_Payment\Lite\Models\SubscriptionRelationModel;
6
7 /**
8 * Exit if accessed directly
9 */
10 if ( ! defined( 'ABSPATH' ) ) {
11 exit;
12 }
13
14 /**
15 * My Account > Subscriptions tab.
16 *
17 * Adds two WooCommerce My Account endpoints — a Subscriptions list and a
18 * single-subscription view — where customers see and manage their Better
19 * Payment subscriptions. All management actions reuse the existing
20 * nonce-guarded POST handlers in {@see Subscriptions} (customer cancel,
21 * auto-renewal toggle) and WooCommerce's own key-guarded order-pay URL for
22 * past-due renewal invoices; this class never mutates subscription state
23 * itself.
24 *
25 * Registration notes:
26 * - The endpoints are added through `woocommerce_get_query_vars`, which
27 * WooCommerce itself turns into rewrite endpoints — no direct
28 * `add_rewrite_endpoint()` call is needed.
29 * - Rewrite rules are flushed ONCE, keyed on a stored version option —
30 * never unconditionally on every request.
31 * - The endpoints stay registered even while the My Account Tab setting is
32 * off; the setting gates the menu item and the rendered content, so
33 * toggling it needs no rewrite flush.
34 * - State-changing actions are POST forms with per-order nonces, handled on
35 * `template_redirect` (proper redirect-after-post) — never GET links, and
36 * never handled mid-template after headers are sent.
37 *
38 * @since 2.4.0
39 */
40 class MyAccount {
41
42 /**
43 * My Account endpoint slug for the subscriptions list. The endpoint
44 * value, when present, is the list page number.
45 */
46 const ENDPOINT = 'bp-subscriptions';
47
48 /**
49 * My Account endpoint slug for the single-subscription view. The
50 * endpoint value is the parent (subscription) order id.
51 */
52 const VIEW_ENDPOINT = 'bp-view-subscription';
53
54 /**
55 * Option storing the rewrite version the rules were last flushed for.
56 */
57 const REWRITE_OPTION = 'better_payment_wc_myaccount_rewrite_version';
58
59 /**
60 * Bump to force a one-time rewrite flush after an endpoint change.
61 */
62 const REWRITE_VERSION = '1';
63
64 /**
65 * Subscriptions shown per list page.
66 */
67 const PER_PAGE = 10;
68
69 /**
70 * Register the My Account hooks. Called from Loader::register() — only
71 * when WooCommerce is active.
72 *
73 * @return void
74 */
75 public static function register() {
76 // Endpoint registration. WooCommerce adds a rewrite endpoint for
77 // every query var in this filter, so this is the single source of
78 // registration for both endpoints.
79 add_filter( 'woocommerce_get_query_vars', array( __CLASS__, 'add_query_vars' ) );
80
81 // One-time rewrite flush so the new endpoints resolve without a
82 // manual Settings > Permalinks save.
83 add_action( 'init', array( __CLASS__, 'maybe_flush_rewrite_rules' ), 50 );
84
85 // The Subscriptions tab, above Logout.
86 add_filter( 'woocommerce_account_menu_items', array( __CLASS__, 'add_menu_item' ), 200 );
87
88 // Endpoint content + browser/page titles.
89 add_action( 'woocommerce_account_' . self::ENDPOINT . '_endpoint', array( __CLASS__, 'render_list' ) );
90 add_action( 'woocommerce_account_' . self::VIEW_ENDPOINT . '_endpoint', array( __CLASS__, 'render_view' ) );
91 add_filter( 'woocommerce_endpoint_' . self::ENDPOINT . '_title', array( __CLASS__, 'list_title' ) );
92 add_filter( 'woocommerce_endpoint_' . self::VIEW_ENDPOINT . '_title', array( __CLASS__, 'view_title' ) );
93
94 // Status badge / table styling, on My Account pages only.
95 add_action( 'wp_enqueue_scripts', array( __CLASS__, 'enqueue_styles' ), 20 );
96 }
97
98 /**
99 * Whether the My Account Subscriptions tab is enabled (E-commerce >
100 * Subscription > My Account Subscriptions Tab setting). Read live on
101 * every request — never cached, never inferred.
102 *
103 * @return bool
104 */
105 public static function enabled() {
106 return 'yes' === Subscriptions::setting( 'myaccount_tab' );
107 }
108
109 /**
110 * Register both endpoints as WooCommerce query vars. WooCommerce turns
111 * these into rewrite endpoints on `init`.
112 *
113 * @param array $vars WooCommerce query vars (internal name => slug).
114 * @return array
115 */
116 public static function add_query_vars( $vars ) {
117 $vars[ self::ENDPOINT ] = self::ENDPOINT;
118 $vars[ self::VIEW_ENDPOINT ] = self::VIEW_ENDPOINT;
119
120 return $vars;
121 }
122
123 /**
124 * Flush rewrite rules once per endpoint-schema version. Flushing them
125 * unconditionally rebuilds and re-saves the whole rule set on every
126 * request; keying on an option keeps this a one-time cost.
127 *
128 * @return void
129 */
130 public static function maybe_flush_rewrite_rules() {
131 if ( self::REWRITE_VERSION === get_option( self::REWRITE_OPTION ) ) {
132 return;
133 }
134
135 flush_rewrite_rules();
136 update_option( self::REWRITE_OPTION, self::REWRITE_VERSION );
137 }
138
139 /**
140 * Add the Subscriptions tab to the My Account menu (gated on the
141 * My Account Tab setting).
142 *
143 * @param array $items Menu items (endpoint => label).
144 * @return array
145 */
146 public static function add_menu_item( $items ) {
147 if ( ! self::enabled() ) {
148 return $items;
149 }
150
151 return self::insert_menu_item( (array) $items, __( 'Subscriptions', 'better-payment' ) );
152 }
153
154 /**
155 * Pure: insert the Subscriptions item before Logout, preserving the
156 * existing order. Appends when there is no logout item (a theme may have
157 * removed it), and never duplicates an existing entry.
158 *
159 * @param array $items Menu items (endpoint => label).
160 * @param string $label Tab label.
161 * @return array
162 */
163 public static function insert_menu_item( $items, $label ) {
164 if ( isset( $items[ self::ENDPOINT ] ) ) {
165 return $items;
166 }
167
168 $updated = array();
169
170 foreach ( $items as $endpoint => $item_label ) {
171 if ( 'customer-logout' === $endpoint ) {
172 $updated[ self::ENDPOINT ] = $label;
173 }
174
175 $updated[ $endpoint ] = $item_label;
176 }
177
178 if ( ! isset( $updated[ self::ENDPOINT ] ) ) {
179 $updated[ self::ENDPOINT ] = $label;
180 }
181
182 return $updated;
183 }
184
185 /**
186 * Browser/page title for the list endpoint.
187 *
188 * @param string $title Default endpoint title.
189 * @return string
190 */
191 public static function list_title( $title ) {
192 return __( 'Subscriptions', 'better-payment' );
193 }
194
195 /**
196 * Browser/page title for the single-subscription endpoint.
197 *
198 * @param string $title Default endpoint title.
199 * @return string
200 */
201 public static function view_title( $title ) {
202 $order_id = absint( get_query_var( self::VIEW_ENDPOINT ) );
203
204 if ( $order_id > 0 ) {
205 /* translators: %d: subscription (parent order) id */
206 return sprintf( __( 'Subscription #%d', 'better-payment' ), $order_id );
207 }
208
209 return __( 'Subscription', 'better-payment' );
210 }
211
212 /**
213 * URL of the subscriptions list (optionally a specific page).
214 *
215 * @param int $page List page number (1 emits the bare endpoint URL).
216 * @return string
217 */
218 public static function list_url( $page = 1 ) {
219 if ( ! function_exists( 'wc_get_endpoint_url' ) || ! function_exists( 'wc_get_page_permalink' ) ) {
220 return '';
221 }
222
223 $page = max( 1, (int) $page );
224
225 return wc_get_endpoint_url( self::ENDPOINT, ( $page > 1 ) ? (string) $page : '', wc_get_page_permalink( 'myaccount' ) );
226 }
227
228 /**
229 * URL of one subscription's My Account view.
230 *
231 * @param int $order_id Parent (subscription) order id.
232 * @return string
233 */
234 public static function view_url( $order_id ) {
235 if ( ! function_exists( 'wc_get_endpoint_url' ) || ! function_exists( 'wc_get_page_permalink' ) ) {
236 return '';
237 }
238
239 return wc_get_endpoint_url( self::VIEW_ENDPOINT, (string) absint( $order_id ), wc_get_page_permalink( 'myaccount' ) );
240 }
241
242 /**
243 * Enqueue the subscription table/badge styles on My Account pages only —
244 * never site-wide (a status stylesheet loaded on every frontend page is
245 * pure weight on the pages that show no subscription).
246 *
247 * @return void
248 */
249 public static function enqueue_styles() {
250 if ( ! self::enabled() || ! function_exists( 'is_account_page' ) || ! is_account_page() ) {
251 return;
252 }
253
254 wp_enqueue_style( 'better-payment-wc-subscriptions' );
255 }
256
257 /* ---------------------------------------------------------------------
258 * List view
259 * ------------------------------------------------------------------- */
260
261 /**
262 * Render the subscriptions list endpoint.
263 *
264 * Ownership is enforced at the query: only the current user's own
265 * subscription parent orders are fetched. Logged-out visitors get
266 * WooCommerce's own My Account login form before this ever runs, but the
267 * guard stays anyway — an endpoint callback must never rely on the page
268 * around it for authentication.
269 *
270 * @param mixed $current_page Endpoint value (list page number).
271 * @return void
272 */
273 public static function render_list( $current_page ) {
274 if ( ! self::enabled() || ! is_user_logged_in() || ! function_exists( 'wc_get_orders' ) ) {
275 return;
276 }
277
278 $page = max( 1, absint( $current_page ) );
279 $result = Subscriptions::customer_subscriptions( get_current_user_id(), $page, self::PER_PAGE );
280
281 if ( empty( $result['orders'] ) ) {
282 self::render_empty_list();
283 return;
284 }
285
286 ?>
287 <table class="woocommerce-orders-table shop_table shop_table_responsive my_account_orders bp-subscriptions-table">
288 <thead>
289 <tr>
290 <th><span class="nobr"><?php esc_html_e( 'Subscription', 'better-payment' ); ?></span></th>
291 <th><span class="nobr"><?php esc_html_e( 'Product', 'better-payment' ); ?></span></th>
292 <th><span class="nobr"><?php esc_html_e( 'Status', 'better-payment' ); ?></span></th>
293 <th><span class="nobr"><?php esc_html_e( 'Next payment', 'better-payment' ); ?></span></th>
294 <th><span class="nobr"><?php esc_html_e( 'Total', 'better-payment' ); ?></span></th>
295 <th><span class="nobr"><?php esc_html_e( 'Actions', 'better-payment' ); ?></span></th>
296 </tr>
297 </thead>
298 <tbody>
299 <?php foreach ( $result['orders'] as $order ) : ?>
300 <?php
301 if ( ! $order instanceof \WC_Order ) {
302 continue;
303 }
304
305 $status = (string) $order->get_meta( Subscriptions::STATUS_META );
306 ?>
307 <tr>
308 <td data-title="<?php esc_attr_e( 'Subscription', 'better-payment' ); ?>">
309 <a href="<?php echo esc_url( self::view_url( $order->get_id() ) ); ?>">
310 <?php echo esc_html( '#' . $order->get_order_number() ); ?>
311 </a>
312 </td>
313 <td data-title="<?php esc_attr_e( 'Product', 'better-payment' ); ?>">
314 <?php echo esc_html( self::product_names( $order ) ); ?>
315 </td>
316 <td data-title="<?php esc_attr_e( 'Status', 'better-payment' ); ?>">
317 <?php self::render_status_badge( $status ); ?>
318 </td>
319 <td data-title="<?php esc_attr_e( 'Next payment', 'better-payment' ); ?>">
320 <?php echo esc_html( self::next_payment_text( $order, $status ) ); ?>
321 </td>
322 <td data-title="<?php esc_attr_e( 'Total', 'better-payment' ); ?>">
323 <?php echo wp_kses_post( self::amount_text( $order ) ); ?>
324 </td>
325 <td data-title="<?php esc_attr_e( 'Actions', 'better-payment' ); ?>">
326 <a class="woocommerce-button button view" href="<?php echo esc_url( self::view_url( $order->get_id() ) ); ?>">
327 <?php esc_html_e( 'View', 'better-payment' ); ?>
328 </a>
329 </td>
330 </tr>
331 <?php endforeach; ?>
332 </tbody>
333 </table>
334 <?php
335
336 self::render_pagination( $page, (int) $result['max_pages'] );
337 }
338
339 /**
340 * Empty state — a notice plus a route to the shop, mirroring
341 * WooCommerce's own empty orders list.
342 *
343 * @return void
344 */
345 protected static function render_empty_list() {
346 ?>
347 <div class="woocommerce-message woocommerce-message--info woocommerce-Message woocommerce-Message--info woocommerce-info">
348 <?php if ( function_exists( 'wc_get_page_permalink' ) ) : ?>
349 <a class="woocommerce-Button button" href="<?php echo esc_url( wc_get_page_permalink( 'shop' ) ); ?>">
350 <?php esc_html_e( 'Browse products', 'better-payment' ); ?>
351 </a>
352 <?php endif; ?>
353 <?php esc_html_e( 'You have no subscriptions yet.', 'better-payment' ); ?>
354 </div>
355 <?php
356 }
357
358 /**
359 * Previous/next pagination for the list. Built from the endpoint
360 * constants — never a hardcoded slug, which breaks the moment the
361 * endpoint slug is customized.
362 *
363 * @param int $page Current page (1-based).
364 * @param int $max_pages Total pages.
365 * @return void
366 */
367 protected static function render_pagination( $page, $max_pages ) {
368 if ( $max_pages <= 1 ) {
369 return;
370 }
371
372 ?>
373 <div class="woocommerce-pagination woocommerce-pagination--without-numbers woocommerce-Pagination">
374 <?php if ( $page > 1 ) : ?>
375 <a class="woocommerce-button woocommerce-button--previous woocommerce-Button woocommerce-Button--previous button" href="<?php echo esc_url( self::list_url( $page - 1 ) ); ?>">
376 <?php esc_html_e( 'Previous', 'better-payment' ); ?>
377 </a>
378 <?php endif; ?>
379 <?php if ( $page < $max_pages ) : ?>
380 <a class="woocommerce-button woocommerce-button--next woocommerce-Button woocommerce-Button--next button" href="<?php echo esc_url( self::list_url( $page + 1 ) ); ?>">
381 <?php esc_html_e( 'Next', 'better-payment' ); ?>
382 </a>
383 <?php endif; ?>
384 </div>
385 <?php
386 }
387
388 /* ---------------------------------------------------------------------
389 * Single subscription view
390 * ------------------------------------------------------------------- */
391
392 /**
393 * Render the single-subscription endpoint.
394 *
395 * Security gates, in order: feature enabled, viewer logged in, the order
396 * exists, the order IS a subscription parent (has subscription status
397 * meta), and the viewer is the order's own customer. A failed gate
398 * renders an error notice — never a redirect, because endpoint content
399 * runs mid-template after headers are sent (redirecting here lands the
400 * customer on a broken '/404' path instead of the notice).
401 *
402 * @param mixed $order_id Endpoint value (parent order id).
403 * @return void
404 */
405 public static function render_view( $order_id ) {
406 if ( ! self::enabled() || ! function_exists( 'wc_get_order' ) ) {
407 return;
408 }
409
410 if ( ! is_user_logged_in() ) {
411 self::render_invalid_notice();
412 return;
413 }
414
415 $order = wc_get_order( absint( $order_id ) );
416
417 if (
418 ! $order instanceof \WC_Order
419 || '' === (string) $order->get_meta( Subscriptions::STATUS_META )
420 || get_current_user_id() !== $order->get_customer_id()
421 ) {
422 self::render_invalid_notice();
423 return;
424 }
425
426 $status = (string) $order->get_meta( Subscriptions::STATUS_META );
427
428 self::render_details_table( $order, $status );
429 self::render_actions( $order );
430 self::render_related_orders( $order );
431
432 ?>
433 <p class="bp-subscription-back">
434 <a class="woocommerce-button button" href="<?php echo esc_url( self::list_url() ); ?>">
435 <?php esc_html_e( 'Back to subscriptions', 'better-payment' ); ?>
436 </a>
437 </p>
438 <?php
439 }
440
441 /**
442 * "Invalid subscription" notice — same message for a missing order, a
443 * non-subscription order, and someone else's subscription, so the
444 * response does not confirm whether a guessed id exists.
445 *
446 * @return void
447 */
448 protected static function render_invalid_notice() {
449 if ( function_exists( 'wc_print_notice' ) ) {
450 wc_print_notice( __( 'That subscription could not be found.', 'better-payment' ), 'error' );
451 }
452
453 ?>
454 <p>
455 <a class="woocommerce-button button" href="<?php echo esc_url( self::list_url() ); ?>">
456 <?php esc_html_e( 'Back to subscriptions', 'better-payment' ); ?>
457 </a>
458 </p>
459 <?php
460 }
461
462 /**
463 * The subscription details table (status, schedule, dates, renewals,
464 * auto-renewal state, payment method, parent order link).
465 *
466 * @param \WC_Order $order Parent (subscription) order.
467 * @param string $status Raw subscription status.
468 * @return void
469 */
470 protected static function render_details_table( $order, $status ) {
471 $created = $order->get_date_created();
472 $next_payment = self::next_payment_text( $order, $status );
473 $count = (int) $order->get_meta( Subscriptions::RENEWAL_COUNT_META );
474
475 ?>
476 <h2 class="bp-subscription-section-title"><?php esc_html_e( 'Subscription details', 'better-payment' ); ?></h2>
477 <table class="woocommerce-table shop_table bp-subscription-details">
478 <tbody>
479 <tr>
480 <th><?php esc_html_e( 'Status', 'better-payment' ); ?></th>
481 <td><?php self::render_status_badge( $status ); ?></td>
482 </tr>
483 <tr>
484 <th><?php esc_html_e( 'Product', 'better-payment' ); ?></th>
485 <td><?php echo esc_html( self::product_names( $order ) ); ?></td>
486 </tr>
487 <tr>
488 <th><?php esc_html_e( 'Amount', 'better-payment' ); ?></th>
489 <td><?php echo wp_kses_post( self::amount_text( $order ) ); ?></td>
490 </tr>
491 <tr>
492 <th><?php esc_html_e( 'Started', 'better-payment' ); ?></th>
493 <td><?php echo esc_html( $created ? date_i18n( get_option( 'date_format' ), $created->getTimestamp() ) : '' ); ?></td>
494 </tr>
495 <tr>
496 <th><?php esc_html_e( 'Next payment', 'better-payment' ); ?></th>
497 <td><?php echo esc_html( '' !== $next_payment ? $next_payment : '' ); ?></td>
498 </tr>
499 <tr>
500 <th><?php esc_html_e( 'Renewals', 'better-payment' ); ?></th>
501 <td><?php echo esc_html( (string) $count ); ?></td>
502 </tr>
503 <?php if ( 'yes' === (string) $order->get_meta( Subscriptions::TRIAL_APPLIED_META ) ) : ?>
504 <tr>
505 <th><?php esc_html_e( 'Free trial', 'better-payment' ); ?></th>
506 <td><?php esc_html_e( 'Applied on signup — the first payment was charged when the trial ended.', 'better-payment' ); ?></td>
507 </tr>
508 <?php endif; ?>
509 <?php if ( Subscriptions::should_save_payment_method() ) : ?>
510 <tr>
511 <th><?php esc_html_e( 'Automatic renewal', 'better-payment' ); ?></th>
512 <td>
513 <?php
514 echo esc_html(
515 Subscriptions::auto_renewal_enabled_for( $order )
516 ? __( 'On', 'better-payment' )
517 : __( 'Off', 'better-payment' )
518 );
519 ?>
520 </td>
521 </tr>
522 <?php endif; ?>
523 <?php if ( '' !== (string) $order->get_payment_method_title() ) : ?>
524 <tr>
525 <th><?php esc_html_e( 'Payment method', 'better-payment' ); ?></th>
526 <td><?php echo esc_html( $order->get_payment_method_title() ); ?></td>
527 </tr>
528 <?php endif; ?>
529 <tr>
530 <th><?php esc_html_e( 'Signup order', 'better-payment' ); ?></th>
531 <td>
532 <a href="<?php echo esc_url( $order->get_view_order_url() ); ?>">
533 <?php echo esc_html( '#' . $order->get_order_number() ); ?>
534 </a>
535 </td>
536 </tr>
537 </tbody>
538 </table>
539 <?php
540 }
541
542 /**
543 * Customer actions: pay a pending renewal invoice, cancel, and the
544 * auto-renewal toggle. Cancel and the toggle are the existing
545 * nonce-guarded POST forms from {@see Subscriptions} — rendered in
546 * 'subscription' context so their handlers redirect back here. Each
547 * renders only when its own policy allows it (user-cancel snapshot,
548 * Customer Auto-Renew Control setting, live status, stored payment method).
549 *
550 * @param \WC_Order $order Parent (subscription) order.
551 * @return void
552 */
553 protected static function render_actions( $order ) {
554 $pending = Subscriptions::pending_renewal_order( $order );
555
556 ob_start();
557
558 if ( $pending instanceof \WC_Order && get_current_user_id() === $pending->get_customer_id() ) {
559 ?>
560 <a class="woocommerce-button button bp-subscription-pay" href="<?php echo esc_url( $pending->get_checkout_payment_url() ); ?>">
561 <?php esc_html_e( 'Pay renewal invoice', 'better-payment' ); ?>
562 </a>
563 <?php
564 }
565
566 Subscriptions::render_auto_renew_toggle( $order, 'subscription' );
567 Subscriptions::render_customer_cancel_button( $order, 'subscription' );
568
569 $actions = trim( (string) ob_get_clean() );
570
571 if ( '' === $actions ) {
572 return;
573 }
574
575 ?>
576 <h2 class="bp-subscription-section-title"><?php esc_html_e( 'Manage subscription', 'better-payment' ); ?></h2>
577 <div class="bp-subscription-actions">
578 <?php echo $actions; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- built above from fully escaped partials. ?>
579 </div>
580 <?php
581 }
582
583 /**
584 * Related orders (signup + renewals) from the subscription/order
585 * relation table. Only orders that belong to the viewing customer are
586 * listed — a relation row must never leak another customer's order.
587 *
588 * @param \WC_Order $parent Parent (subscription) order.
589 * @return void
590 */
591 protected static function render_related_orders( $parent ) {
592 $relations = SubscriptionRelationModel::get_orders( $parent->get_id(), 'woo' );
593
594 if ( empty( $relations ) ) {
595 return;
596 }
597
598 $rows = array();
599
600 foreach ( $relations as $relation ) {
601 $order = wc_get_order( absint( $relation->order_id ) );
602
603 if ( ! $order instanceof \WC_Order || get_current_user_id() !== $order->get_customer_id() ) {
604 continue;
605 }
606
607 $rows[] = array(
608 'order' => $order,
609 'type' => (string) $relation->type,
610 );
611 }
612
613 if ( empty( $rows ) ) {
614 return;
615 }
616
617 ?>
618 <h2 class="bp-subscription-section-title"><?php esc_html_e( 'Related orders', 'better-payment' ); ?></h2>
619 <table class="woocommerce-table shop_table shop_table_responsive bp-subscription-orders">
620 <thead>
621 <tr>
622 <th><span class="nobr"><?php esc_html_e( 'Order', 'better-payment' ); ?></span></th>
623 <th><span class="nobr"><?php esc_html_e( 'Date', 'better-payment' ); ?></span></th>
624 <th><span class="nobr"><?php esc_html_e( 'Type', 'better-payment' ); ?></span></th>
625 <th><span class="nobr"><?php esc_html_e( 'Status', 'better-payment' ); ?></span></th>
626 <th><span class="nobr"><?php esc_html_e( 'Total', 'better-payment' ); ?></span></th>
627 </tr>
628 </thead>
629 <tbody>
630 <?php foreach ( $rows as $row ) : ?>
631 <?php
632 $order = $row['order'];
633 $created = $order->get_date_created();
634 ?>
635 <tr>
636 <td data-title="<?php esc_attr_e( 'Order', 'better-payment' ); ?>">
637 <a href="<?php echo esc_url( $order->get_view_order_url() ); ?>">
638 <?php echo esc_html( '#' . $order->get_order_number() ); ?>
639 </a>
640 </td>
641 <td data-title="<?php esc_attr_e( 'Date', 'better-payment' ); ?>">
642 <?php echo esc_html( $created ? date_i18n( get_option( 'date_format' ), $created->getTimestamp() ) : '' ); ?>
643 </td>
644 <td data-title="<?php esc_attr_e( 'Type', 'better-payment' ); ?>">
645 <?php echo esc_html( 'renew' === $row['type'] ? __( 'Renewal', 'better-payment' ) : __( 'Signup', 'better-payment' ) ); ?>
646 </td>
647 <td data-title="<?php esc_attr_e( 'Status', 'better-payment' ); ?>">
648 <?php echo esc_html( function_exists( 'wc_get_order_status_name' ) ? wc_get_order_status_name( $order->get_status() ) : ucfirst( $order->get_status() ) ); ?>
649 </td>
650 <td data-title="<?php esc_attr_e( 'Total', 'better-payment' ); ?>">
651 <?php echo wp_kses_post( $order->get_formatted_order_total() ); ?>
652 </td>
653 </tr>
654 <?php endforeach; ?>
655 </tbody>
656 </table>
657 <?php
658 }
659
660 /* ---------------------------------------------------------------------
661 * Display helpers
662 * ------------------------------------------------------------------- */
663
664 /**
665 * The subscription product name(s) on an order, comma-joined.
666 *
667 * @param \WC_Order $order Parent (subscription) order.
668 * @return string
669 */
670 protected static function product_names( $order ) {
671 $names = array();
672
673 foreach ( Subscriptions::order_subscription_items( $order ) as $item ) {
674 $names[] = $item->get_name();
675 }
676
677 return implode( ', ', $names );
678 }
679
680 /**
681 * "$10.00 / month" — the order total plus the billing schedule
682 * snapshotted on the parent order.
683 *
684 * @param \WC_Order $order Parent (subscription) order.
685 * @return string HTML (wc_price output).
686 */
687 protected static function amount_text( $order ) {
688 $interval = max( 1, (int) $order->get_meta( Subscriptions::INTERVAL_META ) );
689 $period = Subscriptions::sanitize_period( $order->get_meta( Subscriptions::PERIOD_META ) );
690 $price = function_exists( 'wc_price' )
691 ? wc_price( $order->get_total(), array( 'currency' => $order->get_currency() ) )
692 : $order->get_currency() . ' ' . $order->get_total();
693
694 return sprintf(
695 /* translators: 1: formatted price, 2: billing schedule ("month", "every 3 months") */
696 __( '%1$s / %2$s', 'better-payment' ),
697 $price,
698 Subscriptions::describe_schedule( $interval, $period )
699 );
700 }
701
702 /**
703 * Next-payment date text — empty on terminal statuses (a cancelled
704 * subscription has no next charge, and a stale date reads as one).
705 *
706 * @param \WC_Order $order Parent (subscription) order.
707 * @param string $status Raw subscription status.
708 * @return string
709 */
710 protected static function next_payment_text( $order, $status ) {
711 $next = (int) $order->get_meta( Subscriptions::NEXT_PAYMENT_META );
712
713 if ( $next <= 0 || ! in_array( $status, array( 'active', 'past_due' ), true ) ) {
714 return '';
715 }
716
717 return date_i18n( get_option( 'date_format' ), $next );
718 }
719
720 /**
721 * Status badge markup.
722 *
723 * @param string $status Raw subscription status.
724 * @return void
725 */
726 protected static function render_status_badge( $status ) {
727 $status = strtolower( (string) $status );
728
729 printf(
730 '<span class="bp-subscription-status bp-subscription-status--%1$s">%2$s</span>',
731 esc_attr( sanitize_html_class( $status, 'unknown' ) ),
732 esc_html( Subscriptions::customer_status_label( $status ) )
733 );
734 }
735 }
736