'string', 'single' => true, 'show_in_rest' => true, 'auth_callback' => $can_edit_downloads, ]); register_post_meta(DownloadPostType::POST_TYPE, '_ToDownload_product_type', [ 'type' => 'string', 'single' => true, 'show_in_rest' => true, 'default' => 'single', 'auth_callback' => $can_edit_downloads, ]); // Array of tiers: [{"name":"Personal","amount":"9.00"}, ...] register_post_meta(DownloadPostType::POST_TYPE, '_ToDownload_variable_prices', [ 'type' => 'array', 'single' => true, 'show_in_rest' => [ 'schema' => [ 'type' => 'array', 'items' => [ 'type' => 'object', 'properties' => [ 'name' => ['type' => 'string'], 'amount' => ['type' => 'string'], ], ], ], ], 'auth_callback' => $can_edit_downloads, ]); // Bundle item IDs (array of integers). register_post_meta(DownloadPostType::POST_TYPE, '_ToDownload_bundle_items', [ 'type' => 'array', 'single' => true, 'show_in_rest' => [ 'schema' => [ 'type' => 'array', 'items' => ['type' => 'integer'], ], ], 'auth_callback' => $can_edit_downloads, ]); register_post_meta(DownloadPostType::POST_TYPE, '_ToDownload_file_attachment_id', [ 'type' => 'integer', 'single' => true, 'show_in_rest' => true, 'auth_callback' => $can_edit_downloads, ]); register_post_meta(DownloadPostType::POST_TYPE, '_ToDownload_preview_attachment_id', [ 'type' => 'integer', 'single' => true, 'show_in_rest' => true, 'auth_callback' => $can_edit_downloads, ]); register_post_meta(DownloadPostType::POST_TYPE, '_ToDownload_access_days', [ 'type' => 'integer', 'single' => true, 'show_in_rest' => true, 'auth_callback' => $can_edit_downloads, ]); } public static function register_blocks(): void { register_block_type('todownload/storefront', [ 'api_version' => 2, 'render_callback' => [self::class, 'render_storefront'], 'attributes' => [ 'previewSellerId' => ['type' => 'integer', 'default' => 0], 'postsPerPage' => ['type' => 'number', 'default' => 12], ], ]); register_block_type('todownload/pricing', [ 'api_version' => 2, 'render_callback' => [self::class, 'render_pricing'], 'attributes' => [ 'showTitle' => ['type' => 'boolean', 'default' => false], ], ]); register_block_type('todownload/purchase-button', [ 'api_version' => 2, 'render_callback' => [self::class, 'render_purchase_button'], 'attributes' => [ 'showAddToCart' => ['type' => 'boolean', 'default' => true], 'showBuyNow' => ['type' => 'boolean', 'default' => true], 'addToCartText' => ['type' => 'string', 'default' => 'Add to Cart'], 'buyNowText' => ['type' => 'string', 'default' => 'Buy Now'], 'showPriceSelector' => ['type' => 'boolean', 'default' => true], ], ]); register_block_type('todownload/preview', [ 'api_version' => 2, 'render_callback' => [self::class, 'render_preview'], 'attributes' => [ 'useProductPreviewMeta' => ['type' => 'boolean', 'default' => true], 'attachmentId' => ['type' => 'integer'], ], ]); register_block_type('todownload/cart', [ 'api_version' => 2, 'render_callback' => [self::class, 'render_cart'], ]); register_block_type('todownload/checkout', [ 'api_version' => 2, 'render_callback' => [self::class, 'render_checkout'], ]); register_block_type('todownload/purchase-history', [ 'api_version' => 2, 'render_callback' => [self::class, 'render_purchase_history'], ]); register_block_type('todownload/products', [ 'api_version' => 2, 'render_callback' => [self::class, 'render_products'], 'attributes' => [ 'postsPerPage' => ['type' => 'number', 'default' => 12], ], ]); register_block_type('todownload/account', [ 'api_version' => 2, 'render_callback' => [self::class, 'render_account'], ]); register_block_type('todownload/reviews', [ 'api_version' => 2, 'render_callback' => [self::class, 'render_reviews'], ]); } public static function render_cart(): string { $cart = \ToDownload\Cart\Cart::instance(); $items = $cart->all(); ob_start(); echo '
' . esc_html__('Your cart is empty.', '2download-connector') . '
'; echo '' . esc_html__('Total:', '2download-connector') . ' ' . esc_html(Money::usd((float) $cart->total())) . '
'; echo ''; return (string) ob_get_clean(); } public static function render_checkout(): string { return \ToDownload\Shortcodes\Shortcodes::checkout(); } public static function render_purchase_history(): string { return \ToDownload\Shortcodes\Shortcodes::purchase_history(); } public static function render_account(): string { return \ToDownload\Shortcodes\Shortcodes::account(); } public static function render_products(array $attributes): string { $ppp = isset($attributes['postsPerPage']) ? (int) $attributes['postsPerPage'] : 12; $ppp = max(1, min(100, $ppp)); return \ToDownload\Shortcodes\Shortcodes::downloads_grid(['posts_per_page' => $ppp]); } public static function enqueue_editor_assets(): void { $js = ToDownload_PLUGIN_URL . 'assets/blocks.js'; $css = ToDownload_PLUGIN_URL . 'assets/blocks-editor.css'; wp_register_script( self::SCRIPT_HANDLE, $js, [ 'wp-blocks', 'wp-element', 'wp-i18n', 'wp-components', 'wp-block-editor', 'wp-data', 'wp-core-data', 'wp-server-side-render', ], ToDownload_VERSION, true ); if (function_exists('wp_set_script_translations')) { wp_set_script_translations(self::SCRIPT_HANDLE, '2download-connector', ToDownload_PLUGIN_DIR . 'languages'); } wp_enqueue_script(self::SCRIPT_HANDLE); // Provide editor-only context for blocks.js (used to default preview seller id). $user_id = (int) get_current_user_id(); $is_seller = $user_id > 0 && current_user_can('edit_todownload_downloads'); $payload = wp_json_encode([ 'currentUserId' => $user_id, 'isSeller' => $is_seller, ]); if (is_string($payload) && $payload !== '') { wp_add_inline_script(self::SCRIPT_HANDLE, 'window.ToDownloadBlocks = ' . $payload . ';', 'before'); } wp_register_style(self::STYLE_HANDLE_EDITOR, $css, [], ToDownload_VERSION); wp_enqueue_style(self::STYLE_HANDLE_EDITOR); } public static function render_storefront(array $attributes): string { $preview_seller_id = isset($attributes['previewSellerId']) ? (int) $attributes['previewSellerId'] : 0; $posts_per_page = isset($attributes['postsPerPage']) ? (int) $attributes['postsPerPage'] : 12; $posts_per_page = max(1, min(48, $posts_per_page)); // In the editor (ServerSideRender), use the attribute-provided preview seller. // On the front-end, the shortcode can resolve seller from query string. $atts = [ 'posts_per_page' => $posts_per_page, ]; if ($preview_seller_id > 0) { $atts['seller_user_id'] = $preview_seller_id; } return \ToDownload\Shortcodes\Shortcodes::storefront($atts); } private static function get_download_id_from_context(array $attributes, $block = null): int { if (!empty($attributes['downloadId'])) { return (int) $attributes['downloadId']; } // In the block-renderer REST endpoint and some editor contexts, global $post can be // something other than the Download being edited. Use block context when available. if ($block instanceof \WP_Block) { $ctx_post_id = isset($block->context['postId']) ? (int) $block->context['postId'] : 0; if ($ctx_post_id > 0) { return $ctx_post_id; } } // ServerSideRender (editor) hits the block-renderer REST route and often supplies `post_id`. // Use it when global $post isn't reliably set. if (defined('REST_REQUEST') && REST_REQUEST) { $req_post_id = 0; $rest_nonce = isset($_SERVER['HTTP_X_WP_NONCE']) ? sanitize_text_field((string) wp_unslash($_SERVER['HTTP_X_WP_NONCE'])) : ''; if ($rest_nonce !== '' && wp_verify_nonce($rest_nonce, 'wp_rest')) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Gated by verified wp_rest nonce above. if (isset($_POST['post_id'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Gated by verified wp_rest nonce above. $req_post_id = absint(wp_unslash($_POST['post_id'])); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Gated by verified wp_rest nonce above. } elseif (isset($_GET['post_id'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Gated by verified wp_rest nonce above. $req_post_id = absint(wp_unslash($_GET['post_id'])); } } if ($req_post_id > 0) { return $req_post_id; } } global $post; return $post ? (int) $post->ID : 0; } public static function render_pricing(array $attributes, string $content = '', $block = null): string { $download_id = self::get_download_id_from_context($attributes, $block); if (!$download_id) { return ''; } $price = (string) get_post_meta($download_id, '_ToDownload_price', true); $product_type = (string) get_post_meta($download_id, '_ToDownload_product_type', true); $tiers = get_post_meta($download_id, '_ToDownload_variable_prices', true); if (!is_array($tiers)) { $tiers = []; } ob_start(); echo '