\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 ($url !== '') { echo '' . esc_html($title !== '' ? $title : ('#' . $post_id)) . ''; } else { echo esc_html($title !== '' ? $title : ('#' . $post_id)); } 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 '
'; // Nonce to avoid processing arbitrary query-string submissions. echo wp_kses_post(wp_nonce_field('todownload_listings', 'ToDownload_listings_nonce', true, false)); foreach ($hidden as $k => $v) { $k = sanitize_key((string) $k); $v = (string) $v; if ($k === '' || $v === '') { continue; } echo ''; } echo ''; echo ''; echo ''; 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 = '' . esc_attr((string) ($user->display_name ?? '')) . ''; } 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 '

' . esc_html($bio) . '

'; } 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 ''; echo ''; echo ''; 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 ''; echo ''; echo ''; echo ''; } echo '
' . esc_html__('Item', '2download-connector') . '' . esc_html__('Price', '2download-connector') . '' . esc_html__('Action', '2download-connector') . '
'; if ($permalink) { echo '' . esc_html($title !== '' ? $title : ('#' . $download_id)) . ''; } else { echo esc_html($title !== '' ? $title : ('#' . $download_id)); } echo '' . esc_html(Money::usd($price)) . '' . esc_html__('Remove', '2download-connector') . '
'; 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 '

' . esc_html($msg) . '

'; } 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 ''; } else { echo ''; } if ($created_at !== '') { echo ''; } $total_label = strtoupper($currency) === 'USD' ? Money::usd($total) : ($currency . ' ' . number_format($total, 2)); echo ''; if ($hosted_mode && $hosted_entitlement_status !== '') { echo ''; } if ($hosted_mode && $show_license && $hosted_license !== '') { echo ''; } echo ''; echo '
' . esc_html__('Status', '2download-connector') . '' . esc_html($hosted_status) . '
' . esc_html__('Status', '2download-connector') . '' . esc_html($status) . '
' . esc_html__('Date', '2download-connector') . '' . esc_html($created_at) . '
' . esc_html__('Total', '2download-connector') . '' . esc_html($total_label) . '
' . esc_html__('Entitlement', '2download-connector') . '' . esc_html($hosted_entitlement_status) . '
' . esc_html__('License', '2download-connector') . '' . esc_html($hosted_license) . '
'; $purchased_ids = []; $recommended_ids = []; $continue_shopping_url = ''; if (!empty($items)) { echo '

' . esc_html__('Items', '2download-connector') . '

'; echo '
'; echo ''; if ($show_license) { echo ''; } 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 ''; echo ''; if ($show_license) { echo ''; } echo ''; } echo '
' . esc_html__('Item', '2download-connector') . '' . esc_html__('Download', '2download-connector') . '' . esc_html__('License', '2download-connector') . '
'; if ($permalink) { echo '' . esc_html($title !== '' ? $title : ('#' . $download_id)) . ''; } else { echo esc_html($title !== '' ? $title : ('#' . $download_id)); } echo ''; if ($dl !== '') { echo '' . esc_html__('Download', '2download-connector') . ''; } else { echo esc_html__('Preparing…', '2download-connector'); } echo ''; if ($license !== '') { echo '' . esc_html($license) . ''; } else { echo esc_html('—'); } 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 ''; 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 '
'; $ref = $order_public_id !== '' ? substr($order_public_id, -8) : '—'; echo '' . esc_html__('Order reference', '2download-connector') . ' ' . esc_html($ref) . ''; echo '(' . esc_html($dt_label) . ')'; echo '' . esc_html__('Download all (.zip)', '2download-connector') . ''; echo '' . esc_html__('View receipt', '2download-connector') . ''; echo '
'; } echo '
'; } echo '
'; echo ''; echo ''; echo ''; echo ''; echo ''; $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 ''; } 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 ''; echo ''; echo ''; echo ''; if ($show_license_col) { echo ''; } echo ''; } echo '
' . esc_html__('Item', '2download-connector') . '' . esc_html__('Access', '2download-connector') . '' . esc_html__('Expires', '2download-connector') . '' . esc_html__('Actions', '2download-connector') . '' . esc_html__('License', '2download-connector') . '
' . esc_html($title !== '' ? $title : ('#' . $download_id)) . '' . esc_html($access_label) . '' . esc_html($expires_label) . ''; 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 ($license !== '') { echo '' . esc_html($license) . ''; } else { echo esc_html('—'); } 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 ''; echo ''; echo ''; echo ''; 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 ''; echo ''; echo ''; echo ''; echo ''; } echo '
' . esc_html__('Item', '2download-connector') . '' . esc_html__('Status', '2download-connector') . '' . esc_html__('Expires', '2download-connector') . '' . esc_html__('Action', '2download-connector') . '
' . esc_html($title) . '' . esc_html($status === 'expired' ? __('Expired', '2download-connector') : __('Active', '2download-connector')) . '' . esc_html($expires) . '' . wp_kses_post($renew) . '
'; 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 ''; 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 ''; echo ''; echo ''; echo ''; } echo '
' . esc_html__('Item', '2download-connector') . '' . esc_html__('Price', '2download-connector') . '' . esc_html__('Action', '2download-connector') . '
'; if ($permalink) { echo '' . esc_html($title !== '' ? $title : ('#' . $download_id)) . ''; } else { echo esc_html($title !== '' ? $title : ('#' . $download_id)); } echo '' . esc_html(Money::usd($price)) . '' . esc_html__('Remove', '2download-connector') . '
'; $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 '
'; } } ?>




0) : ?>



0 && $discount_code_applied !== '') : ?>

0) : ?>

'; 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 ''; echo ''; if ($show_license) { echo ''; 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 ''; if ($show_license) { $license = Licenses::get_for_item($order_id, $download_id); echo ''; } echo ''; } echo '
' . esc_html__('Item', '2download-connector') . '' . esc_html__('Download', '2download-connector') . '' . esc_html__('License', '2download-connector') . '
' . esc_html($title) . '' . esc_html__('Download', '2download-connector') . '' . esc_html($license !== '' ? $license : '—') . '
'; } else { echo '

' . esc_html__('Downloads are available once an order is complete.', '2download-connector') . '

'; } echo ''; return (string) ob_get_clean(); } echo '
'; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; 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 ''; echo ''; echo ''; echo ''; echo ''; echo ''; } echo '
' . esc_html__('Order', '2download-connector') . '' . esc_html__('Date', '2download-connector') . '' . esc_html__('Status', '2download-connector') . '' . esc_html__('Total', '2download-connector') . '' . esc_html__('Action', '2download-connector') . '
' . esc_html($ref) . '' . esc_html($date) . '' . esc_html($status) . '' . esc_html(Money::usd($total)) . '' . esc_html__('View', '2download-connector') . '
'; 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); } }