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 '
';
}
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');
}
?>
/>
>
>
>
·
×
style="display:none;">
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 .= ''
. ' '
. esc_html($label)
. ' ';
}
}
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 .= ''
. ' '
. esc_html($label)
. ' ';
}
}
wp_send_json_success([
'html' => $html,
'has_more' => $has_more,
'next_page' => $paged + 1,
]);
}
}