\ToDownload\Sellers\Sellers::ROLE,
'number' => 2,
'fields' => 'ID',
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Required lookup.
'meta_key' => \ToDownload\Sellers\Sellers::USER_META_HOSTED_STORE_PUBLIC_ID,
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value -- Required lookup.
'meta_value' => $store_public_id,
]);
if (is_array($seller_ids) && count($seller_ids) === 1) {
return (int) $seller_ids[0];
}
// Fallback: if there is exactly one seller user, use them.
$all_sellers = get_users([
'role' => \ToDownload\Sellers\Sellers::ROLE,
'number' => 2,
'fields' => 'ID',
]);
if (is_array($all_sellers) && count($all_sellers) === 1) {
return (int) $all_sellers[0];
}
return 0;
}
public static function register(): void {
// New canonical shortcodes.
add_shortcode('ToDownload_downloads', [self::class, 'downloads_grid']);
add_shortcode('ToDownload_purchase_link', [self::class, 'purchase_link']);
add_shortcode('ToDownload_cart', [self::class, 'cart']);
add_shortcode('ToDownload_cart_link', [self::class, 'cart_link']);
add_shortcode('ToDownload_checkout', [self::class, 'checkout']);
add_shortcode('ToDownload_purchase_history', [self::class, 'purchase_history']);
add_shortcode('ToDownload_account', [self::class, 'account']);
add_shortcode('ToDownload_subscriptions', [self::class, 'subscriptions']);
add_shortcode('ToDownload_downloads_library', [self::class, 'downloads_library']);
add_shortcode('ToDownload_receipt', [self::class, 'receipt']);
// Seller growth tools.
add_shortcode('ToDownload_storefront', [self::class, 'storefront']);
add_shortcode('ToDownload_all_products', [self::class, 'all_products']);
}
private static function resolve_seller_user_id(array $atts = []): int {
// 1) Explicit seller_user_id.
$seller_user_id = isset($atts['seller_user_id']) ? (int) $atts['seller_user_id'] : 0;
if ($seller_user_id > 0) {
return $seller_user_id;
}
// 2) seller=ID or seller=username (shortcode attr).
$seller = isset($atts['seller']) ? trim((string) $atts['seller']) : '';
if ($seller !== '') {
if (ctype_digit($seller)) {
return (int) $seller;
}
$u = get_user_by('login', $seller);
if (!$u) {
$u = get_user_by('slug', $seller);
}
if ($u && !empty($u->ID)) {
return (int) $u->ID;
}
}
// 3) Query param ToDownload_seller=ID or username.
// phpcs:disable WordPress.Security.NonceVerification.Recommended -- Read-only routing parameter for storefront display.
$nonce_ok = self::verify_get_nonce('ToDownload_seller_nonce', 'todownload_storefront_seller');
if ($nonce_ok) {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Gated by verified nonce above.
$qs = isset($_GET['ToDownload_seller']) ? trim(sanitize_text_field((string) wp_unslash($_GET['ToDownload_seller']))) : '';
if ($qs !== '') {
if (ctype_digit($qs)) {
return (int) $qs;
}
$u = get_user_by('login', $qs);
if (!$u) {
$u = get_user_by('slug', $qs);
}
if ($u && !empty($u->ID)) {
return (int) $u->ID;
}
}
}
// 4) Tenant scoping: resolve seller from hostname (hosted platform / domain-mapped tenants).
$host_seller = self::resolve_seller_user_id_from_host();
if ($host_seller > 0) {
return $host_seller;
}
// 5) If there is exactly one seller user, default to them.
$sellers = get_users([
'role' => \ToDownload\Sellers\Sellers::ROLE,
'number' => 2,
'fields' => 'ID',
]);
if (is_array($sellers) && count($sellers) === 1) {
return (int) $sellers[0];
}
return 0;
}
private static function resolve_seller_user_id_from_host(): int {
$host = '';
if (function_exists('home_url')) {
$host = (string) (wp_parse_url(home_url('/'), PHP_URL_HOST) ?? '');
}
if ($host === '' && isset($_SERVER['HTTP_HOST'])) {
$host = sanitize_text_field((string) wp_unslash($_SERVER['HTTP_HOST']));
}
$host = strtolower(trim($host));
if ($host === '') {
return 0;
}
/**
* Allow hosted platforms to provide a custom host->seller mapping.
*
* @param int $seller_user_id Resolved seller user ID (0 means unknown).
* @param string $host Current request host.
*/
$mapped = (int) apply_filters('ToDownload_resolve_seller_user_id_from_host', 0, $host);
if ($mapped > 0) {
$u = get_user_by('id', $mapped);
if ($u && !empty($u->ID) && in_array(\ToDownload\Sellers\Sellers::ROLE, (array) ($u->roles ?? []), true)) {
return (int) $u->ID;
}
}
// Default heuristic (staging): {seller}.wp.2dl.app -> seller login/slug.
$parts = array_values(array_filter(explode('.', $host), function ($p): bool {
return trim((string) $p) !== '';
}));
if (count($parts) < 2) {
return 0;
}
$candidate = (string) ($parts[0] ?? '');
$candidate = sanitize_key($candidate);
if ($candidate === '' || $candidate === 'www' || $candidate === 'wp') {
return 0;
}
$u = get_user_by('login', $candidate);
if (!$u) {
$u = get_user_by('slug', $candidate);
}
if (!$u || empty($u->ID)) {
return 0;
}
if (!in_array(\ToDownload\Sellers\Sellers::ROLE, (array) ($u->roles ?? []), true)) {
return 0;
}
return (int) $u->ID;
}
private static function render_product_card(int $post_id, string $cta_html): string {
$post_id = (int) $post_id;
if ($post_id <= 0) {
return '';
}
$title = (string) get_the_title($post_id);
$url = (string) get_permalink($post_id);
$excerpt = (string) get_the_excerpt($post_id);
$price = (float) get_post_meta($post_id, '_ToDownload_price', true);
ob_start();
echo '
';
echo '
';
if (trim($excerpt) !== '') {
echo '
' . wp_kses_post($excerpt) . '
';
}
if ($price > 0) {
echo '
' . esc_html(Money::usd($price)) . '
';
}
echo '
' . wp_kses_post($cta_html) . '
';
echo '
';
return (string) ob_get_clean();
}
private static function render_download_cards(array $ids): string {
$ids = array_values(array_unique(array_filter(array_map('intval', $ids))));
if (empty($ids)) {
return '' . esc_html__('No products found.', '2download-connector') . '
';
}
$posts = get_posts([
'post_type' => \ToDownload\PostTypes\DownloadPostType::POST_TYPE,
'post_status' => 'publish',
'post__in' => $ids,
'orderby' => 'post__in',
'posts_per_page' => min(200, count($ids)),
]);
if (!is_array($posts) || empty($posts)) {
return '' . esc_html__('No products found.', '2download-connector') . '
';
}
ob_start();
echo '';
foreach ($posts as $p) {
$pid = (int) ($p->ID ?? 0);
if ($pid <= 0) {
continue;
}
echo wp_kses_post(self::render_product_card($pid, do_shortcode('[ToDownload_purchase_link id="' . $pid . '"]')));
}
echo '
';
return (string) ob_get_clean();
}
private static function listings_search_term(): string {
$nonce_ok = self::verify_get_nonce('ToDownload_listings_nonce', 'todownload_listings');
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only listing filter presence check; gated by verified nonce.
if (!$nonce_ok || !isset($_GET['ToDownload_q'])) {
return '';
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only listing filter; gated by verified nonce.
$q = sanitize_text_field((string) wp_unslash($_GET['ToDownload_q']));
$q = trim($q);
if ($q === '') {
return '';
}
// Keep search small/safe.
return mb_substr($q, 0, 80);
}
private static function listings_sort(): string {
$nonce_ok = self::verify_get_nonce('ToDownload_listings_nonce', 'todownload_listings');
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only listing filter presence check; gated by verified nonce.
if (!$nonce_ok || !isset($_GET['ToDownload_sort'])) {
return 'newest';
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only listing filter; gated by verified nonce.
$sort = sanitize_key((string) wp_unslash($_GET['ToDownload_sort']));
$allowed = ['newest', 'price_asc', 'price_desc'];
return in_array($sort, $allowed, true) ? $sort : 'newest';
}
private static function listings_page(): int {
$nonce_ok = self::verify_get_nonce('ToDownload_listings_nonce', 'todownload_listings');
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only pagination presence check; gated by verified nonce.
if (!$nonce_ok || !isset($_GET['ToDownload_page'])) {
return 1;
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only pagination; gated by verified nonce.
$page = absint(wp_unslash($_GET['ToDownload_page']));
return max(1, $page);
}
private static function verify_get_nonce(string $field, string $action): bool {
if (!function_exists('wp_verify_nonce')) {
return false;
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This reads the nonce itself for verification.
$nonce = isset($_GET[$field]) ? sanitize_text_field((string) wp_unslash($_GET[$field])) : '';
return ($nonce !== '' && wp_verify_nonce($nonce, $action));
}
private static function current_url(): string {
$request_uri = isset($_SERVER['REQUEST_URI']) ? sanitize_text_field((string) wp_unslash($_SERVER['REQUEST_URI'])) : '/';
$request_uri = $request_uri !== '' ? $request_uri : '/';
if ($request_uri[0] !== '/') {
$request_uri = '/' . ltrim($request_uri, '/');
}
return (string) home_url($request_uri);
}
private static function render_listings_controls(string $heading, string $q, string $sort, array $hidden = []): void {
echo '' . esc_html($heading) . ' ';
echo '';
}
public static function storefront($atts = []): string {
$atts = shortcode_atts([
'seller_user_id' => 0,
'seller' => '',
'posts_per_page' => 12,
], $atts);
$seller_user_id = self::resolve_seller_user_id($atts);
if ($seller_user_id <= 0) {
return '' . esc_html__('Storefront', '2download-connector') . ' '
. esc_html__('Seller not specified. Use [ToDownload_storefront seller_user_id="123"].', '2download-connector')
. '
';
}
$user = get_user_by('id', $seller_user_id);
if (!$user) {
return '' . esc_html__('Storefront', '2download-connector') . ' ' . esc_html__('Seller not found.', '2download-connector') . '
';
}
$ppp = max(1, min(48, (int) $atts['posts_per_page']));
$page = self::listings_page();
$search = self::listings_search_term();
$sort = self::listings_sort();
$order = 'DESC';
$orderby = 'date';
$meta_key = '';
if ($sort === 'price_asc') {
$orderby = 'meta_value_num';
$order = 'ASC';
$meta_key = '_ToDownload_price';
}
if ($sort === 'price_desc') {
$orderby = 'meta_value_num';
$order = 'DESC';
$meta_key = '_ToDownload_price';
}
$args = [
'post_type' => \ToDownload\PostTypes\DownloadPostType::POST_TYPE,
'post_status' => 'publish',
'author' => $seller_user_id,
'posts_per_page' => $ppp,
'paged' => $page,
'orderby' => $orderby,
'order' => $order,
];
if ($meta_key !== '') {
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Required for price sorting.
$args['meta_key'] = $meta_key;
}
if ($search !== '') {
$args['s'] = $search;
}
$q = new \WP_Query($args);
$business_name = trim((string) get_user_meta($seller_user_id, \ToDownload\Sellers\Sellers::USER_META_STOREFRONT_BUSINESS_NAME, true));
$logo_url = trim((string) get_user_meta($seller_user_id, \ToDownload\Sellers\Sellers::USER_META_STOREFRONT_LOGO_URL, true));
ob_start();
echo '';
self::render_listings_controls(
$business_name !== '' ? $business_name : ($user->display_name !== '' ? $user->display_name : __('Storefront', '2download-connector')),
$search,
$sort,
[
'ToDownload_seller' => (string) $seller_user_id,
'ToDownload_seller_nonce' => function_exists('wp_create_nonce') ? (string) wp_create_nonce('todownload_storefront_seller') : '',
]
);
$bio = trim((string) get_user_meta($seller_user_id, 'description', true));
$custom_avatar_url = trim((string) get_user_meta($seller_user_id, 'ToDownload_storefront_avatar_url', true));
$avatar = '';
if ($logo_url !== '') {
$avatar = '
';
} else if ($custom_avatar_url !== '') {
$avatar = '
';
} else {
$avatar = get_avatar($seller_user_id, 88, '', (string) ($user->display_name ?? ''), ['class' => 'ToDownload-storefront__avatar']);
}
echo '
';
if ($avatar !== '') {
echo '
' . wp_kses_post($avatar) . '
';
}
echo '
';
/* translators: %s = seller display name */
echo '
' . esc_html(sprintf(__('Welcome to %s.', '2download-connector'), (string) ($business_name !== '' ? $business_name : ($user->display_name !== '' ? $user->display_name : __('this seller', '2download-connector'))))) . '
';
echo '
';
echo '
';
echo '
' . esc_html__('About', '2download-connector') . ' ';
if ($bio !== '') {
echo '
';
} else {
echo '
' . esc_html__('Seller bio coming soon.', '2download-connector') . '
';
}
$settings = self::settings();
$products_page_id = (int) ($settings['products_page_id'] ?? 0);
if ($products_page_id <= 0) {
$products_page = get_page_by_path('2download-products');
$products_page_id = ($products_page instanceof \WP_Post) ? (int) $products_page->ID : 0;
}
$products_url = $products_page_id > 0 ? (string) get_permalink($products_page_id) : '';
if ($products_url !== '') {
echo '
' . esc_html__('All products', '2download-connector') . '
';
}
if (!$q->have_posts()) {
echo '
' . esc_html__('No products found yet.', '2download-connector') . '
';
echo '
';
return (string) ob_get_clean();
}
$ids = [];
while ($q->have_posts()) {
$q->the_post();
$ids[] = (int) get_the_ID();
}
wp_reset_postdata();
$featured = (int) get_user_meta($seller_user_id, \ToDownload\Sellers\Sellers::USER_META_STOREFRONT_FEATURED_DOWNLOAD_ID, true);
$featured_id = 0;
if ($featured > 0) {
$p = get_post($featured);
if (($p instanceof \WP_Post) && (string) $p->post_type === \ToDownload\PostTypes\DownloadPostType::POST_TYPE && (string) $p->post_status === 'publish' && (int) $p->post_author === (int) $seller_user_id) {
$featured_id = $featured;
}
}
if ($featured_id <= 0 && !empty($ids)) {
$featured_id = (int) $ids[0];
}
if ($featured_id > 0) {
echo '' . esc_html__('Featured', '2download-connector') . ' ';
echo '';
echo '
';
echo wp_kses_post(self::render_product_card($featured_id, ''));
echo '
';
echo '
';
$ids = array_values(array_diff($ids, [$featured_id]));
}
echo '' . esc_html__('Products', '2download-connector') . ' ';
echo wp_kses_post(self::render_download_cards($ids));
// Pagination (simple).
$max_pages = (int) ($q->max_num_pages ?? 1);
if ($max_pages > 1) {
echo '';
/* translators: 1: current page number, 2: total pages */
echo '
' . esc_html(sprintf(__('Page %1$d of %2$d', '2download-connector'), $page, $max_pages)) . ' ';
if ($page > 1) {
$prev = add_query_arg(
[
'ToDownload_page' => $page - 1,
'ToDownload_listings_nonce' => wp_create_nonce('todownload_listings'),
],
self::current_url()
);
echo '
' . esc_html__('Previous', '2download-connector') . ' ';
}
if ($page < $max_pages) {
$next = add_query_arg(
[
'ToDownload_page' => $page + 1,
'ToDownload_listings_nonce' => wp_create_nonce('todownload_listings'),
],
self::current_url()
);
echo '
' . esc_html__('Next', '2download-connector') . ' ';
}
echo '
';
}
echo '';
return (string) ob_get_clean();
}
public static function all_products($atts = []): string {
$atts = shortcode_atts([
'posts_per_page' => 12,
], $atts);
// Tenant scoping:
// - If this is a single-tenant site (one seller per WP site), the tenant boundary is the site.
// Still scope by seller author, using hosted_store_public_id to resolve the seller (defense-in-depth).
// - If this is a multi-seller site, require a resolved seller and filter by author to prevent leakage.
$settings = self::settings();
$tenant_store_public_id = trim((string) ($settings['hosted_store_public_id'] ?? ''));
$single_tenant_site = $tenant_store_public_id !== '';
$seller_user_id = 0;
if ($single_tenant_site) {
$seller_user_id = self::resolve_seller_user_id_from_store_public_id($tenant_store_public_id);
if ($seller_user_id <= 0) {
return '' . esc_html__('All products', '2download-connector') . ' '
. esc_html__('This store could not be resolved for the current configuration.', '2download-connector')
. '
';
}
} else {
$seller_user_id = self::resolve_seller_user_id($atts);
if ($seller_user_id <= 0) {
return '' . esc_html__('All products', '2download-connector') . ' '
. esc_html__('This store could not be resolved for the current domain.', '2download-connector')
. '
';
}
}
$ppp = max(1, min(48, (int) $atts['posts_per_page']));
$page = self::listings_page();
$search = self::listings_search_term();
$sort = self::listings_sort();
$order = 'DESC';
$orderby = 'date';
$meta_key = '';
if ($sort === 'price_asc') {
$orderby = 'meta_value_num';
$order = 'ASC';
$meta_key = '_ToDownload_price';
}
if ($sort === 'price_desc') {
$orderby = 'meta_value_num';
$order = 'DESC';
$meta_key = '_ToDownload_price';
}
$args = [
'post_type' => \ToDownload\PostTypes\DownloadPostType::POST_TYPE,
'post_status' => 'publish',
'posts_per_page' => $ppp,
'paged' => $page,
'orderby' => $orderby,
'order' => $order,
];
if ($seller_user_id > 0) {
$args['author'] = $seller_user_id;
}
if ($meta_key !== '') {
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Required for price sorting.
$args['meta_key'] = $meta_key;
}
if ($search !== '') {
$args['s'] = $search;
}
$q = new \WP_Query($args);
ob_start();
echo '';
self::render_listings_controls(__('All products', '2download-connector'), $search, $sort);
if (!$q->have_posts()) {
echo '
' . esc_html__('No products found yet.', '2download-connector') . '
';
echo '
';
return (string) ob_get_clean();
}
$ids = [];
while ($q->have_posts()) {
$q->the_post();
$pid = (int) get_the_ID();
if ($pid > 0) {
$ids[] = $pid;
}
}
wp_reset_postdata();
// Defense-in-depth: ensure IDs match the resolved seller author.
if ($seller_user_id > 0) {
$ids = array_values(array_filter($ids, function (int $post_id) use ($seller_user_id): bool {
$p = get_post($post_id);
if (!$p) {
return false;
}
return (int) ($p->post_author ?? 0) === (int) $seller_user_id;
}));
}
echo wp_kses_post(self::render_download_cards($ids));
$max_pages = (int) ($q->max_num_pages ?? 1);
if ($max_pages > 1) {
echo '';
/* translators: 1: current page number, 2: total pages */
echo '
' . esc_html(sprintf(__('Page %1$d of %2$d', '2download-connector'), $page, $max_pages)) . ' ';
if ($page > 1) {
$prev = add_query_arg(
[
'ToDownload_page' => $page - 1,
'ToDownload_listings_nonce' => wp_create_nonce('todownload_listings'),
],
self::current_url()
);
echo '
' . esc_html__('Previous', '2download-connector') . ' ';
}
if ($page < $max_pages) {
$next = add_query_arg(
[
'ToDownload_page' => $page + 1,
'ToDownload_listings_nonce' => wp_create_nonce('todownload_listings'),
],
self::current_url()
);
echo '
' . esc_html__('Next', '2download-connector') . ' ';
}
echo '
';
}
echo '';
return (string) ob_get_clean();
}
private static function settings(): array {
$s = get_option('ToDownload_settings', []);
return is_array($s) ? $s : [];
}
private static function checkout_url(): string {
$settings = self::settings();
$checkout_page_id = (int) ($settings['checkout_page_id'] ?? 0);
$checkout_url = (string) ($settings['checkout_url'] ?? '');
$target = $checkout_page_id ? (string) get_permalink($checkout_page_id) : '';
if ($target === '' && $checkout_url !== '') {
$target = $checkout_url;
}
return $target !== '' ? $target : home_url('/');
}
private static function cart_url(): string {
$settings = self::settings();
$cart_page_id = (int) ($settings['cart_page_id'] ?? 0);
$target = $cart_page_id ? (string) get_permalink($cart_page_id) : '';
// Fall back to checkout since it already shows cart items.
return $target !== '' ? $target : self::checkout_url();
}
private static function account_url(): string {
$settings = self::settings();
$account_page_id = (int) ($settings['account_page_id'] ?? 0);
if ($account_page_id <= 0) {
$account_page_id = (int) get_option('ToDownload_account_page_id');
}
$target = $account_page_id > 0 ? (string) get_permalink($account_page_id) : '';
return $target !== '' ? $target : home_url('/');
}
private static function secure_email_query_args(string $email): array {
$email = sanitize_email($email);
if ($email === '' || !is_email($email)) {
return [];
}
$token = Token::email_lookup_token($email);
if ($token === '') {
return [];
}
return [
'ToDownload_email' => $email,
self::EMAIL_TOKEN_PARAM => $token,
];
}
private static function account_url_for_email(string $email, string $tab = ''): string {
$args = self::secure_email_query_args($email);
if ($tab !== '') {
$args['ToDownload_tab'] = sanitize_key($tab);
}
return !empty($args) ? (string) add_query_arg($args, self::account_url()) : self::account_url();
}
private static function checkout_email_prefill(): string {
if (is_user_logged_in()) {
$user = wp_get_current_user();
$email = $user ? sanitize_email((string) $user->user_email) : '';
return ($email !== '' && is_email($email)) ? $email : '';
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Checkout prefill is not an authorization decision.
$email = isset($_GET['ToDownload_email']) ? sanitize_email((string) wp_unslash($_GET['ToDownload_email'])) : '';
return ($email !== '' && is_email($email)) ? $email : '';
}
private static function cart_single_seller_user_id(array $cart_items): int {
$seller_ids = [];
foreach ($cart_items as $it) {
$download_id = (int) ($it['download_id'] ?? 0);
if ($download_id <= 0) {
continue;
}
$p = get_post($download_id);
if (!$p) {
continue;
}
$seller_ids[] = (int) ($p->post_author ?? 0);
}
$seller_ids = array_values(array_unique(array_filter(array_map('intval', $seller_ids))));
return count($seller_ids) === 1 ? (int) $seller_ids[0] : 0;
}
public static function cart_link($atts = []): string {
$atts = shortcode_atts([
'text' => __('Cart', '2download-connector'),
'show_count' => 1,
], $atts);
$cart = Cart::instance();
$count = count($cart->all());
$label = (string) $atts['text'];
$show_count = (int) ($atts['show_count'] ?? 1) === 1;
$url = esc_url(self::cart_url());
$out = '' . esc_html($label);
if ($show_count) {
/* translators: %d = number of items in the cart */
$out .= ' ' . esc_html((string) (int) $count) . ' ';
}
$out .= ' ';
return $out;
}
public static function cart(): string {
$cart = Cart::instance();
$items = $cart->all();
$checkout_url = esc_url(self::checkout_url());
$continue_url = '';
ob_start();
echo '';
echo '
' . esc_html__('Cart', '2download-connector') . ' ';
if (empty($items)) {
$continue_url = get_post_type_archive_link('todownload_download');
$continue_url = $continue_url ? esc_url($continue_url) : esc_url(home_url('/'));
echo '
' . esc_html__('Your cart is empty.', '2download-connector') . '
';
echo '
' . esc_html__('Continue shopping', '2download-connector') . '
';
echo '
';
return (string) ob_get_clean();
}
// Continue shopping URL: first item with a configured URL wins.
foreach ($items as $it) {
$download_id = (int) ($it['download_id'] ?? 0);
if ($download_id <= 0) {
continue;
}
$maybe = (string) get_post_meta($download_id, '_ToDownload_continue_shopping_url', true);
if ($maybe !== '') {
$continue_url = esc_url($maybe);
break;
}
}
if ($continue_url === '') {
$archive = get_post_type_archive_link('todownload_download');
$continue_url = $archive ? esc_url($archive) : esc_url(home_url('/'));
}
echo '';
echo '';
echo '' . esc_html__('Item', '2download-connector') . ' ';
echo '' . esc_html__('Price', '2download-connector') . ' ';
echo '' . esc_html__('Action', '2download-connector') . ' ';
echo ' ';
$cart_ids = [];
$recommended_ids = [];
foreach ($items as $it) {
$download_id = (int) ($it['download_id'] ?? 0);
if ($download_id <= 0) {
continue;
}
$cart_ids[] = $download_id;
$upsells = get_post_meta($download_id, '_ToDownload_upsell_download_ids', true);
if (is_array($upsells)) {
foreach ($upsells as $u) {
$u = (int) $u;
if ($u > 0) {
$recommended_ids[] = $u;
}
}
}
$title = get_the_title($download_id);
$permalink = get_permalink($download_id);
$price = (float) ($it['price'] ?? 0);
$remove = add_query_arg(['ToDownload_remove_from_cart' => $download_id], self::current_url());
$remove = wp_nonce_url($remove, 'ToDownload_remove_from_cart_' . $download_id, 'ToDownload_nonce');
echo '';
echo '';
if ($permalink) {
echo '' . esc_html($title !== '' ? $title : ('#' . $download_id)) . ' ';
} else {
echo esc_html($title !== '' ? $title : ('#' . $download_id));
}
echo ' ';
echo '' . esc_html(Money::usd($price)) . ' ';
echo '' . esc_html__('Remove', '2download-connector') . ' ';
echo ' ';
}
echo '
';
echo '' . esc_html__('Subtotal:', '2download-connector') . ' ' . esc_html(Money::usd((float) $cart->subtotal())) . '
';
$tax = (float) $cart->tax();
if ($tax > 0) {
echo '' . esc_html__('Tax:', '2download-connector') . ' ' . esc_html(Money::usd($tax)) . '
';
}
echo '' . esc_html__('Total:', '2download-connector') . ' ' . esc_html(Money::usd((float) $cart->total())) . '
';
echo '';
echo '' . esc_html__('Continue shopping', '2download-connector') . ' ';
echo '' . esc_html__('Checkout', '2download-connector') . ' ';
echo '
';
// Upsells (recommendations in cart based on cart contents).
$cart_ids = array_values(array_unique(array_filter(array_map('intval', $cart_ids))));
$recommended_ids = array_values(array_unique(array_filter(array_map('intval', $recommended_ids))));
if (!empty($cart_ids) && !empty($recommended_ids)) {
$recommended_ids = array_values(array_diff($recommended_ids, $cart_ids));
}
if (!empty($recommended_ids)) {
$upsell_posts = get_posts([
'post_type' => 'todownload_download',
'post__in' => $recommended_ids,
'orderby' => 'post__in',
'post_status' => 'publish',
'posts_per_page' => 6,
]);
if (is_array($upsell_posts) && !empty($upsell_posts)) {
echo '';
echo '
' . esc_html__('You may also like', '2download-connector') . ' ';
echo '
';
foreach ($upsell_posts as $p) {
$pid = (int) $p->ID;
$current = self::current_url();
$add_url = add_query_arg([
'ToDownload_add_to_cart' => $pid,
], $current);
$add_url = wp_nonce_url($add_url, 'ToDownload_add_to_cart_' . $pid, 'ToDownload_nonce');
$buy_url = add_query_arg([
'ToDownload_add_to_cart' => $pid,
'ToDownload_buy_now' => 1,
'ToDownload_clear_cart' => 1,
], $current);
$buy_url = wp_nonce_url($buy_url, 'ToDownload_add_to_cart_' . $pid, 'ToDownload_nonce');
echo wp_kses_post(self::render_product_card(
$pid,
'
' . esc_html__('Add to cart', '2download-connector') . ' '
. '
' . esc_html__('Instant checkout', '2download-connector') . ' '
));
}
echo '
';
echo '
';
}
}
echo '';
return (string) ob_get_clean();
}
public static function receipt(): string {
$order_public_id = isset($_GET['ToDownload_order']) ? sanitize_text_field((string) wp_unslash($_GET['ToDownload_order'])) : '';
$legacy_order_id = isset($_GET['ToDownload_order_id']) ? absint(wp_unslash($_GET['ToDownload_order_id'])) : 0;
$token = isset($_GET['ToDownload_receipt_token']) ? sanitize_text_field((string) wp_unslash($_GET['ToDownload_receipt_token'])) : '';
$session_id = isset($_GET['session_id']) ? sanitize_text_field((string) wp_unslash($_GET['session_id'])) : '';
$email = self::effective_email();
$order_public_id = trim((string) $order_public_id);
$order_id = 0;
if ($order_public_id !== '') {
$order_id = Orders::find_order_id_by_public_id($order_public_id);
} else if ($legacy_order_id > 0) {
// Back-compat for old links (should not be generated anymore).
$order_id = $legacy_order_id;
$order_public_id = Orders::order_public_id($order_id);
}
if ($order_id <= 0 || $order_public_id === '') {
return '' . esc_html__('Receipt', '2download-connector') . ' ' . esc_html__('Missing order.', '2download-connector') . '
';
}
$order = Orders::get_order($order_id);
if (!$order) {
return '' . esc_html__('Receipt', '2download-connector') . ' ' . esc_html__('Order not found.', '2download-connector') . '
';
}
$customer = Orders::get_customer((int) ($order['customer_id'] ?? 0));
$order_email = $customer ? sanitize_email((string) ($customer['email'] ?? '')) : '';
// Access check:
// - Logged-in + linked customer user_id
// - Email lookup match
// - Secure receipt token (for post-checkout guests)
if (!self::viewer_can_access_order($order_id, $email)) {
if ($order_email === '' || !Token::verify_receipt_token($order_public_id, $order_email, $token)) {
return '' . esc_html__('Receipt', '2download-connector') . ' ' . esc_html__('Not allowed.', '2download-connector') . '
';
}
}
$settings = get_option('ToDownload_settings', []);
if (!is_array($settings)) {
$settings = [];
}
$items = Orders::get_order_items($order_id);
$show_license = self::order_is_seller_membership_purchase($items, $settings);
$seller_user_id = (int) ($order['seller_user_id'] ?? 0);
$hosted = Sellers::hosted_credentials($seller_user_id);
$hosted_base = trim((string) ($hosted['hosted_base'] ?? 'https://2dl.app'));
$hosted_store = trim((string) ($hosted['store_public_id'] ?? ''));
$hosted_key = trim((string) ($hosted['api_key'] ?? ''));
$hosted_mode = !empty($hosted['connected']) && $hosted_store !== '' && $hosted_key !== '';
// Best-effort sync on receipt view:
// The hosted checkout provider appends `session_id=cs_...` to the success_url. Since webhooks are handled by
// 2dl.app (not this WordPress site), we sync order status here so downloads appear (or are revoked) promptly.
$status_now = (string) ($order['status'] ?? '');
if ($status_now !== 'complete' && $session_id !== '' && strpos($session_id, 'cs_') === 0) {
$gateway = GatewayRegistry::instance()->get('hosted_checkout');
if ($gateway instanceof HostedCheckoutGateway) {
try {
$gateway->sync_order_from_checkout_session($session_id, $order_id);
$order = Orders::get_order($order_id) ?: $order;
} catch (\Throwable $e) {
// Best-effort only.
}
}
}
// Hosted platform receipt: resolve payment status + license from 2dl.app.
$hosted_status = '';
$hosted_license = '';
$hosted_entitlement_status = '';
$hosted_error_msg = '';
$hosted_error_code = '';
$hosted_error_status = 0;
$request_id = isset($_GET['ToDownload_request_id']) ? sanitize_text_field((string) wp_unslash($_GET['ToDownload_request_id'])) : '';
$request_id = trim((string) $request_id);
if ($request_id === '') {
$request_id = function_exists('wp_generate_uuid4') ? (string) wp_generate_uuid4() : (string) time();
}
if ($hosted_mode && $session_id !== '' && strpos($session_id, 'cs_') === 0) {
$ent = HostedClient::post_json(
$hosted_base,
'/api/v1/entitlements/by-session',
[
'store_public_id' => $hosted_store,
'session_id' => $session_id,
],
$hosted_key,
$request_id
);
if (!empty($ent['ok']) && !empty($ent['data']) && is_array($ent['data'])) {
$data = (array) $ent['data'];
$hosted_status = isset($data['status']) ? (string) $data['status'] : '';
$ent_row = isset($data['entitlement']) && is_array($data['entitlement']) ? (array) $data['entitlement'] : [];
$hosted_entitlement_status = isset($ent_row['status']) ? (string) $ent_row['status'] : '';
$hosted_license = isset($ent_row['license_key']) ? (string) $ent_row['license_key'] : '';
} else {
$err = isset($ent['error']) && is_array($ent['error']) ? (array) $ent['error'] : [];
$hosted_error_msg = isset($err['message']) ? (string) $err['message'] : '';
$hosted_error_code = isset($err['code']) ? (string) $err['code'] : '';
$hosted_error_status = isset($ent['status']) ? (int) $ent['status'] : 0;
if ($hosted_error_msg === '' && !empty($ent['message'])) {
$hosted_error_msg = (string) $ent['message'];
}
}
}
if ($hosted_mode && ($session_id === '' || strpos($session_id, 'cs_') !== 0)) {
$hosted_error_code = 'MISSING_SESSION_ID';
$hosted_error_msg = __('Missing checkout session id. Please use the receipt link from your checkout confirmation.', '2download-connector');
$hosted_error_status = 400;
}
// Optional resend receipt action.
$resend_receipt = isset($_GET['ToDownload_resend_receipt']) ? sanitize_key((string) wp_unslash($_GET['ToDownload_resend_receipt'])) : '';
if ($resend_receipt === '1') {
$nonce = isset($_GET['ToDownload_nonce']) ? sanitize_text_field((string) wp_unslash($_GET['ToDownload_nonce'])) : '';
$nonce_ok = false;
if ($nonce !== '') {
if ($order_public_id !== '') {
$nonce_ok = wp_verify_nonce($nonce, 'ToDownload_resend_receipt_' . $order_public_id);
} else if ($order_id > 0) {
$nonce_ok = wp_verify_nonce($nonce, 'ToDownload_resend_receipt_' . $order_id);
}
}
if ($nonce_ok) {
$ok = EmailDelivery::instance()->resend_order_email($order_id);
$notice = $ok ? __('Receipt sent.', '2download-connector') : __('Could not send receipt. (Order must be complete and have a valid email.)', '2download-connector');
$cls = $ok ? 'ToDownload-reviews__notice' : 'ToDownload-reviews__error';
$msg_html = '' . esc_html($notice) . '
';
}
}
$status = (string) ($order['status'] ?? '');
$total = (float) ($order['total'] ?? 0);
$currency = (string) ($order['currency'] ?? 'USD');
$created_at = (string) ($order['created_at'] ?? '');
$refresh_url = esc_url(self::current_url());
$account_url = self::account_url();
if (!is_user_logged_in() && $order_email !== '' && is_email($order_email)) {
$account_url = self::account_url_for_email($order_email);
}
ob_start();
echo '';
echo '
' . esc_html__('Thank you', '2download-connector') . ' ';
$ref = substr($order_public_id, -8);
echo '
' . esc_html__('Order reference', '2download-connector') . ' ' . esc_html($ref) . '
';
if (!empty($msg_html)) {
echo wp_kses_post($msg_html);
}
if ($hosted_mode) {
if ($hosted_status === 'paid' && $hosted_entitlement_status === 'active') {
if ($show_license && $hosted_license !== '') {
echo '
'
. esc_html__('Payment confirmed by 2dl.app. Your license is ready below.', '2download-connector')
. '
';
} else {
echo '
'
. esc_html__('Payment confirmed by 2dl.app. Your download is ready below.', '2download-connector')
. '
';
}
} else if ($hosted_status === 'refunded') {
echo '
'
. esc_html__('This purchase was refunded. Downloads and licenses are no longer available.', '2download-connector')
. '
';
} else if ($hosted_status === 'disputed') {
echo '
'
. esc_html__('This payment is under dispute. Downloads and licenses are temporarily unavailable.', '2download-connector')
. '
';
} else if ($hosted_status === 'failed') {
echo '
'
. esc_html__('Payment was not completed (failed or expired). You were not charged. Please try again.', '2download-connector')
. '
';
} else if ($hosted_error_code === 'SESSION_NOT_FOUND') {
echo '
'
. esc_html__('We’re still confirming your payment with 2dl.app. If this doesn’t update after a moment, refresh this page.', '2download-connector')
. '
';
} else {
echo '
'
. esc_html__('We’re confirming your payment with 2dl.app. If this doesn’t update after a moment, refresh this page.', '2download-connector')
. '
';
}
if ($hosted_error_msg !== '') {
// Public-safe messaging, with extra detail for admins.
$is_admin_viewer = function_exists('current_user_can') && current_user_can('manage_options');
if ($hosted_error_status === 401 || $hosted_error_status === 403 || $hosted_error_code === 'API_KEY_INVALID') {
$msg = $is_admin_viewer
? __('Hosted connection is not authorized. Check Store API key in 2DL settings.', '2download-connector')
: __('We couldn’t verify the store connection to 2dl.app. Please contact the site owner.', '2download-connector');
echo '
';
} else if (
$hosted_error_status >= 500
|| $hosted_error_status === 0
|| $hosted_error_code === 'HOSTED_TIMEOUT'
|| $hosted_error_code === 'HOSTED_BAD_RESPONSE'
|| $hosted_error_code === 'HOSTED_REQUEST_FAILED'
) {
echo '
'
. esc_html__('2dl.app is temporarily unavailable. Please refresh in a moment.', '2download-connector')
. '
';
} else if ($hosted_error_code === 'MISSING_SESSION_ID') {
echo '
' . esc_html($hosted_error_msg) . '
';
} else {
echo '
'
. esc_html__('Could not fetch hosted entitlement. Please refresh this page.', '2download-connector')
. '
';
}
if ($is_admin_viewer) {
echo '
'
. esc_html__('Diagnostics:', '2download-connector')
. ' '
. esc_html__('request_id=', '2download-connector') . esc_html($request_id)
. esc_html__(', status=', '2download-connector') . esc_html((string) $hosted_error_status)
. esc_html__(', code=', '2download-connector') . esc_html($hosted_error_code)
. ($hosted_error_msg !== '' ? (esc_html__(', message=', '2download-connector') . esc_html($hosted_error_msg)) : '')
. '
';
}
}
} else if ($status !== 'complete') {
echo '
' . esc_html__('We’re confirming your payment. This can take a few seconds. If your downloads don’t appear after a moment, refresh this page.', '2download-connector') . '
';
}
if ($status === 'complete' && class_exists('ZipArchive')) {
$all_link = DownloadDelivery::generate_download_all_link($order_id);
echo '
'
. '' . esc_html__('Download all (.zip)', '2download-connector') . ' '
. ' ' . esc_html__('Get everything from this order in one file.', '2download-connector') . ' '
. '
';
}
echo '
';
echo '';
if ($hosted_mode && $hosted_status !== '') {
echo '' . esc_html__('Status', '2download-connector') . ' ' . esc_html($hosted_status) . ' ';
} else {
echo '' . esc_html__('Status', '2download-connector') . ' ' . esc_html($status) . ' ';
}
if ($created_at !== '') {
echo '' . esc_html__('Date', '2download-connector') . ' ' . esc_html($created_at) . ' ';
}
$total_label = strtoupper($currency) === 'USD' ? Money::usd($total) : ($currency . ' ' . number_format($total, 2));
echo '' . esc_html__('Total', '2download-connector') . ' ' . esc_html($total_label) . ' ';
if ($hosted_mode && $hosted_entitlement_status !== '') {
echo '' . esc_html__('Entitlement', '2download-connector') . ' ' . esc_html($hosted_entitlement_status) . ' ';
}
if ($hosted_mode && $show_license && $hosted_license !== '') {
echo '' . esc_html__('License', '2download-connector') . ' ' . esc_html($hosted_license) . ' ';
}
echo ' ';
echo '
';
$purchased_ids = [];
$recommended_ids = [];
$continue_shopping_url = '';
if (!empty($items)) {
echo '
' . esc_html__('Items', '2download-connector') . ' ';
echo '
';
echo '' . esc_html__('Item', '2download-connector') . ' ' . esc_html__('Download', '2download-connector') . ' ';
if ($show_license) {
echo '' . esc_html__('License', '2download-connector') . ' ';
}
echo '';
if ($show_license && !$hosted_mode && $status === 'complete') {
Licenses::ensure_for_order($order_id);
}
foreach ($items as $it) {
$download_id = (int) ($it['download_id'] ?? 0);
if ($download_id <= 0) {
continue;
}
$purchased_ids[] = $download_id;
if ($continue_shopping_url === '') {
$maybe = (string) get_post_meta($download_id, '_ToDownload_continue_shopping_url', true);
if ($maybe !== '') {
$continue_shopping_url = $maybe;
}
}
$upsells = get_post_meta($download_id, '_ToDownload_upsell_download_ids', true);
if (is_array($upsells)) {
foreach ($upsells as $u) {
$u = (int) $u;
if ($u > 0) {
$recommended_ids[] = $u;
}
}
}
$title = get_the_title($download_id);
$permalink = get_permalink($download_id);
$dl = $status === 'complete' ? DownloadDelivery::generate_link($order_id, $download_id) : '';
$license = '';
if ($show_license && $status === 'complete') {
if ($hosted_mode) {
$license = $hosted_license;
} else {
$license = Licenses::get_for_item($order_id, $download_id);
}
}
echo '';
echo '';
if ($permalink) {
echo '' . esc_html($title !== '' ? $title : ('#' . $download_id)) . ' ';
} else {
echo esc_html($title !== '' ? $title : ('#' . $download_id));
}
echo ' ';
echo '';
if ($dl !== '') {
echo '' . esc_html__('Download', '2download-connector') . ' ';
} else {
echo esc_html__('Preparing…', '2download-connector');
}
echo ' ';
if ($show_license) {
echo '';
if ($license !== '') {
echo '' . esc_html($license) . '';
} else {
echo esc_html('—');
}
echo ' ';
}
echo ' ';
}
echo '
';
}
$purchased_ids = array_values(array_unique(array_filter(array_map('intval', $purchased_ids))));
$recommended_ids = array_values(array_unique(array_filter(array_map('intval', $recommended_ids))));
if (!empty($purchased_ids) && !empty($recommended_ids)) {
$recommended_ids = array_values(array_diff($recommended_ids, $purchased_ids));
}
if ($continue_shopping_url === '') {
$archive = get_post_type_archive_link('todownload_download');
$continue_shopping_url = $archive ? $archive : home_url('/');
}
if (!empty($recommended_ids)) {
$upsell_posts = get_posts([
'post_type' => 'todownload_download',
'post__in' => $recommended_ids,
'orderby' => 'post__in',
'post_status' => 'publish',
'posts_per_page' => 6,
]);
if (is_array($upsell_posts) && !empty($upsell_posts)) {
echo '
';
echo '
' . esc_html__('Recommended for you', '2download-connector') . ' ';
echo '
';
foreach ($upsell_posts as $p) {
$pid = (int) $p->ID;
$current = self::current_url();
$add_url = add_query_arg([
'ToDownload_add_to_cart' => $pid,
], $current);
$add_url = wp_nonce_url($add_url, 'ToDownload_add_to_cart_' . $pid, 'ToDownload_nonce');
$buy_args = [
'ToDownload_add_to_cart' => $pid,
'ToDownload_buy_now' => 1,
'ToDownload_clear_cart' => 1,
];
if ($order_email !== '' && is_email($order_email)) {
$buy_args['ToDownload_email'] = $order_email;
}
$buy_url = add_query_arg($buy_args, $current);
$buy_url = wp_nonce_url($buy_url, 'ToDownload_add_to_cart_' . $pid, 'ToDownload_nonce');
echo wp_kses_post(self::render_product_card(
$pid,
'
' . esc_html__('Add to cart', '2download-connector') . ' '
. '
' . esc_html__('Instant checkout', '2download-connector') . ' '
));
}
echo '
';
echo '
';
}
}
// Actions
echo '
';
if ($status !== 'complete') {
echo '' . esc_html__('Refresh', '2download-connector') . ' ';
}
$resend = add_query_arg([
'ToDownload_order' => $order_public_id,
'ToDownload_resend_receipt' => 1,
], self::current_url());
$resend = wp_nonce_url($resend, 'ToDownload_resend_receipt_' . $order_public_id, 'ToDownload_nonce');
echo '' . esc_html__('Resend receipt', '2download-connector') . ' ';
echo '' . esc_html__('Continue shopping', '2download-connector') . ' ';
echo '' . esc_html__('My account', '2download-connector') . ' ';
echo '
';
echo '
';
return (string) ob_get_clean();
}
private static function effective_email(): string {
if (is_user_logged_in()) {
$user = wp_get_current_user();
$email = $user ? sanitize_email((string) $user->user_email) : '';
return ($email !== '' && is_email($email)) ? $email : '';
}
// Guest account lookups require a per-email signed token. A WordPress nonce is not enough because all
// anonymous visitors share the same nonce value for an action.
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Authorization is the signed email token below.
$email = isset($_GET['ToDownload_email']) ? sanitize_email((string) wp_unslash($_GET['ToDownload_email'])) : '';
if ($email === '' || !is_email($email)) {
return '';
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Authorization token is validated immediately.
$token = isset($_GET[self::EMAIL_TOKEN_PARAM]) ? sanitize_text_field((string) wp_unslash($_GET[self::EMAIL_TOKEN_PARAM])) : '';
return Token::verify_email_lookup_token($email, $token) ? $email : '';
}
public static function account(): string {
ob_start();
echo '';
echo '
' . esc_html__('My Account', '2download-connector') . ' ';
$current_url = self::current_url();
if (is_user_logged_in()) {
echo '
';
if (current_user_can('edit_todownload_downloads')) {
echo '' . esc_html__('Seller dashboard', '2download-connector') . ' ';
}
echo '' . esc_html__('Log out', '2download-connector') . ' ';
echo '
';
}
// phpcs:disable WordPress.Security.NonceVerification.Recommended -- Read-only view selection.
$tab = isset($_GET['ToDownload_tab']) ? sanitize_key((string) wp_unslash($_GET['ToDownload_tab'])) : 'orders';
// phpcs:enable WordPress.Security.NonceVerification.Recommended
$allowed_tabs = [
'orders' => __('Orders', '2download-connector'),
'downloads' => __('Downloads', '2download-connector'),
'subscriptions' => __('Subscriptions', '2download-connector'),
];
if (!isset($allowed_tabs[$tab])) {
$tab = 'orders';
}
$guest_email = is_user_logged_in() ? '' : self::effective_email();
if (!is_user_logged_in()) {
$login_url = wp_login_url($current_url);
echo '
' . esc_html__('Log in', '2download-connector') . ' ' . esc_html__('to view purchases without entering your email.', '2download-connector') . '
';
if ($guest_email === '') {
echo '
' . esc_html__('For your security, use the secure account link from your receipt email to view guest purchases.', '2download-connector') . '
';
}
}
// Tabs (simple, no JS) - Shopify-ish layout while still WordPress-y.
echo '
';
foreach ($allowed_tabs as $key => $label) {
$url = add_query_arg(['ToDownload_tab' => $key], self::current_url());
$cls = $key === $tab ? 'ToDownload-tabs__link is-active' : 'ToDownload-tabs__link';
echo '' . esc_html($label) . ' ';
}
echo ' ';
if ($tab === 'downloads') {
echo wp_kses_post(self::downloads_library_html());
} else if ($tab === 'subscriptions') {
$email = self::effective_email();
if ($email) {
echo wp_kses_post(self::subscriptions_html($email));
} else {
echo '
' . esc_html__('Log in or use the secure account link from your receipt email to view subscriptions.', '2download-connector') . '
';
}
} else {
// Default Orders tab uses purchase history UI.
echo wp_kses_post(self::purchase_history_html(false));
}
echo '
';
return (string) ob_get_clean();
}
public static function downloads_library(): string {
return self::downloads_library_html();
}
private static function downloads_library_html(): string {
$email = self::effective_email();
$orders = self::get_viewer_orders($email);
if (empty($orders)) {
$message = $email === ''
? __('Log in or use the secure account link from your receipt email to view downloads.', '2download-connector')
: __('No downloads found yet.', '2download-connector');
return '' . esc_html__('Downloads', '2download-connector') . ' '
. esc_html($message)
. '
' . esc_html__("Why can't I download?", '2download-connector') . ' '
. esc_html__('If your order is still processing, use “View receipt” then refresh.', '2download-connector')
. ' '
. esc_html__('If your access expired, click “Renew”.', '2download-connector')
. ' '
. esc_html__('If a link still fails, re-send your receipt from “View receipt”.', '2download-connector')
. '
';
}
// Build a map of download_id => latest completed order containing it.
$latest = [];
// Track latest active All Access pass per seller_user_id.
$passes = [];
$settings = get_option('ToDownload_settings', []);
$default_access_days = is_array($settings) ? (int) ($settings['default_access_days'] ?? 0) : 0;
$default_access_days = max(0, $default_access_days);
$receipt_base = is_array($settings) ? (string) ($settings['success_url'] ?? '') : '';
if ($receipt_base === '') {
$receipt_base = home_url('/');
}
foreach ($orders as $o) {
if ((string) ($o['status'] ?? '') !== 'complete') {
continue;
}
$order_id = (int) ($o['id'] ?? 0);
if ($order_id <= 0) {
continue;
}
$created_at = (string) ($o['created_at'] ?? '');
$completed_at = (string) ($o['updated_at'] ?? $o['created_at'] ?? '');
$items = Orders::get_order_items($order_id);
foreach ($items as $it) {
$download_id = (int) ($it['download_id'] ?? 0);
if ($download_id <= 0) {
continue;
}
// All Access pass detection.
$is_pass = (string) get_post_meta($download_id, '_ToDownload_is_all_access_pass', true) === '1';
if ($is_pass) {
$seller_user_id = (int) get_post_field('post_author', $download_id);
if ($seller_user_id > 0) {
// Determine if pass is still active based on its own access days.
$days = (int) get_post_meta($download_id, '_ToDownload_access_days', true);
$days = max(0, $days);
$active = true;
$expires_ts = 0;
if ($days > 0 && $created_at !== '') {
$completed_ts = strtotime(($completed_at !== '' ? $completed_at : $created_at) . ' UTC');
if ($completed_ts) {
$expires_ts = (int) ($completed_ts + ($days * DAY_IN_SECONDS));
$expires_ts = (int) apply_filters('ToDownload_download_expires_ts', $expires_ts, $order_id, $download_id, $o);
if ($expires_ts > 0 && time() > $expires_ts) {
$active = false;
}
}
}
if ($active) {
$prev = isset($passes[$seller_user_id]) ? (string) ($passes[$seller_user_id]['created_at'] ?? '') : '';
if ($prev === '' || ($created_at !== '' && strtotime($created_at) > strtotime($prev))) {
$passes[$seller_user_id] = [
'order_id' => $order_id,
'created_at' => $created_at,
'completed_at' => $completed_at,
'pass_id' => $download_id,
'expires_ts' => $expires_ts,
];
}
}
}
// Don't list the pass itself as a downloadable file.
continue;
}
if (!isset($latest[$download_id])) {
$latest[$download_id] = [
'order_id' => $order_id,
'created_at' => $created_at,
'completed_at' => $completed_at,
'via_pass' => false,
];
continue;
}
// Prefer the newest by date string fallback.
$prev = (string) ($latest[$download_id]['created_at'] ?? '');
if ($prev === '' || ($created_at !== '' && strtotime($created_at) > strtotime($prev))) {
$latest[$download_id] = [
'order_id' => $order_id,
'created_at' => $created_at,
'completed_at' => $completed_at,
'via_pass' => false,
];
}
}
}
// Expand library with items unlocked by All Access passes.
if (!empty($passes)) {
foreach ($passes as $seller_user_id => $pinfo) {
$seller_user_id = (int) $seller_user_id;
$order_id = (int) ($pinfo['order_id'] ?? 0);
$created_at = (string) ($pinfo['created_at'] ?? '');
$pass_id = (int) ($pinfo['pass_id'] ?? 0);
$pass_expires_ts = (int) ($pinfo['expires_ts'] ?? 0);
if ($seller_user_id <= 0 || $order_id <= 0 || $pass_id <= 0) {
continue;
}
$seller_downloads = get_posts([
'post_type' => \ToDownload\PostTypes\DownloadPostType::POST_TYPE,
'author' => $seller_user_id,
'post_status' => 'publish',
'posts_per_page' => 200,
'orderby' => 'date',
'order' => 'DESC',
'fields' => 'ids',
]);
if (!is_array($seller_downloads) || empty($seller_downloads)) {
continue;
}
foreach ($seller_downloads as $did) {
$did = (int) $did;
if ($did <= 0) {
continue;
}
if ($did === $pass_id) {
continue;
}
if ((string) get_post_meta($did, '_ToDownload_is_all_access_pass', true) === '1') {
continue;
}
if (!isset($latest[$did])) {
$latest[$did] = [
'order_id' => $order_id,
'created_at' => $created_at,
'completed_at' => (string) ($pinfo['completed_at'] ?? $created_at),
'via_pass' => true,
'seller_user_id' => $seller_user_id,
'pass_id' => $pass_id,
'pass_expires_ts' => $pass_expires_ts,
];
}
}
}
}
if (empty($latest)) {
return '' . esc_html__('Downloads', '2download-connector') . ' '
. esc_html__('Downloads are available once an order is complete.', '2download-connector')
. '
' . esc_html__("Why can't I download?", '2download-connector') . ' '
. esc_html__('If your order is still processing, use “View receipt” then refresh.', '2download-connector')
. ' '
. esc_html__('If your access expired, click “Renew”.', '2download-connector')
. ' '
. esc_html__('If a link still fails, re-send your receipt from “View receipt”.', '2download-connector')
. '
';
}
ob_start();
echo '';
echo '
' . esc_html__('Downloads', '2download-connector') . ' ';
echo '
' . esc_html__('Downloads are available while your access is active. Use “View receipt” to re-send your receipt or refresh order status.', '2download-connector') . '
';
echo '
' . esc_html__("Why can't I download?", '2download-connector') . ' '
. esc_html__('If your order is still processing, use “View receipt” then refresh.', '2download-connector')
. ' '
. esc_html__('If your access expired, click “Renew”.', '2download-connector')
. ' '
. esc_html__('If a link still fails, re-send your receipt from “View receipt”.', '2download-connector')
. '
';
// Per-order actions (only for actual purchased orders, not All Access pass-unlocked items).
$order_actions = [];
foreach ($latest as $download_id => $info) {
if (!empty($info['via_pass'])) {
continue;
}
$order_id = (int) ($info['order_id'] ?? 0);
if ($order_id <= 0) {
continue;
}
if (!isset($order_actions[$order_id])) {
$order_actions[$order_id] = [
'created_at' => (string) ($info['created_at'] ?? ''),
'completed_at' => (string) ($info['completed_at'] ?? ''),
];
}
}
if (!empty($order_actions) && class_exists('ZipArchive')) {
echo '
';
echo '
' . esc_html__('Your orders', '2download-connector') . ' ';
foreach ($order_actions as $order_id => $meta) {
$order_id = (int) $order_id;
$order_public_id = Orders::order_public_id($order_id);
$dt = (string) ($meta['created_at'] ?? '');
$dt_label = '—';
$dt_ts = $dt !== '' ? strtotime($dt . ' UTC') : false;
if ($dt_ts) {
$dt_label = date_i18n(get_option('date_format'), $dt_ts);
}
$zip_url = DownloadDelivery::generate_download_all_link($order_id);
$receipt_url = add_query_arg([
'ToDownload_order' => $order_public_id,
], $receipt_base);
if (!is_user_logged_in() && $email !== '') {
$receipt_url = add_query_arg([
'ToDownload_email' => $email,
'ToDownload_receipt_token' => Token::receipt_token($order_public_id, $email),
], $receipt_url);
}
echo '
';
}
echo '
';
}
echo '
';
echo '';
echo '' . esc_html__('Item', '2download-connector') . ' ';
echo '' . esc_html__('Access', '2download-connector') . ' ';
echo '' . esc_html__('Expires', '2download-connector') . ' ';
echo '' . esc_html__('Actions', '2download-connector') . ' ';
$settings = get_option('ToDownload_settings', []);
if (!is_array($settings)) {
$settings = [];
}
$membership_cache = [];
$show_license_col = false;
foreach ($latest as $download_id => $info) {
if (!empty($info['via_pass'])) {
continue;
}
$oid = (int) ($info['order_id'] ?? 0);
if ($oid <= 0) {
continue;
}
if (!array_key_exists($oid, $membership_cache)) {
$membership_cache[$oid] = self::order_is_seller_membership_purchase(Orders::get_order_items($oid), $settings);
}
if (!empty($membership_cache[$oid])) {
$show_license_col = true;
break;
}
}
if ($show_license_col) {
echo '' . esc_html__('License', '2download-connector') . ' ';
}
echo ' ';
foreach ($latest as $download_id => $info) {
$download_id = (int) $download_id;
$order_id = (int) ($info['order_id'] ?? 0);
$created_at = (string) ($info['created_at'] ?? '');
$completed_at = (string) ($info['completed_at'] ?? $created_at);
$via_pass = !empty($info['via_pass']);
$title = $download_id ? get_the_title($download_id) : '';
$date_label = '—';
$created_ts = $created_at !== '' ? strtotime($created_at . ' UTC') : false;
if ($created_ts) {
$date_label = esc_html(date_i18n(get_option('date_format'), $created_ts));
}
// Expiry calculation mirrors DownloadDelivery.
$expires_ts = 0;
$days = 0;
$expired = false;
if ($via_pass) {
$expires_ts = (int) ($info['pass_expires_ts'] ?? 0);
$expired = $expires_ts > 0 && time() > $expires_ts;
} else {
$days = (int) get_post_meta($download_id, '_ToDownload_access_days', true);
if ($days <= 0) {
$days = $default_access_days;
}
$days = max(0, $days);
if ($days > 0) {
$completed_ts = $completed_at !== '' ? strtotime($completed_at . ' UTC') : false;
if ($completed_ts) {
$expires_ts = (int) ($completed_ts + ($days * DAY_IN_SECONDS));
$expires_ts = (int) apply_filters('ToDownload_download_expires_ts', $expires_ts, $order_id, $download_id, ['id' => $order_id, 'created_at' => $created_at, 'updated_at' => $completed_at]);
$expired = $expires_ts > 0 && time() > $expires_ts;
}
}
}
$expires_label = $expires_ts > 0 ? date_i18n(get_option('date_format'), $expires_ts) : __('Unlimited', '2download-connector');
if ($expired && $expires_ts > 0) {
$expires_label = sprintf(
/* translators: %s = expiration date */
__('Expired on %s', '2download-connector'),
date_i18n(get_option('date_format'), $expires_ts)
);
}
$license = '';
if (!$via_pass) {
if (!array_key_exists($order_id, $membership_cache)) {
$membership_cache[$order_id] = self::order_is_seller_membership_purchase(Orders::get_order_items($order_id), $settings);
}
if (!empty($membership_cache[$order_id])) {
// Ensure licenses exist for this order.
Licenses::ensure_for_order($order_id);
$license = Licenses::get_for_item($order_id, $download_id);
}
}
$link = DownloadDelivery::generate_link($order_id, $download_id);
$order_public_id = Orders::order_public_id($order_id);
$receipt_url = add_query_arg([
'ToDownload_order' => $order_public_id,
], $receipt_base);
if (!is_user_logged_in() && $email !== '') {
$receipt_url = add_query_arg([
'ToDownload_email' => $email,
'ToDownload_receipt_token' => Token::receipt_token($order_public_id, $email),
], $receipt_url);
}
$access_label = $via_pass ? __('All Access Pass', '2download-connector') : __('Purchased', '2download-connector');
if (!$via_pass && $date_label !== '—') {
$access_label .= ' • ' . $date_label;
}
echo '';
echo '' . esc_html($title !== '' ? $title : ('#' . $download_id)) . ' ';
echo '' . esc_html($access_label) . ' ';
echo '' . esc_html($expires_label) . ' ';
echo '';
if ($expired) {
$renew_url = add_query_arg([
'ToDownload_add_to_cart' => $download_id,
'ToDownload_buy_now' => 1,
'ToDownload_clear_cart' => 1,
], home_url('/'));
if ($email !== '') {
$renew_url = add_query_arg(['ToDownload_email' => $email], $renew_url);
}
$renew_url = wp_nonce_url($renew_url, 'ToDownload_add_to_cart_' . $download_id, 'ToDownload_nonce');
echo '' . esc_html__('Renew', '2download-connector') . ' ';
} else {
echo '' . esc_html__('Download', '2download-connector') . ' ';
}
echo ' ' . esc_html__('View receipt', '2download-connector') . ' ';
echo ' ';
if ($show_license_col) {
echo '';
if ($license !== '') {
echo '' . esc_html($license) . '';
} else {
echo esc_html('—');
}
echo ' ';
}
echo ' ';
}
echo '
';
echo '
';
return (string) ob_get_clean();
}
private static function get_viewer_orders(string $email): array {
if (is_user_logged_in()) {
$orders = Orders::find_orders_by_user_id(get_current_user_id());
if (!empty($orders)) {
return $orders;
}
}
return $email !== '' ? Orders::find_orders_by_email($email) : [];
}
private static function order_is_seller_membership_purchase(array $items, array $settings): bool {
if (count($items) !== 1) {
return false;
}
$it = $items[0] ?? null;
if (!is_array($it)) {
return false;
}
$download_id = (int) ($it['download_id'] ?? 0);
if ($download_id <= 0) {
return false;
}
$tier = sanitize_key((string) get_post_meta($download_id, self::META_SELLER_MEMBERSHIP_TIER, true));
if ($tier === Pricing::TIER_PRO || $tier === Pricing::TIER_STUDIO) {
return true;
}
$pro_id = (int) ($settings['seller_plan_pro_product_id'] ?? 0);
$studio_id = (int) ($settings['seller_plan_studio_product_id'] ?? 0);
if ($studio_id > 0 && $download_id === $studio_id) {
return true;
}
if ($pro_id > 0 && $download_id === $pro_id) {
return true;
}
return false;
}
public static function subscriptions(): string {
$email = self::effective_email();
if (!$email) {
return '' . esc_html__('Log in or use the secure account link from your receipt email to view subscriptions.', '2download-connector') . '
';
}
return self::subscriptions_html($email);
}
private static function subscriptions_html(string $email): string {
$rows = Subscriptions::for_email($email);
ob_start();
echo '';
echo '
' . esc_html__('Subscriptions', '2download-connector') . ' ';
if (empty($rows)) {
echo '
' . esc_html__('No active or past subscriptions found.', '2download-connector') . '
';
echo '
';
return (string) ob_get_clean();
}
echo '';
echo '';
echo '' . esc_html__('Item', '2download-connector') . ' ';
echo '' . esc_html__('Status', '2download-connector') . ' ';
echo '' . esc_html__('Expires', '2download-connector') . ' ';
echo '' . esc_html__('Action', '2download-connector') . ' ';
echo ' ';
foreach ($rows as $r) {
$download_id = (int) ($r['download_id'] ?? 0);
$title = $download_id ? get_the_title($download_id) : '';
$status = (string) ($r['status'] ?? '');
$expires_ts = (int) ($r['expires_ts'] ?? 0);
$expires = $expires_ts > 0 ? date_i18n(get_option('date_format'), $expires_ts) : __('Unlimited', '2download-connector');
$renew = '';
if ($download_id) {
$url = add_query_arg([
'ToDownload_add_to_cart' => $download_id,
'ToDownload_buy_now' => 1,
'ToDownload_email' => $email,
], self::current_url());
$url = wp_nonce_url($url, 'ToDownload_add_to_cart_' . $download_id, 'ToDownload_nonce');
$renew = '' . esc_html__('Renew', '2download-connector') . ' ';
}
echo '';
echo '' . esc_html($title) . ' ';
echo '' . esc_html($status === 'expired' ? __('Expired', '2download-connector') : __('Active', '2download-connector')) . ' ';
echo '' . esc_html($expires) . ' ';
echo '' . wp_kses_post($renew) . ' ';
echo ' ';
}
echo '
';
echo '';
return (string) ob_get_clean();
}
public static function downloads_grid($atts): string {
$atts = shortcode_atts([
'posts_per_page' => 12,
], $atts);
$q = new \WP_Query([
'post_type' => [
\ToDownload\PostTypes\DownloadPostType::POST_TYPE,
],
'posts_per_page' => (int) $atts['posts_per_page'],
]);
ob_start();
echo '';
while ($q->have_posts()) {
$q->the_post();
$id = (int) get_the_ID();
echo wp_kses_post(self::render_product_card($id, do_shortcode('[ToDownload_purchase_link id="' . (int) $id . '"]')));
}
wp_reset_postdata();
echo '
';
return (string) ob_get_clean();
}
public static function purchase_link($atts): string {
$atts = shortcode_atts([
'id' => 0,
'text' => __('Add to Cart', '2download-connector'),
], $atts);
$id = (int) $atts['id'];
if (!$id) {
return '';
}
$url = add_query_arg(['ToDownload_add_to_cart' => $id], self::current_url());
$url = wp_nonce_url($url, 'ToDownload_add_to_cart_' . $id, 'ToDownload_nonce');
$url = esc_url($url);
return '' . esc_html($atts['text']) . '
';
}
public static function checkout(): string {
$cart = Cart::instance();
$items = $cart->all();
$email_prefill = self::checkout_email_prefill();
// phpcs:disable WordPress.Security.NonceVerification.Recommended -- Read-only prefill/notice query params; no state changes.
$first_prefill = isset($_GET['ToDownload_first_name']) ? sanitize_text_field((string) wp_unslash($_GET['ToDownload_first_name'])) : '';
$last_prefill = isset($_GET['ToDownload_last_name']) ? sanitize_text_field((string) wp_unslash($_GET['ToDownload_last_name'])) : '';
$discount_prefill = isset($_GET['ToDownload_discount_code']) ? sanitize_text_field((string) wp_unslash($_GET['ToDownload_discount_code'])) : '';
$gateway_prefill = isset($_GET['ToDownload_gateway']) ? sanitize_text_field((string) wp_unslash($_GET['ToDownload_gateway'])) : 'hosted_checkout';
$error_msg = isset($_GET['ToDownload_checkout_error']) ? sanitize_text_field((string) wp_unslash($_GET['ToDownload_checkout_error'])) : '';
// phpcs:enable WordPress.Security.NonceVerification.Recommended
$seller_user_id = self::cart_single_seller_user_id($items);
// Always enable discount preview for better buyer experience.
$discount_preview_enabled = true;
ob_start();
echo '';
echo '
' . esc_html__('Checkout', '2download-connector') . ' ';
if (empty($items)) {
echo '
' . esc_html__('Your cart is empty.', '2download-connector') . '
';
echo '
';
return (string) ob_get_clean();
}
if ($error_msg !== '') {
echo '' . esc_html($error_msg) . '
';
}
$cart_url = esc_url(self::cart_url());
echo '' . esc_html__('← Back to cart', '2download-connector') . '
';
echo '' . esc_html__('Order summary', '2download-connector') . ' ';
echo '';
echo '' . esc_html__('Item', '2download-connector') . ' ' . esc_html__('Price', '2download-connector') . ' ' . esc_html__('Action', '2download-connector') . ' ';
foreach ($items as $it) {
$download_id = (int) ($it['download_id'] ?? 0);
if ($download_id <= 0) {
continue;
}
$title = get_the_title($download_id);
$permalink = get_permalink($download_id);
$price = (float) ($it['price'] ?? 0);
$remove = add_query_arg(['ToDownload_remove_from_cart' => $download_id], self::current_url());
$remove = wp_nonce_url($remove, 'ToDownload_remove_from_cart_' . $download_id, 'ToDownload_nonce');
echo '';
echo '';
if ($permalink) {
echo '' . esc_html($title !== '' ? $title : ('#' . $download_id)) . ' ';
} else {
echo esc_html($title !== '' ? $title : ('#' . $download_id));
}
echo ' ';
echo '' . esc_html(Money::usd($price)) . ' ';
echo '' . esc_html__('Remove', '2download-connector') . ' ';
echo ' ';
}
echo '
';
$subtotal = (float) $cart->subtotal();
$tax = (float) $cart->tax();
$discount = 0.0;
$discount_preview_error = '';
$discount_code_applied = '';
if ($discount_preview_enabled && $discount_prefill !== '') {
$seller_user_id = 0;
foreach ($items as $it) {
$download_id = (int) ($it['download_id'] ?? 0);
if ($download_id > 0) {
$seller_user_id = (int) get_post_field('post_author', $download_id);
break;
}
}
$buyer_email = '';
if (is_user_logged_in()) {
$user = wp_get_current_user();
$buyer_email = $user && !empty($user->user_email) ? (string) $user->user_email : '';
}
$res = Discounts::apply_discount_code($discount_prefill, $items, $subtotal, $seller_user_id, $buyer_email);
if (!empty($res['ok'])) {
$discount = (float) ($res['discount'] ?? 0.0);
$discount_code_applied = $discount_prefill;
} else {
$discount_preview_error = (string) ($res['message'] ?? __('Invalid discount code.', '2download-connector'));
}
}
$total = (float) max(0.0, $subtotal + $tax - $discount);
echo '';
echo '
' . esc_html__('Order Total', '2download-connector') . ' ';
echo '
' . esc_html__('Subtotal:', '2download-connector') . ' ' . esc_html(Money::usd($subtotal)) . '
';
if ($discount > 0 && $discount_code_applied !== '') {
echo '
' . esc_html__('Discount', '2download-connector') . ' (' . esc_html($discount_code_applied) . '): -' . esc_html(Money::usd($discount)) . '
';
}
if ($tax > 0) {
echo '
' . esc_html__('Tax:', '2download-connector') . ' ' . esc_html(Money::usd($tax)) . '
';
}
echo '
';
echo '
' . esc_html__('Total:', '2download-connector') . ' ' . esc_html(Money::usd($total)) . '
';
echo '
';
// Upsells (Shopify-style recommendations in checkout based on cart contents).
$cart_ids = [];
$recommended_ids = [];
foreach ($items as $it) {
$download_id = (int) ($it['download_id'] ?? 0);
if ($download_id <= 0) {
continue;
}
$cart_ids[] = $download_id;
$upsells = get_post_meta($download_id, '_ToDownload_upsell_download_ids', true);
if (is_array($upsells)) {
foreach ($upsells as $u) {
$u = (int) $u;
if ($u > 0) {
$recommended_ids[] = $u;
}
}
}
}
$cart_ids = array_values(array_unique(array_filter(array_map('intval', $cart_ids))));
$recommended_ids = array_values(array_unique(array_filter(array_map('intval', $recommended_ids))));
if (!empty($cart_ids) && !empty($recommended_ids)) {
$recommended_ids = array_values(array_diff($recommended_ids, $cart_ids));
}
if (!empty($recommended_ids)) {
$upsell_posts = get_posts([
'post_type' => 'todownload_download',
'post__in' => $recommended_ids,
'orderby' => 'post__in',
'post_status' => 'publish',
'posts_per_page' => 6,
]);
if (is_array($upsell_posts) && !empty($upsell_posts)) {
echo '';
echo '
' . esc_html__('You may also like', '2download-connector') . ' ';
echo '
';
foreach ($upsell_posts as $p) {
$pid = (int) $p->ID;
$current = self::current_url();
$add_url = add_query_arg([
'ToDownload_add_to_cart' => $pid,
], $current);
$add_url = wp_nonce_url($add_url, 'ToDownload_add_to_cart_' . $pid, 'ToDownload_nonce');
echo wp_kses_post(self::render_product_card(
$pid,
'
' . esc_html__('Add to cart', '2download-connector') . ' '
));
}
echo '
';
echo '
';
}
}
?>
';
return (string) ob_get_clean();
}
public static function purchase_history(): string {
return self::purchase_history_html(true);
}
private static function purchase_history_html(bool $with_wrapper): string {
$email = self::effective_email();
ob_start();
if ($with_wrapper) {
echo '';
echo '
' . esc_html__('Purchase History', '2download-connector') . ' ';
} else {
echo '
';
echo '
' . esc_html__('Purchase History', '2download-connector') . ' ';
}
if (!$email) {
echo '
' . esc_html__('Log in or use the secure account link from your receipt email to view purchase history.', '2download-connector') . '
';
echo '
';
return (string) ob_get_clean();
}
// Handle resend receipt action (GET) before rendering.
$resend_receipt = isset($_GET['ToDownload_resend_receipt']) ? sanitize_key((string) wp_unslash($_GET['ToDownload_resend_receipt'])) : '';
if ($resend_receipt === '1') {
$order_public_id = isset($_GET['ToDownload_order']) ? sanitize_text_field((string) wp_unslash($_GET['ToDownload_order'])) : '';
$legacy_order_id = isset($_GET['ToDownload_order_id']) ? absint(wp_unslash($_GET['ToDownload_order_id'])) : 0;
$nonce = isset($_GET['ToDownload_nonce']) ? sanitize_text_field((string) wp_unslash($_GET['ToDownload_nonce'])) : '';
$order_public_id = trim((string) $order_public_id);
$resolved_order_id = 0;
if ($order_public_id !== '') {
$resolved_order_id = Orders::find_order_id_by_public_id($order_public_id);
} else if ($legacy_order_id > 0) {
$resolved_order_id = $legacy_order_id;
$order_public_id = Orders::order_public_id($resolved_order_id);
}
$nonce_ok = false;
if ($nonce !== '') {
if ($order_public_id !== '') {
$nonce_ok = wp_verify_nonce($nonce, 'ToDownload_resend_receipt_' . $order_public_id);
} else if ($resolved_order_id > 0) {
$nonce_ok = wp_verify_nonce($nonce, 'ToDownload_resend_receipt_' . $resolved_order_id);
}
}
if ($resolved_order_id > 0 && $nonce_ok) {
if (self::viewer_can_access_order($resolved_order_id, $email)) {
$ok = EmailDelivery::instance()->resend_order_email($resolved_order_id);
if ($ok) {
echo '
' . esc_html__('Receipt sent.', '2download-connector') . '
';
} else {
echo '
' . esc_html__('Could not send receipt. (Order must be complete and have a valid email.)', '2download-connector') . '
';
}
} else {
echo '
' . esc_html__('Not allowed.', '2download-connector') . '
';
}
}
}
$orders = self::get_viewer_orders($email);
if (empty($orders)) {
echo '
' . esc_html__('No orders found.', '2download-connector') . '
';
echo '
';
return (string) ob_get_clean();
}
$selected_order_public_id = isset($_GET['ToDownload_order']) ? sanitize_text_field((string) wp_unslash($_GET['ToDownload_order'])) : '';
$selected_order_id = 0;
if ($selected_order_public_id !== '') {
$selected_order_id = Orders::find_order_id_by_public_id((string) $selected_order_public_id);
} else if (isset($_GET['ToDownload_order_id'])) {
// Back-compat for old links.
$selected_order_id = absint(wp_unslash($_GET['ToDownload_order_id']));
$selected_order_public_id = Orders::order_public_id($selected_order_id);
}
if ($selected_order_id > 0) {
$selected = null;
foreach ($orders as $o) {
if ((int) ($o['id'] ?? 0) === $selected_order_id) {
$selected = $o;
break;
}
}
if (!$selected) {
echo '' . esc_html__('Order not found.', '2download-connector') . '
';
echo '';
return (string) ob_get_clean();
}
$order_id = (int) $selected['id'];
$items = Orders::get_order_items($order_id);
$settings = get_option('ToDownload_settings', []);
if (!is_array($settings)) {
$settings = [];
}
$show_license = self::order_is_seller_membership_purchase($items, $settings);
$back = remove_query_arg(['ToDownload_order', 'ToDownload_order_id'], self::current_url());
$back = esc_url($back);
echo '' . esc_html__('← Back to orders', '2download-connector') . '
';
$ref = $selected_order_public_id !== '' ? substr((string) $selected_order_public_id, -8) : '—';
echo '' . esc_html__('Order reference', '2download-connector') . ' ' . esc_html($ref) . ' ';
echo '' . esc_html__('Status:', '2download-connector') . ' ' . esc_html((string) ($selected['status'] ?? '')) . ' ';
echo '' . esc_html__('Total:', '2download-connector') . ' ' . esc_html(Money::usd((float) ($selected['total'] ?? 0))) . '
';
if ((string) ($selected['status'] ?? '') === 'complete') {
$order_public_id = Orders::order_public_id($order_id);
$resend = add_query_arg([
'ToDownload_order' => $order_public_id,
'ToDownload_resend_receipt' => 1,
], self::current_url());
$resend = wp_nonce_url($resend, 'ToDownload_resend_receipt_' . $order_public_id, 'ToDownload_nonce');
echo '' . esc_html__('Resend receipt', '2download-connector') . '
';
echo '';
echo '';
echo '' . esc_html__('Item', '2download-connector') . ' ';
echo '' . esc_html__('Download', '2download-connector') . ' ';
if ($show_license) {
echo '' . esc_html__('License', '2download-connector') . ' ';
Licenses::ensure_for_order($order_id);
}
echo ' ';
foreach ($items as $it) {
$download_id = (int) ($it['download_id'] ?? 0);
if ($download_id <= 0) {
continue;
}
$title = get_the_title($download_id);
$link = DownloadDelivery::generate_link($order_id, $download_id);
echo '' . esc_html($title) . ' ' . esc_html__('Download', '2download-connector') . ' ';
if ($show_license) {
$license = Licenses::get_for_item($order_id, $download_id);
echo '' . esc_html($license !== '' ? $license : '—') . ' ';
}
echo ' ';
}
echo '
';
} else {
echo '' . esc_html__('Downloads are available once an order is complete.', '2download-connector') . '
';
}
echo '';
return (string) ob_get_clean();
}
echo '';
echo '';
echo '' . esc_html__('Order', '2download-connector') . ' ';
echo '' . esc_html__('Date', '2download-connector') . ' ';
echo '' . esc_html__('Status', '2download-connector') . ' ';
echo '' . esc_html__('Total', '2download-connector') . ' ';
echo '' . esc_html__('Action', '2download-connector') . ' ';
echo ' ';
foreach ($orders as $order) {
$order_id = (int) ($order['id'] ?? 0);
$date = (string) ($order['created_at'] ?? '');
$status = (string) ($order['status'] ?? '');
$total = (float) ($order['total'] ?? 0);
$order_public_id = Orders::order_public_id($order_id);
$view = add_query_arg(['ToDownload_order' => $order_public_id], self::current_url());
$view = esc_url($view);
echo '';
$ref = $order_public_id !== '' ? substr($order_public_id, -8) : '—';
echo '' . esc_html($ref) . ' ';
echo '' . esc_html($date) . ' ';
echo '' . esc_html($status) . ' ';
echo '' . esc_html(Money::usd($total)) . ' ';
echo '' . esc_html__('View', '2download-connector') . ' ';
echo ' ';
}
echo '
';
echo '';
return (string) ob_get_clean();
}
private static function viewer_can_access_order(int $order_id, string $effective_email): bool {
$order = Orders::get_order($order_id);
if (!$order) {
return false;
}
$customer_id = (int) ($order['customer_id'] ?? 0);
if ($customer_id <= 0) {
return false;
}
$customer = Orders::get_customer($customer_id);
if (!$customer) {
return false;
}
if (is_user_logged_in()) {
$uid = (int) get_current_user_id();
$cid_uid = (int) ($customer['user_id'] ?? 0);
if ($cid_uid > 0 && $cid_uid === $uid) {
return true;
}
}
$order_email = sanitize_email((string) ($customer['email'] ?? ''));
return $order_email !== '' && $effective_email !== '' && strtolower($order_email) === strtolower($effective_email);
}
}