brand_properties(); if ( '' !== $brand ) { wp_add_inline_style( self::HANDLE, $brand ); } // Front-end form submitter. Registered (not enqueued) here; FrontendForm // pulls it in only on pages that actually render a form. wp_register_script( self::FORM_HANDLE, FXLC_URL . 'assets/js/form.js', [], FXLC_VERSION, true ); wp_localize_script( self::FORM_HANDLE, 'fxlcForm', FrontendForm::script_data() ); $config = []; foreach ( $this->block_dirs() as $dir ) { register_block_type( $dir ); $meta = $this->read_meta( $dir . '/block.json' ); if ( null !== $meta ) { $config[] = $meta; } } wp_localize_script( self::HANDLE, 'fxlcBlocks', [ 'blocks' => $config, 'lists' => [ 'forms' => $this->form_options(), 'heroVariants' => [ [ 'label' => __( 'Full: the opening of the home page', 'flexa-local-services' ), 'value' => 'full', ], [ 'label' => __( 'Compact: the opening of an inner page', 'flexa-local-services' ), 'value' => 'compact', ], ], 'serviceScopes' => [ [ 'label' => __( 'Any service', 'flexa-local-services' ), 'value' => '', ], [ 'label' => __( 'Only the service being viewed', 'flexa-local-services' ), 'value' => 'current', ], ], 'areaScopes' => [ [ 'label' => __( 'Any area', 'flexa-local-services' ), 'value' => '', ], [ 'label' => __( 'Only the area being viewed', 'flexa-local-services' ), 'value' => 'current', ], ], 'areaLayouts' => [ [ 'label' => __( 'Cards', 'flexa-local-services' ), 'value' => 'cards', ], [ 'label' => __( 'List', 'flexa-local-services' ), 'value' => 'list', ], [ 'label' => __( 'Compact', 'flexa-local-services' ), 'value' => 'compact', ], ], ], ] ); } /** * Options for the form picker in the editor. Loaded only in the admin: a * visitor's page has no business querying the forms table. * * @return list */ private function form_options(): array { if ( ! is_admin() ) { return []; } $options = [ [ 'label' => __( 'Select a form', 'flexa-local-services' ), 'value' => 0, ], ]; foreach ( ( new FormRepository() )->all( 1, 100 ) as $form ) { $options[] = [ 'label' => '' !== $form->name ? $form->name : sprintf( /* translators: %d: form id. */ __( 'Form #%d', 'flexa-local-services' ), $form->id ), 'value' => $form->id, ]; } return $options; } /** * Add the "Flexa Local" block category. * * @param array> $categories * @return array> */ public function category( array $categories ): array { array_unshift( $categories, [ 'slug' => 'flexa-local', 'title' => __( 'Flexa Local', 'flexa-local-services' ), 'icon' => null, ] ); return $categories; } /** * The two brand colors from Settings, pushed into the custom properties the * block stylesheet already reads. Without this the color pickers would be a * setting that changes nothing on the front end. * * The text colour on a primary button comes with it: dark on a light brand * colour, white on a dark one. CSS cannot pick between the two yet, and a * fixed choice fails one half of the colours an owner might choose. */ private function brand_properties(): string { /** @var array $colors */ $colors = (array) Settings::get( 'colors', [] ); $primary = (string) sanitize_hex_color( (string) ( $colors['primary'] ?? '' ) ); $secondary = (string) sanitize_hex_color( (string) ( $colors['secondary'] ?? '' ) ); $rules = []; if ( '' !== $primary ) { $rules[] = '--fxlc-primary:' . $primary . ';'; $rules[] = '--fxlc-on-primary:' . self::text_on( $primary ) . ';'; } if ( '' !== $secondary ) { $rules[] = '--fxlc-accent:' . $secondary . ';'; } return [] === $rules ? '' : ':root{' . implode( '', $rules ) . '}'; } /** * White or near-black, whichever reads better on $hex (WCAG contrast). */ private static function text_on( string $hex ): string { $dark = '#1c1917'; $hex = ltrim( $hex, '#' ); if ( 3 === strlen( $hex ) ) { $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2]; } $luminance = static function ( string $rgb ): float { $channels = []; foreach ( str_split( $rgb, 2 ) as $pair ) { $c = hexdec( $pair ) / 255; $channels[] = $c <= 0.03928 ? $c / 12.92 : ( ( $c + 0.055 ) / 1.055 ) ** 2.4; } return 0.2126 * $channels[0] + 0.7152 * $channels[1] + 0.0722 * $channels[2]; }; $bg = $luminance( $hex ); $on_white = 1.05 / ( $bg + 0.05 ); $on_dark = ( $bg + 0.05 ) / ( $luminance( ltrim( $dark, '#' ) ) + 0.05 ); return $on_white >= $on_dark ? '#ffffff' : $dark; } /** * @return list */ private function block_dirs(): array { $dirs = glob( FXLC_PATH . 'app/Blocks/*', GLOB_ONLYDIR ); if ( false === $dirs ) { return []; } $out = []; foreach ( $dirs as $dir ) { if ( is_readable( $dir . '/block.json' ) ) { $out[] = $dir; } } return $out; } /** * Pull the editor config (name/title/icon/attributes/controls) from block.json. * * @return array|null */ private function read_meta( string $file ): ?array { $json = wp_json_file_decode( $file, [ 'associative' => true ] ); if ( ! is_array( $json ) || ! isset( $json['name'] ) ) { return null; } $fxlc = is_array( $json['fxlc'] ?? null ) ? $json['fxlc'] : []; return [ 'name' => (string) $json['name'], 'title' => (string) ( $json['title'] ?? $json['name'] ), 'category' => (string) ( $json['category'] ?? 'flexa-local' ), 'icon' => (string) ( $json['icon'] ?? 'location-alt' ), 'attributes' => is_array( $json['attributes'] ?? null ) ? $json['attributes'] : [], 'controls' => is_array( $fxlc['controls'] ?? null ) ? $fxlc['controls'] : [], // A line at the top of the panel, for a block whose content comes // from somewhere other than its own settings. 'help' => is_string( $fxlc['help'] ?? null ) ? $fxlc['help'] : '', ]; } }