base ?? ''), ['post', 'post-new'], true)) { return; } if ((string) ($screen->post_type ?? '') !== self::POST_TYPE) { return; } if (!function_exists('wp_max_upload_size')) { return; } $limit_bytes = (int) wp_max_upload_size(); if ($limit_bytes <= 0) { return; } $threshold = 16 * 1024 * 1024; if ($limit_bytes >= $threshold) { return; } $limit_label = function_exists('size_format') ? (string) size_format($limit_bytes) : ((string) $limit_bytes . ' bytes'); $help_url = 'https://wordpress.org/support/article/common-wordpress-errors/#maximum-upload-file-size'; echo '

' . esc_html(sprintf( /* translators: %s = upload size limit */ __('Your server upload limit is %s. This is set by your hosting (PHP/nginx), not by 2DL.', '2download-connector'), $limit_label )) . ' ' . '' . esc_html__('See instructions', '2download-connector') . '' . '

'; } private static function is_valid_upsell_candidate(int $candidate_id, int $author_id, int $current_post_id): bool { $candidate_id = (int) $candidate_id; $author_id = (int) $author_id; $current_post_id = (int) $current_post_id; if ($candidate_id <= 0 || $candidate_id === $current_post_id) { return false; } $p = get_post($candidate_id); if (!($p instanceof \WP_Post)) { return false; } if ((string) ($p->post_type ?? '') !== self::POST_TYPE) { return false; } if ((string) ($p->post_status ?? '') !== 'publish') { return false; } return (int) ($p->post_author ?? 0) === $author_id; } public static function enqueue_admin_assets(string $hook): void { if (!in_array($hook, ['post.php', 'post-new.php'], true)) { return; } if (!function_exists('get_current_screen')) { return; } $screen = get_current_screen(); if (!$screen) { return; } if (($screen->post_type ?? '') !== self::POST_TYPE) { return; } // Needed for wp.media (Media Library / upload modal). if (function_exists('wp_enqueue_media')) { wp_enqueue_media(); } $js = ToDownload_PLUGIN_URL . 'assets/admin-upsells.js'; wp_register_script('todownload-admin-upsells', $js, [], ToDownload_VERSION, true); wp_enqueue_script('todownload-admin-upsells'); $picker_js = ToDownload_PLUGIN_URL . 'assets/admin-media-picker.js'; wp_register_script('todownload-admin-media-picker', $picker_js, [], ToDownload_VERSION, true); wp_localize_script('todownload-admin-media-picker', 'ToDownloadMediaPicker', [ 'title' => __('Select or upload a file', '2download-connector'), 'button' => __('Use this file', '2download-connector'), 'selectedLabel' => __('Selected:', '2download-connector'), ]); wp_enqueue_script('todownload-admin-media-picker'); } public static function register_post_type(): void { register_post_type(self::POST_TYPE, [ 'labels' => [ 'name' => __('Products', '2download-connector'), 'singular_name' => __('Product', '2download-connector'), 'menu_name' => __('Products', '2download-connector'), 'add_new' => __('Add Product', '2download-connector'), 'add_new_item' => __('Add Product', '2download-connector'), 'edit_item' => __('Edit Product', '2download-connector'), 'new_item' => __('New Product', '2download-connector'), 'view_item' => __('View Product', '2download-connector'), 'view_items' => __('View Products', '2download-connector'), 'search_items' => __('Search Products', '2download-connector'), 'not_found' => __('No products found.', '2download-connector'), 'not_found_in_trash' => __('No products found in Trash.', '2download-connector'), 'all_items' => __('Products', '2download-connector'), ], 'public' => true, 'show_in_rest' => true, 'supports' => ['title', 'editor', 'thumbnail', 'comments'], 'capability_type' => 'todownload_download', 'map_meta_cap' => true, 'show_in_menu' => 'ToDownload_seller', 'menu_icon' => 'dashicons-download', 'has_archive' => true, 'rewrite' => ['slug' => '2download-connector'], 'template' => [ ['todownload/pricing', []], ['todownload/purchase-button', []], ['todownload/preview', []], ['todownload/reviews', []], ], ]); } public static function register_metaboxes(): void { add_meta_box( 'ToDownload_download_settings', __('Download Settings', '2download-connector'), [self::class, 'render_metabox'], self::POST_TYPE, 'side', 'default' ); } public static function render_metabox($post): void { wp_nonce_field('ToDownload_download_save', 'ToDownload_download_nonce'); $price = get_post_meta($post->ID, '_ToDownload_price', true); $file_id = get_post_meta($post->ID, '_ToDownload_file_attachment_id', true); $file_id_int = (int) $file_id; $file_label = ''; if ($file_id_int > 0) { $att = get_post($file_id_int); if ($att instanceof \WP_Post) { $file_label = (string) get_the_title($file_id_int); $file_label = trim($file_label); if ($file_label === '') { $file_label = $att->post_name !== '' ? $att->post_name : ''; } } } $access_days = (int) get_post_meta($post->ID, '_ToDownload_access_days', true); $is_all_access_pass = (string) get_post_meta($post->ID, '_ToDownload_is_all_access_pass', true) === '1'; $membership_tier = (string) get_post_meta($post->ID, '_ToDownload_seller_membership_tier', true); $membership_tier = sanitize_key($membership_tier); if (!in_array($membership_tier, [Pricing::TIER_PRO, Pricing::TIER_STUDIO], true)) { $membership_tier = ''; } $upsells = get_post_meta($post->ID, '_ToDownload_upsell_download_ids', true); if (!is_array($upsells)) { $upsells = []; } $upsells = array_values(array_filter(array_map('intval', $upsells))); $continue_url = (string) get_post_meta($post->ID, '_ToDownload_continue_shopping_url', true); $author_id = (int) ($post->post_author ?? 0); $selected = $upsells; $selected = array_values(array_unique(array_filter(array_map('intval', $selected)))); $selected = array_values(array_filter($selected, function ($id) use ($post, $author_id): bool { return self::is_valid_upsell_candidate((int) $id, (int) $author_id, (int) $post->ID); })); $upsells = $selected; $per_page = 50; // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude -- Excluding current + selected upsells is required for UX. $choices = get_posts([ 'post_type' => self::POST_TYPE, 'author' => $author_id, 'post_status' => ['publish'], 'posts_per_page' => $per_page, 'orderby' => 'title', 'order' => 'ASC', 'fields' => 'ids', // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude -- Excluding current + selected upsells is required for UX. 'exclude' => array_values(array_unique(array_merge([(int) $post->ID], $selected))), ]); if (!is_array($choices)) { $choices = []; } $maybe_more = count($choices) >= $per_page; ?>

