money = $money; $this->pageTitle = $pageTitle; $this->setTierDiscountDeps( $discounts, $discountSettings ); } /** * The component slug. * * @return string */ public function slug(): string { return 'product-details'; } /** * Reduce a product to the flat, escape-ready view data the template needs. * * @param Product $product The product to render. * @param array $attrs Resolved attribute values (the tier-meta toggles). * @return array */ protected function viewData( Product $product, array $attrs ): array { $settings = $product->settings(); return array( 'name' => $product->name(), 'show_name' => ! $this->pageTitle->blockHidesNameFor( $product ), 'layout' => 'single' === ( $attrs['layout'] ?? 'two-column' ) ? 'single' : 'two-column', 'main_image' => $this->image( (int) ( $settings['main_image'] ?? 0 ) ), 'overview' => $this->richField( $product->overview() ), 'description' => $this->richField( $product->description() ), 'screenshots' => ScreenshotGallery::fromIds( $settings['screenshots'] ?? array() ), 'purchase' => array( 'block' => 'releasecart-product-details' ) + ProductTierSelector::formData( $product, $this->money, TierMetaPresenter::flagsFrom( $attrs ), $this->tierDiscountMap( $product ), $this->tierStrikeEnabled() ), ); } /** * Whether the product has no purchasable tier. * * @param array $data The reduced view data. * @return bool */ protected function viewDataIsEmpty( array $data ): bool { return empty( $data['purchase']['has_tiers'] ); } /** * Sample purchase data for an editor preview. * * @param Product $product The resolved product. * @param array $attrs Resolved attribute values (the tier-meta toggles). * @return array */ protected function sampleViewData( Product $product, array $attrs ): array { $data = $this->viewData( $product, $attrs ); $data['purchase'] = array( 'block' => 'releasecart-product-details' ) + ProductTierSelector::formData( SamplePreview::tierProduct(), $this->money, TierMetaPresenter::flagsFrom( $attrs ) ); return $data; } /** * The routed product and tier-meta options. * * @return array> */ public function schema(): array { return parent::schema() + array( 'layout' => array( 'type' => 'enum', 'enum' => array( 'two-column', 'single' ), 'default' => 'two-column', 'label' => __( 'Layout', 'releasecart' ), 'description' => __( 'Two columns (media beside the buying panel) where there is room, or a single stacked column.', 'releasecart' ), 'control' => 'select', ), ) + TierMetaPresenter::toggleSchema(); } /** * Asset handles for the shared screenshot grid. * * @return array */ public function assets(): array { $assets = parent::assets(); $assets['styles'] = array_merge( $assets['styles'], array( 'releasecart-product-screenshots' ) ); $assets['scripts'] = array_merge( $assets['scripts'], array( 'releasecart-product-screenshots' ) ); return $assets; } /** * Render the editor placeholder when no product resolves. * * @return string Escaped HTML. */ protected function editorPlaceholder(): string { return '
' . '

' . esc_html__( 'Product details will appear here on the product page.', 'releasecart' ) . '

' . '
'; } /** * Resolve an attachment to a URL and alt text. * * @param int $id The attachment id. * @return array{url: string, alt: string}|null */ private function image( int $id ): ?array { if ( $id <= 0 ) { return null; } $url = wp_get_attachment_image_url( $id, 'large' ); if ( ! is_string( $url ) || '' === $url ) { return null; } return array( 'url' => $url, 'alt' => (string) get_post_meta( $id, '_wp_attachment_image_alt', true ), ); } }