account_id(); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only local pagination, strictly bounded in the L3 adapter. $page = isset( $_GET['service_page'] ) && is_scalar( $_GET['service_page'] ) ? absint( $_GET['service_page'] ) : 1; $catalog = $api->catalog( $account_id, max( 1, $page ), 20 ); echo '

' . esc_html__( 'Services and embeds', 'waitlistize' ) . '

' . esc_html__( 'Display settings apply only to this embed. Service configuration stays in Waitlistize.', 'waitlistize' ) . '

'; if ( is_wp_error( $catalog ) ) { echo '

' . esc_html( $catalog->get_error_message() ) . '

'; return; } $this->form( 'services', array_replace( Attributes::defaults( 'services' ), array_intersect_key( DisplayDefaults::values(), Attributes::defaults( 'services' ) ) ), $account_id, __( 'Service catalog', 'waitlistize' ) ); foreach ( $catalog['data'] as $service ) { $attributes = array_replace( Attributes::defaults( 'booking' ), array_intersect_key( DisplayDefaults::values(), Attributes::defaults( 'booking' ) ) ); $attributes['serviceId'] = $service['id']; $this->form( 'booking', $attributes, $account_id, $service['name'], $service ); } if ( ! $catalog['data'] ) { echo '

' . esc_html__( 'No services to display.', 'waitlistize' ) . '

'; } for ( $direction = -1; $direction <= 1; $direction += 2 ) { $target = $page + $direction; if ( $target >= 1 && $target <= $catalog['meta']['last_page'] ) { echo '' . esc_html( $direction < 0 ? __( 'Previous', 'waitlistize' ) : __( 'Next', 'waitlistize' ) ) . ' '; } } $this->placements(); echo ''; } private function form( string $kind, array $attributes, int $account_id, string $title, array $service = array() ): void { $attributes['connectionAccountId'] = $account_id; $this->initial = $attributes; echo '

' . esc_html( $title ) . '

'; if ( $service ) { echo '

' . esc_html( $service['description'] ) . '

' . esc_html( $service['type'] . ' · ' . $service['timezone'] . ' · ' . trim( $service['public_price_amount'] . ' ' . $service['public_price_currency'] ) ) . '

'; } wp_nonce_field( 'waitlistize_embed_draft', '_waitlistize_nonce' ); echo ''; if ( 'booking' === $kind ) { echo ''; } echo '
' . esc_html__( 'Display settings', 'waitlistize' ) . ''; $this->select( 'locale', __( 'Language', 'waitlistize' ), array( 'auto' => __( 'Account default', 'waitlistize' ), 'hu' => 'Magyar', 'en' => 'English', 'de' => 'Deutsch', ) ); $this->select( 'style_source', __( 'Appearance', 'waitlistize' ), array( 'inherit' => __( 'WordPress theme', 'waitlistize' ), 'service' => __( 'Service accent color', 'waitlistize' ), ) ); if ( 'booking' === $kind ) { $this->select( 'resource_selector', __( 'Resource selector (display only)', 'waitlistize' ), array( 'hidden' => __( 'Hidden (backend default resource)', 'waitlistize' ), 'optional' => __( 'Optional', 'waitlistize' ), ) ); } else { $this->select( 'type', __( 'Service type', 'waitlistize' ), array( 'all' => __( 'All', 'waitlistize' ), 'single' => __( 'Single', 'waitlistize' ), 'group' => __( 'Group', 'waitlistize' ), ) ); $this->select( 'layout', __( 'Layout', 'waitlistize' ), array( 'list' => __( 'List', 'waitlistize' ), 'grid' => __( 'Grid', 'waitlistize' ), ) ); echo ''; } foreach ( array( 'show_title' => __( 'Show title', 'waitlistize' ), 'show_description' => __( 'Show description', 'waitlistize' ), 'show_price' => __( 'Show price', 'waitlistize' ), ) as $key => $label ) { if ( 'show_title' === $key && 'services' === $kind ) { continue; } $this->select( $key, $label, array( 'true' => __( 'Yes', 'waitlistize' ), 'false' => __( 'No', 'waitlistize' ), ) ); } echo '
'; if ( current_user_can( 'edit_pages' ) ) { echo '

'; } echo '
'; } private function select( string $name, string $label, array $options ): void { echo ''; } /** Service IDs/attributes remain assertions; request scope is always the token account. */ public function create_draft( array $input ): int|WP_Error { if ( ! current_user_can( 'manage_options' ) || ! current_user_can( 'edit_pages' ) ) { return Services::error( 'rest_forbidden', 403 ); } $kind = $input['kind'] ?? ''; unset( $input['kind'] ); if ( ! in_array( $kind, array( 'booking', 'services' ), true ) ) { return Services::error( 'embed_parameters', 400 ); } $parsed = Attributes::shortcode( $input ); if ( is_wp_error( $parsed ) || $kind !== $parsed['kind'] ) { return Services::error( 'embed_parameters', 400 ); } $attributes = $parsed['attributes']; $api = new Services(); if ( ! $attributes['connectionAccountId'] || $api->account_id() !== $attributes['connectionAccountId'] ) { return Services::error( 'embed_binding', 409 ); } $title = __( 'Waitlistize services', 'waitlistize' ); if ( 'booking' === $kind ) { $service = $api->detail( $attributes['connectionAccountId'], $attributes['serviceId'], $attributes['locale'] ); if ( is_wp_error( $service ) ) { return $service; } $title = wp_strip_all_tags( $service['name'] ); } return wp_insert_post( wp_slash( array( 'post_type' => 'page', 'post_status' => 'draft', 'post_title' => $title, 'post_content' => serialize_block( array( 'blockName' => 'waitlistize/' . $kind, 'attrs' => $attributes, 'innerBlocks' => array(), 'innerHTML' => '', 'innerContent' => array(), ) ), ) ), true ); } public function draft(): void { if ( ! current_user_can( 'manage_options' ) || ! current_user_can( 'edit_pages' ) ) { wp_die( esc_html__( 'Administrator permission is required.', 'waitlistize' ), '', array( 'response' => 403 ) ); } check_admin_referer( 'waitlistize_embed_draft', '_waitlistize_nonce' ); $input = wp_unslash( $_POST ); unset( $input['action'], $input['_waitlistize_nonce'], $input['_wp_http_referer'] ); $id = $this->create_draft( $input ); if ( is_wp_error( $id ) ) { wp_die( esc_html( $id->get_error_message() ), '', array( 'response' => 400 ) ); } wp_safe_redirect( get_edit_post_link( $id, 'raw' ) ); exit; } private function placements(): void { echo '

' . esc_html__( 'Known placements', 'waitlistize' ) . '

' . esc_html__( 'This limited search checks up to 200 stored posts and pages containing Waitlistize markup. Reusable blocks, templates, widgets, custom fields and other indirect embeds may be missing. This is not a complete inventory.', 'waitlistize' ) . '

'; // phpcs:disable WordPress.WP.PostsPerPage -- Bounded, explicitly limited local placement inventory. $query = new \WP_Query( array( 'post_type' => array( 'post', 'page' ), 'post_status' => array( 'publish', 'draft', 'private', 'pending', 'future' ), 's' => 'waitlistize', 'posts_per_page' => 200, 'no_found_rows' => true, ) ); // phpcs:enable WordPress.WP.PostsPerPage echo ''; } }