| 1 |
<?php |
| 2 |
|
| 3 |
namespace SpringDevs\Subscription\Frontend; |
| 4 |
|
| 5 |
use SpringDevs\Subscription\Illuminate\Helper; |
| 6 |
use SpringDevs\Subscription\Illuminate\Subscription\Subscription; |
| 7 |
|
| 8 |
// HPOS: This file is compatible with WooCommerce High-Performance Order Storage (HPOS). |
| 9 |
// All WooCommerce order data is accessed via WooCommerce CRUD methods (wc_get_order, wc_get_order_item_meta, etc.). |
| 10 |
// All direct post meta access is for subscription data only, not WooCommerce order data. |
| 11 |
// If you add new order data access, use WooCommerce CRUD for HPOS compatibility. |
| 12 |
|
| 13 |
/** |
| 14 |
* Class MyAccount |
| 15 |
* |
| 16 |
* @package SpringDevs\Subscription\Frontend |
| 17 |
*/ |
| 18 |
class MyAccount { |
| 19 |
/** |
| 20 |
* Subscriptions endpoint slug. |
| 21 |
* |
| 22 |
* @var string |
| 23 |
*/ |
| 24 |
private $subscriptions_endpoint = 'subscriptions'; |
| 25 |
|
| 26 |
/** |
| 27 |
* View Subscriptions endpoint slug. |
| 28 |
* |
| 29 |
* @var string |
| 30 |
*/ |
| 31 |
private $view_subscriptions_endpoint = 'view-subscription'; |
| 32 |
|
| 33 |
|
| 34 |
/** |
| 35 |
* Initialize the class |
| 36 |
*/ |
| 37 |
public function __construct() { |
| 38 |
// Assign endpoint slug from settings. |
| 39 |
$this->subscriptions_endpoint = Subscription::get_user_endpoint( 'subs_list' ); |
| 40 |
$this->view_subscriptions_endpoint = Subscription::get_user_endpoint( 'view_subs' ); |
| 41 |
|
| 42 |
// Flush rewrite rules on init. |
| 43 |
add_action( 'init', array( $this, 'flush_rewrite_rules' ) ); |
| 44 |
|
| 45 |
// Add My Subscriptions menu item. |
| 46 |
add_filter( 'woocommerce_account_menu_items', array( $this, 'custom_my_account_menu_items' ), 200 ); |
| 47 |
|
| 48 |
// Add subscription url endpoints to query vars. |
| 49 |
add_filter( 'woocommerce_get_query_vars', array( $this, 'custom_query_vars' ) ); |
| 50 |
|
| 51 |
// Subscription EndPoint Content. |
| 52 |
add_action( "woocommerce_account_{$this->subscriptions_endpoint}_endpoint", array( $this, 'subscrpt_endpoint_content' ) ); |
| 53 |
add_action( "woocommerce_account_{$this->view_subscriptions_endpoint}_endpoint", array( $this, 'view_subscrpt_content' ) ); |
| 54 |
|
| 55 |
// Subscription page titles |
| 56 |
add_filter( "woocommerce_endpoint_{$this->subscriptions_endpoint}_title", array( $this, 'change_subscriptions_title' ) ); |
| 57 |
add_filter( "woocommerce_endpoint_{$this->view_subscriptions_endpoint}_title", array( $this, 'change_single_subscription_title' ) ); |
| 58 |
|
| 59 |
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_styles' ) ); |
| 60 |
} |
| 61 |
|
| 62 |
/** |
| 63 |
* Add custom url on MyAccount. |
| 64 |
* |
| 65 |
* @param array $query_vars query_vars. |
| 66 |
* |
| 67 |
* @return array |
| 68 |
*/ |
| 69 |
public function custom_query_vars( array $query_vars ): array { |
| 70 |
$query_vars[ $this->subscriptions_endpoint ] = $this->subscriptions_endpoint; |
| 71 |
$query_vars[ $this->view_subscriptions_endpoint ] = $this->view_subscriptions_endpoint; |
| 72 |
|
| 73 |
return $query_vars; |
| 74 |
} |
| 75 |
|
| 76 |
/** |
| 77 |
* Display Subscription Content. |
| 78 |
* |
| 79 |
* @param int $id Post ID. |
| 80 |
*/ |
| 81 |
public function view_subscrpt_content( $id ) { |
| 82 |
$id = absint( $id ); // typecast to int. |
| 83 |
|
| 84 |
$subs_post = get_post( $id ); |
| 85 |
$author_id = $subs_post ? (int) $subs_post->post_author : 0; |
| 86 |
$current_user_id = get_current_user_id(); |
| 87 |
$user_is_admin = current_user_can( 'manage_options' ); |
| 88 |
|
| 89 |
if ( ! $user_is_admin && (int) $author_id !== (int) $current_user_id ) { |
| 90 |
return wp_safe_redirect( '/404' ); |
| 91 |
} |
| 92 |
|
| 93 |
$subscription_id = $id; |
| 94 |
$subscription_data = Helper::get_subscription_data( $subscription_id ); |
| 95 |
$related_orders = Helper::get_related_orders( $subscription_id ); |
| 96 |
|
| 97 |
$status = $subscription_data['status'] ?? ''; |
| 98 |
$verbose_status = Helper::get_verbose_status( $status ); |
| 99 |
|
| 100 |
$order_id = $subscription_data['order']['order_id'] ?? 0; |
| 101 |
$order_item_id = $subscription_data['order']['order_item_id'] ?? 0; |
| 102 |
|
| 103 |
$order = wc_get_order( $order_id ); |
| 104 |
$order_item = $order ? $order->get_item( $order_item_id ) : null; |
| 105 |
|
| 106 |
if ( ! $order || ! $order_item ) { |
| 107 |
return wp_safe_redirect( '/404' ); |
| 108 |
} |
| 109 |
|
| 110 |
$user_cancel = $subscription_data['can_user_cancel'] ?? false; |
| 111 |
|
| 112 |
$start_date = $subscription_data['start_date'] ?? ''; |
| 113 |
$start_date = ! empty( $start_date ) ? wp_date( 'F j, Y', strtotime( $start_date ) ) : '-'; |
| 114 |
|
| 115 |
$next_date = $subscription_data['next_date'] ?? ''; |
| 116 |
$next_date = ! empty( $next_date ) ? wp_date( 'F j, Y', strtotime( $next_date ) ) : '-'; |
| 117 |
|
| 118 |
$trial = get_post_meta( $id, '_subscrpt_trial', true ); |
| 119 |
$trial_mode = get_post_meta( $id, '_subscrpt_trial_mode', true ); |
| 120 |
|
| 121 |
// Undiscounted subtotal, the discount that recurs, tax on the discounted amount, and the renewal total. |
| 122 |
$display_totals = Helper::get_subscription_display_totals( $id, $order_item ); |
| 123 |
|
| 124 |
$price = $display_totals['total']; |
| 125 |
$price_excl_tax = $display_totals['full_excl']; |
| 126 |
$tax_amount = $display_totals['tax']; |
| 127 |
$discount = $display_totals['discount']; |
| 128 |
|
| 129 |
$is_grace_period = isset( $subscription_data['grace_period'] ); |
| 130 |
$grace_remaining = $subscription_data['grace_period']['remaining_days'] ?? 0; |
| 131 |
$grace_end_date = $subscription_data['grace_period']['end_date'] ?? ''; |
| 132 |
$grace_end_date = ! empty( $grace_end_date ) ? wp_date( 'F j, Y', strtotime( $grace_end_date ) ) : ''; |
| 133 |
|
| 134 |
$subscrpt_nonce = wp_create_nonce( 'subscrpt_nonce' ); |
| 135 |
$action_buttons = array(); |
| 136 |
|
| 137 |
if ( 'cancelled' !== $status ) { |
| 138 |
if ( in_array( $status, array( 'pending', 'active', 'on_hold' ), true ) && $user_cancel ) { |
| 139 |
$label = __( 'Cancel', 'subscription' ); |
| 140 |
$label = apply_filters( 'subscrpt_split_payment_button_text', $label, 'cancel', $id, $status ); |
| 141 |
|
| 142 |
$action_buttons['cancel'] = array( |
| 143 |
'url' => subscrpt_get_action_url( 'cancelled', $subscrpt_nonce, $id ), |
| 144 |
'label' => $label, |
| 145 |
'class' => 'cancel', |
| 146 |
); |
| 147 |
} elseif ( trim( $status ) === trim( 'pe_cancelled' ) ) { |
| 148 |
$label = __( 'Reactivate', 'subscription' ); |
| 149 |
$label = apply_filters( 'subscrpt_split_payment_button_text', $label, 'reactivate', $id, $status ); |
| 150 |
|
| 151 |
$action_buttons['reactivate'] = array( |
| 152 |
'url' => subscrpt_get_action_url( 'reactivate', $subscrpt_nonce, $id ), |
| 153 |
'label' => $label, |
| 154 |
); |
| 155 |
} elseif ( 'expired' === $status && 'pending' !== $order->get_status() ) { |
| 156 |
// Check if maximum payments reached before showing renew button |
| 157 |
if ( ! subscrpt_is_max_payments_reached( $id ) ) { |
| 158 |
$label = __( 'Renew', 'subscription' ); |
| 159 |
$label = apply_filters( 'subscrpt_split_payment_button_text', $label, 'renew', $id, $status ); |
| 160 |
|
| 161 |
$action_buttons['renew'] = array( |
| 162 |
'url' => subscrpt_get_action_url( 'renew', $subscrpt_nonce, $id ), |
| 163 |
'label' => $label, |
| 164 |
); |
| 165 |
} |
| 166 |
} |
| 167 |
|
| 168 |
if ( 'pending' === $order->get_status() ) { |
| 169 |
$label = __( 'Pay now', 'subscription' ); |
| 170 |
$label = apply_filters( 'subscrpt_split_payment_button_text', $label, 'pay_now', $id, $status ); |
| 171 |
|
| 172 |
$action_buttons['pay_now'] = array( |
| 173 |
'url' => $order->get_checkout_payment_url(), |
| 174 |
'label' => $label, |
| 175 |
); |
| 176 |
} |
| 177 |
} |
| 178 |
|
| 179 |
$is_auto_renew = $subscription_data['is_auto_renew']; |
| 180 |
$renewal_setting = in_array( get_option( 'wp_subscription_auto_renewal_toggle', '1' ), [ 1, '1', 'true', 'yes' ], true ); |
| 181 |
|
| 182 |
$saved_methods = wc_get_customer_saved_methods_list( get_current_user_id() ); |
| 183 |
$has_methods = isset( $saved_methods['cc'] ); |
| 184 |
if ( $has_methods && $renewal_setting && class_exists( 'WC_Stripe' ) && $order && 'stripe' === $order->get_payment_method() ) { |
| 185 |
// Check maximum payment limit for auto-renewal buttons too |
| 186 |
if ( ! subscrpt_is_max_payments_reached( $id ) ) { |
| 187 |
if ( ! $is_auto_renew ) { |
| 188 |
$label = __( 'Turn on Auto Renewal', 'subscription' ); |
| 189 |
$label = apply_filters( 'subscrpt_split_payment_button_text', $label, 'auto-renew-on', $id, $status ); |
| 190 |
|
| 191 |
$action_buttons['auto-renew-on'] = array( |
| 192 |
'url' => subscrpt_get_action_url( 'renew-on', $subscrpt_nonce, $id ), |
| 193 |
'label' => $label, |
| 194 |
); |
| 195 |
} else { |
| 196 |
$label = __( 'Turn off Auto Renewal', 'subscription' ); |
| 197 |
$label = apply_filters( 'subscrpt_split_payment_button_text', $label, 'auto-renew-off', $id, $status ); |
| 198 |
|
| 199 |
$action_buttons['auto-renew-off'] = array( |
| 200 |
'url' => subscrpt_get_action_url( 'renew-off', $subscrpt_nonce, $id ), |
| 201 |
'label' => $label, |
| 202 |
); |
| 203 |
} |
| 204 |
} |
| 205 |
} |
| 206 |
|
| 207 |
// Allow programmatically disabling cancel button |
| 208 |
$disable_cancel = apply_filters( 'subscrpt_split_payment_disable_cancel', false, $id, $status ); |
| 209 |
if ( $disable_cancel && isset( $action_buttons['cancel'] ) ) { |
| 210 |
unset( $action_buttons['cancel'] ); |
| 211 |
} |
| 212 |
|
| 213 |
$action_buttons = apply_filters( 'subscrpt_single_action_buttons', $action_buttons, $id, $subscrpt_nonce, $status ); |
| 214 |
|
| 215 |
wc_get_template( |
| 216 |
'myaccount/single.php', |
| 217 |
array( |
| 218 |
'id' => $id, |
| 219 |
'status' => $status, |
| 220 |
'verbose_status' => $verbose_status, |
| 221 |
'start_date' => $start_date, |
| 222 |
'next_date' => $next_date, |
| 223 |
'is_grace_period' => $is_grace_period, |
| 224 |
'grace_remaining' => $grace_remaining, |
| 225 |
'grace_end_date' => $grace_end_date, |
| 226 |
'trial' => $trial, |
| 227 |
'trial_mode' => empty( $trial_mode ) ? 'off' : $trial_mode, |
| 228 |
'order' => $order, |
| 229 |
'order_item' => $order_item, |
| 230 |
'related_orders' => $related_orders, |
| 231 |
'price' => $price, |
| 232 |
'price_excl_tax' => $price_excl_tax, |
| 233 |
'tax' => $tax_amount, |
| 234 |
'discount' => $discount, |
| 235 |
'user_cancel' => $user_cancel, |
| 236 |
'action_buttons' => $action_buttons, |
| 237 |
'wp_button_class' => wc_wp_theme_get_element_class_name( 'button' ) ? ' ' . wc_wp_theme_get_element_class_name( 'button' ) : '', |
| 238 |
), |
| 239 |
'subscription', |
| 240 |
SUBSCRPT_TEMPLATES |
| 241 |
); |
| 242 |
} |
| 243 |
|
| 244 |
/** |
| 245 |
* Re-write flush |
| 246 |
*/ |
| 247 |
public function flush_rewrite_rules() { |
| 248 |
add_rewrite_endpoint( $this->subscriptions_endpoint, EP_ROOT | EP_PAGES ); |
| 249 |
flush_rewrite_rules(); |
| 250 |
} |
| 251 |
|
| 252 |
/** |
| 253 |
* Change All Subscriptions Title |
| 254 |
* |
| 255 |
* @param string $title Title. |
| 256 |
* |
| 257 |
* @return string |
| 258 |
*/ |
| 259 |
public function change_subscriptions_title( string $title ): string { |
| 260 |
$title = __( 'My Subscriptions', 'subscription' ); |
| 261 |
return $title; |
| 262 |
} |
| 263 |
|
| 264 |
/** |
| 265 |
* Change View Subscription Title |
| 266 |
* |
| 267 |
* @param string $title Title. |
| 268 |
* |
| 269 |
* @return string |
| 270 |
*/ |
| 271 |
public function change_single_subscription_title( string $title ): string { |
| 272 |
$subs_id = get_query_var( $this->view_subscriptions_endpoint, 0 ); |
| 273 |
|
| 274 |
/* translators: %s: Subscription ID */ |
| 275 |
$title = sprintf( __( 'Subscription #%s', 'subscription' ), $subs_id ); |
| 276 |
return $title; |
| 277 |
} |
| 278 |
|
| 279 |
/** |
| 280 |
* Filter menu items. |
| 281 |
* |
| 282 |
* @param array $items MyAccount menu items. |
| 283 |
* @return array |
| 284 |
*/ |
| 285 |
public function custom_my_account_menu_items( array $items ): array { |
| 286 |
// Check if subscriptions menu item already exists to prevent duplicates |
| 287 |
if ( ! isset( $items[ $this->subscriptions_endpoint ] ) ) { |
| 288 |
$logout = $items['customer-logout']; |
| 289 |
unset( $items['customer-logout'] ); |
| 290 |
|
| 291 |
$items[ $this->subscriptions_endpoint ] = __( 'Subscriptions', 'subscription' ); |
| 292 |
$items['customer-logout'] = $logout; |
| 293 |
} |
| 294 |
return $items; |
| 295 |
} |
| 296 |
|
| 297 |
/** |
| 298 |
* Subscription Single EndPoint Content. |
| 299 |
* |
| 300 |
* @param int $current_page Current Page. |
| 301 |
*/ |
| 302 |
public function subscrpt_endpoint_content( $current_page ) { |
| 303 |
$current_page = empty( $current_page ) ? 1 : absint( $current_page ); |
| 304 |
$args = array( |
| 305 |
'author' => get_current_user_id(), |
| 306 |
'posts_per_page' => 10, |
| 307 |
'paged' => $current_page, |
| 308 |
'post_type' => 'subscrpt_order', |
| 309 |
'post_status' => array( 'pending', 'active', 'on_hold', 'cancelled', 'expired', 'pe_cancelled', 'completed' ), |
| 310 |
); |
| 311 |
|
| 312 |
$postslist = new \WP_Query( $args ); |
| 313 |
wc_get_template( |
| 314 |
'myaccount/subscriptions.php', |
| 315 |
array( |
| 316 |
'wp_button_class' => wc_wp_theme_get_element_class_name( 'button' ) ? ' ' . wc_wp_theme_get_element_class_name( 'button' ) : '', |
| 317 |
'postslist' => $postslist, |
| 318 |
'current_page' => $current_page, |
| 319 |
), |
| 320 |
'subscription', |
| 321 |
SUBSCRPT_TEMPLATES |
| 322 |
); |
| 323 |
} |
| 324 |
|
| 325 |
/** |
| 326 |
* Enqueue assets |
| 327 |
*/ |
| 328 |
public function enqueue_styles() { |
| 329 |
wp_enqueue_style( 'subscrpt_status_css' ); |
| 330 |
} |
| 331 |
} |
| 332 |
|