0) { $label = $file_label !== '' ? $file_label : ('#' . $file_id_int); /* translators: 1: selected file label, 2: attachment ID */ echo esc_html(sprintf(__('Selected: %1$s (ID: %2$d)', '2download-connector'), $label, $file_id_int)); } else { esc_html_e('Use Browse to upload/select a file; the attachment ID will be filled in automatically.', '2download-connector'); } ?>


 · 

' . esc_html__('No other products found.', '2download-connector') . ''; } else { echo '
'; foreach ($selected as $sid) { $sid = (int) $sid; $title = get_the_title($sid); $label = $title !== '' ? $title : ('#' . $sid); $data_title = strtolower($label); echo ''; } foreach ($choices as $cid) { $cid = (int) $cid; $title = get_the_title($cid); $label = $title !== '' ? $title : ('#' . $cid); $data_title = strtolower($label); echo ''; } echo '
'; } ?>

0 && (int) $id !== (int) $post_id; }))); $author_id = (int) ($post->post_author ?? 0); $upsells = array_values(array_filter($upsells, function (int $id) use ($author_id, $post_id): bool { return self::is_valid_upsell_candidate((int) $id, (int) $author_id, (int) $post_id); })); $continue_url = isset($_POST['ToDownload_continue_shopping_url']) ? esc_url_raw((string) wp_unslash($_POST['ToDownload_continue_shopping_url'])) : ''; update_post_meta($post_id, '_ToDownload_price', $price); update_post_meta($post_id, '_ToDownload_file_attachment_id', $file_id); update_post_meta($post_id, '_ToDownload_access_days', $access_days); update_post_meta($post_id, '_ToDownload_is_all_access_pass', $is_all_access_pass); if ($membership_tier !== '') { update_post_meta($post_id, '_ToDownload_seller_membership_tier', $membership_tier); } else { delete_post_meta($post_id, '_ToDownload_seller_membership_tier'); } update_post_meta($post_id, '_ToDownload_upsell_download_ids', $upsells); if ($continue_url !== '') { update_post_meta($post_id, '_ToDownload_continue_shopping_url', $continue_url); } else { delete_post_meta($post_id, '_ToDownload_continue_shopping_url'); } } public static function ajax_upsells_search(): void { $post_id = isset($_POST['post_id']) ? absint(wp_unslash($_POST['post_id'])) : 0; $nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : ''; $q = isset($_POST['q']) ? sanitize_text_field((string) wp_unslash($_POST['q'])) : ''; $paged = isset($_POST['page']) ? max(1, absint(wp_unslash($_POST['page']))) : 1; $include_selected = !empty($_POST['include_selected']); $selected_ids = isset($_POST['selected']) && is_array($_POST['selected']) ? array_values(array_unique(array_filter(array_map('intval', (array) wp_unslash($_POST['selected']))))) : []; if ($post_id <= 0 || $nonce === '' || !wp_verify_nonce($nonce, 'ToDownload_upsells_ajax_' . $post_id)) { wp_send_json_error(['message' => 'invalid_nonce'], 403); } if (!current_user_can('edit_post', $post_id)) { wp_send_json_error(['message' => 'not_allowed'], 403); } $post = get_post($post_id); if (!$post || ($post->post_type ?? '') !== self::POST_TYPE) { wp_send_json_error(['message' => 'invalid_post'], 400); } $author_id = (int) ($post->post_author ?? 0); $per_page = 50; $query_args = [ 'post_type' => self::POST_TYPE, 'author' => $author_id, 'post_status' => ['publish'], 'posts_per_page' => $per_page, 'paged' => $paged, 'orderby' => 'title', 'order' => 'ASC', 'fields' => 'ids', 'no_found_rows' => false, // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in -- Excluding current + selected upsells is required for UX. 'post__not_in' => array_values(array_unique(array_merge([$post_id], $selected_ids))), ]; if ($q !== '') { $query_args['s'] = $q; } $wpq = new \WP_Query($query_args); $ids = is_array($wpq->posts) ? $wpq->posts : []; $found = (int) ($wpq->found_posts ?? 0); $has_more = ((int) $paged * (int) $per_page) < $found; $html = ''; if ($include_selected && !empty($selected_ids)) { foreach ($selected_ids as $sid) { $sid = (int) $sid; if (!self::is_valid_upsell_candidate((int) $sid, (int) $author_id, (int) $post_id)) { continue; } $title = get_the_title($sid); $label = $title !== '' ? $title : ('#' . $sid); $data_title = strtolower($label); $html .= ''; } } if (!empty($ids)) { foreach ($ids as $cid) { $cid = (int) $cid; if ($cid <= 0 || $cid === $post_id) { continue; } $title = get_the_title($cid); $label = $title !== '' ? $title : ('#' . $cid); $data_title = strtolower($label); $html .= ''; } } wp_send_json_success([ 'html' => $html, 'has_more' => $has_more, 'next_page' => $paged + 1, ]); } }