Booking * Widget (buildWidgetEmbedCode in internal/handlers/admin_settings_booking_widget.go): * a div carrying data-fullclass-booking and the studio's public key, and the * widget script served by the app. The plugin adds nothing the widget does not * already read; the optional category and view are attributes the same widget * script documents for that div. * * @package FullClassBooking */ if ( ! defined( 'ABSPATH' ) ) { exit; } /** * Option name holding the studio's public booking widget key. */ const FULLCLASS_BOOKING_OPTION_KEY = 'fullclass_booking_api_key'; /** * Where the widget script and API live. * * Production by default. A developer testing against a local FullClass app can * define FULLCLASS_BOOKING_APP_URL in wp-config.php (for example * http://localhost:8080). Studios never need it. * * @return string App base URL with no trailing slash. */ function fullclass_booking_app_url() { if ( defined( 'FULLCLASS_BOOKING_APP_URL' ) && is_string( FULLCLASS_BOOKING_APP_URL ) && '' !== FULLCLASS_BOOKING_APP_URL ) { return untrailingslashit( esc_url_raw( FULLCLASS_BOOKING_APP_URL ) ); } return 'https://app.fullclass.io'; } /** * Pull a public key out of whatever the studio pasted. * * Accepts the bare key or the whole snippet copied from Settings > Booking * Widget, since copying the snippet is what that page invites. Only a public * key is ever accepted: a secret key (sk_) must never be printed into a web * page, so it is refused rather than stored. * * @param string $raw Pasted text. * @return string|WP_Error The key, '' for an empty paste, or an error. */ function fullclass_booking_parse_key( $raw ) { $raw = trim( (string) $raw ); if ( '' === $raw ) { return ''; } if ( preg_match( '/\bsk_[a-f0-9]{16,}/i', $raw ) ) { return new WP_Error( 'fullclass_secret_key', __( 'That is a secret API key. Never put it on a website. Copy the code from Settings > Booking Widget in FullClass instead.', 'fullclass-booking' ) ); } if ( preg_match( '/\bpk_[a-f0-9]{64}\b/', $raw, $m ) ) { return $m[0]; } return new WP_Error( 'fullclass_bad_key', __( 'No booking widget key found. Paste the code from Settings > Booking Widget in FullClass, or the key on its own (it starts with pk_).', 'fullclass-booking' ) ); } /** * Register the widget script. It is only enqueued on pages that render a widget. */ function fullclass_booking_register_script() { wp_register_script( 'fullclass-booking-widget', fullclass_booking_app_url() . '/sdk/v1/booking.min.js', array(), null, // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- the service versions its own script; a ?ver= would only defeat its cache. true ); } add_action( 'wp_enqueue_scripts', 'fullclass_booking_register_script' ); /** * Render the widget markup. * * @param array $args { * Optional embed settings. * * @type string $category A FullClass category ID (cat_...) to show only that category. * @type string $view "list" or "calendar"; anything else follows the studio's setting. * } * @return string HTML. */ function fullclass_booking_render( $args = array() ) { $key = (string) get_option( FULLCLASS_BOOKING_OPTION_KEY, '' ); if ( '' === $key ) { if ( current_user_can( 'manage_options' ) ) { return '
' . sprintf( /* translators: %s: link to the plugin settings page. */ esc_html__( 'The FullClass booking widget needs your key. Add it in %s. Only site admins see this message.', 'fullclass-booking' ), '' . esc_html__( 'Settings > FullClass Booking', 'fullclass-booking' ) . '' ) . '
'; } return ''; } $attrs = ' data-fullclass-booking data-fullclass-apikey="' . esc_attr( $key ) . '"'; // Only a developer override needs this. Production omits it and the widget // uses its own default, exactly like the snippet in FullClass. if ( defined( 'FULLCLASS_BOOKING_APP_URL' ) ) { $attrs .= ' data-fullclass-api-base="' . esc_attr( fullclass_booking_app_url() . '/v1' ) . '"'; } $category = isset( $args['category'] ) ? fullclass_booking_sanitize_category( $args['category'] ) : ''; if ( '' !== $category ) { $attrs .= ' data-fullclass-category="' . esc_attr( $category ) . '"'; } $view = isset( $args['view'] ) ? fullclass_booking_sanitize_view( $args['view'] ) : ''; if ( '' !== $view ) { $attrs .= ' data-fullclass-view="' . esc_attr( $view ) . '"'; } if ( ! wp_script_is( 'fullclass-booking-widget', 'registered' ) ) { fullclass_booking_register_script(); } wp_enqueue_script( 'fullclass-booking-widget' ); return ''; } /** * Keep a category ID to an ID's characters. FullClass issues cat_ plus a ULID, * but the value lands in an HTML attribute, so the safe charset is the rule * rather than the exact format. * * @param mixed $value Raw value. * @return string Sanitized ID or ''. */ function fullclass_booking_sanitize_category( $value ) { $value = trim( (string) $value ); return preg_match( '/^[A-Za-z0-9_-]{1,64}$/', $value ) ? $value : ''; } /** * Keep the view to the two values the widget understands. * * @param mixed $value Raw value. * @return string "list", "calendar" or ''. */ function fullclass_booking_sanitize_view( $value ) { $value = strtolower( trim( (string) $value ) ); return in_array( $value, array( 'list', 'calendar' ), true ) ? $value : ''; } /** * [fullclass_booking category="cat_..." view="calendar"] * * @param array|string $atts Shortcode attributes. * @return string HTML. */ function fullclass_booking_shortcode( $atts ) { $atts = shortcode_atts( array( 'category' => '', 'view' => '', ), $atts, 'fullclass_booking' ); return fullclass_booking_render( $atts ); } add_shortcode( 'fullclass_booking', 'fullclass_booking_shortcode' );