id || ! str_contains( $screen->id, AdminSPA::PAGE_SLUG ) ) { return; } try { $container = Plugin::container(); if ( ! $container->has( SettingsStore::class ) ) { return; } $settings = $container->get( SettingsStore::class ); if ( ! $settings instanceof SettingsStore ) { return; } } catch ( \Throwable ) { return; } $secret = (string) $settings->get( 'payments.stripe_secret_key' ); $pub = (string) $settings->get( 'payments.stripe_publishable_key' ); if ( LivePaymentGuard::using_test_keys( $secret, $pub ) ) { printf( '

%s %s

', esc_html__( 'Buildler payments are in test mode.', 'buildler' ), esc_html__( 'Stripe is using test keys. No live charges will be taken until you save live keys.', 'buildler' ) ); return; } if ( LivePaymentGuard::using_live_keys( $secret, $pub ) && ! LivePaymentGuard::allows_live() ) { printf( '

%s %s

', esc_html__( 'Live Stripe keys are stored.', 'buildler' ), esc_html__( 'Live charges stay blocked until BOOKLINE_ALLOW_LIVE_PAYMENTS is defined as true.', 'buildler' ) ); } } /** * Render the dashboard page. */ public static function render_dashboard(): void { if ( ! current_user_can( 'manage_options' ) ) { return; } global $wpdb; $prefix = $wpdb->prefix . 'bookline_'; // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- temporary demo dashboard until Milestone 11. $counts = [ 'services' => self::count_rows( $wpdb, $prefix, 'services', "status = 'active'" ), 'agents' => self::count_rows( $wpdb, $prefix, 'agents', "status = 'active'" ), 'locations' => self::count_rows( $wpdb, $prefix, 'locations', "status = 'active'" ), 'customers' => self::count_rows( $wpdb, $prefix, 'customers', 'deleted_at IS NULL' ), 'bookings' => self::count_rows( $wpdb, $prefix, 'bookings', 'deleted_at IS NULL' ), 'schedules' => self::count_rows( $wpdb, $prefix, 'schedules', '1=1' ), 'durations' => self::count_rows( $wpdb, $prefix, 'service_durations', '1=1' ), ]; // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared -- table names are escaped via esc_sql(). $services = $wpdb->get_results( self::sql( $prefix, 'services', "SELECT id, name, status FROM %s WHERE status = 'active' ORDER BY id ASC LIMIT 10" ), ARRAY_A ); $agents = $wpdb->get_results( self::sql( $prefix, 'agents', "SELECT id, display_name, timezone, status FROM %s WHERE status = 'active' ORDER BY id ASC LIMIT 10" ), ARRAY_A ); // phpcs:enable // phpcs:enable $rest_ns = esc_url_raw( rest_url( 'bookline/v1' ) ); $demo = get_page_by_path( 'book-online' ); $demo_url = $demo instanceof \WP_Post ? get_permalink( $demo ) : ''; echo '
'; echo '

' . esc_html__( 'Buildler', 'buildler' ) . '

'; echo '

' . esc_html__( 'Appointment booking for WordPress. Core engine is live; the full admin SPA arrives in a later milestone.', 'buildler' ) . '

'; echo '

' . esc_html__( 'Catalogue', 'buildler' ) . '

'; echo ''; if ( $demo_url ) { echo '

' . esc_html__( 'Open booking demo page', 'buildler' ) . '

'; } echo '

' . esc_html__( 'Browse REST API (bookline/v1)', 'buildler' ) . '

'; echo '

' . esc_html__( 'Active services', 'buildler' ) . '

'; if ( empty( $services ) ) { echo '

' . esc_html__( 'No active services yet.', 'buildler' ) . '

'; } else { echo ''; foreach ( $services as $row ) { printf( '', esc_html( (string) (int) $row['id'] ), esc_html( (string) $row['name'] ), esc_html( (string) $row['status'] ) ); } echo '
ID' . esc_html__( 'Name', 'buildler' ) . '' . esc_html__( 'Status', 'buildler' ) . '
%s%s%s
'; } echo '

' . esc_html__( 'Active agents', 'buildler' ) . '

'; if ( empty( $agents ) ) { echo '

' . esc_html__( 'No active agents yet.', 'buildler' ) . '

'; } else { echo ''; foreach ( $agents as $row ) { printf( '', esc_html( (string) (int) $row['id'] ), esc_html( (string) $row['display_name'] ), esc_html( (string) $row['timezone'] ) ); } echo '
ID' . esc_html__( 'Name', 'buildler' ) . '' . esc_html__( 'Timezone', 'buildler' ) . '
%s%s%s
'; } echo '
'; } /** * Count rows in a prefixed Bookline table. * * @param \wpdb $wpdb Database. * @param string $prefix Table prefix including bookline_. * @param string $table Table suffix. * @param string $where Static WHERE clause without user input. */ private static function count_rows( \wpdb $wpdb, string $prefix, string $table, string $where ): int { $sql = sprintf( 'SELECT COUNT(*) FROM `%s` WHERE %s', esc_sql( $prefix . $table ), $where ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared -- static dashboard counts with escaped table names. return (int) $wpdb->get_var( $sql ); } /** * Build a SQL string with a single escaped table placeholder. * * @param string $prefix Table prefix including bookline_. * @param string $table Table suffix. * @param string $query Query containing one %s table placeholder. */ private static function sql( string $prefix, string $table, string $query ): string { return sprintf( $query, esc_sql( $prefix . $table ) ); } }