[ __( 'Logo', 'ai-eshop-optimizer' ), __( 'The site\'s logo, and its name when Design shows it beside the logo.', 'ai-eshop-optimizer' ) ], 'search' => [ __( 'Search field', 'ai-eshop-optimizer' ), __( 'The product search as a field. Its text replaces the placeholder; its width fills the space between its neighbours or is a centred field of about 640 px.', 'ai-eshop-optimizer' ) ], 'actions' => [ __( 'Header actions', 'ai-eshop-optimizer' ), __( 'The icons of Design → Header actions, in its order and with its texts (the one place that decides them). With a search field in the header, its search icon is left out.', 'ai-eshop-optimizer' ) ], 'catalogue' => [ __( 'Catalogue button', 'ai-eshop-optimizer' ), __( 'Rooster One\'s navigator, on the pages where it is on. Its text replaces "Catalogue".', 'ai-eshop-optimizer' ) ], 'menu' => [ __( 'Menu', 'ai-eshop-optimizer' ), __( 'The main navigation on computers; phones and tablets open it from the menu button among the actions. As a mega menu it is Rooster One\'s: the navigation menu named "navigation", its panels from that menu\'s submenus and from the family menus listed by their slugs. Without Rooster One, or without that menu, the plain navigation is printed. Phones keep the theme\'s menu unless the mega menu\'s own drawer is chosen; the search, the language and the appearance controls the theme\'s menu holds are then for the phones\' own actions to show.', 'ai-eshop-optimizer' ) ], 'social' => [ __( 'Social icons', 'ai-eshop-optimizer' ), __( 'The logos of the networks given an address, in the row\'s text colour: its own addresses below, or the site\'s (Design, Social links) when it has none.', 'ai-eshop-optimizer' ) ], 'contact' => [ __( 'Contact: phone and opening hours', 'ai-eshop-optimizer' ), __( 'The shop\'s phone with its opening hours, as Rooster One\'s locations know them: open now, or when it opens. Its label replaces the words before the number; its location is the shop\'s main one unless another is chosen. Without Rooster One, or without a phone for the location, nothing is shown. Where the row is short, the icon stays and the words give way.', 'ai-eshop-optimizer' ) ], ]; $out = []; foreach ( HeaderLayout::ITEMS as $item ) { if ( str_starts_with( $item, 'text-' ) ) { $name = [ sprintf( __( 'Text %d', 'ai-eshop-optimizer' ), (int) substr( $item, 5 ) ), __( 'A text in the header links\' look, with an optional link (a web address, tel: or mailto:) and an icon: a phone line, the stores.', 'ai-eshop-optimizer' ) ]; } else { $name = $names[ $item ]; } $out[ $item ] = [ 'label' => $name[0], 'help' => $name[1], 'keys' => HeaderLayout::KEYS[ $item ] ]; } return $out; } public static function choices(): array { $rtl = function_exists( 'is_rtl' ) && is_rtl(); return [ 'rows' => [ 'top' => __( 'Top bar', 'ai-eshop-optimizer' ), 'main' => __( 'Main row', 'ai-eshop-optimizer' ), 'bottom' => __( 'Menu row', 'ai-eshop-optimizer' ), 'off' => __( 'Hidden', 'ai-eshop-optimizer' ), ], 'zones' => [ 'start' => $rtl ? __( 'Right', 'ai-eshop-optimizer' ) : __( 'Left', 'ai-eshop-optimizer' ), 'center' => __( 'Centre', 'ai-eshop-optimizer' ), 'end' => $rtl ? __( 'Left', 'ai-eshop-optimizer' ) : __( 'Right', 'ai-eshop-optimizer' ), ], 'surfaces' => [ 'page' => __( 'The page\'s colours', 'ai-eshop-optimizer' ), 'subtle' => __( 'Subtle', 'ai-eshop-optimizer' ), 'contrast' => __( 'Contrast', 'ai-eshop-optimizer' ), 'accent' => __( 'Accent', 'ai-eshop-optimizer' ), 'accent-2' => __( 'Second accent', 'ai-eshop-optimizer' ), ], 'sizes' => [ 'compact' => __( 'Compact: a bar of about 40 px', 'ai-eshop-optimizer' ), 'regular' => __( 'Regular: the header\'s height', 'ai-eshop-optimizer' ), 'tall' => __( 'Tall', 'ai-eshop-optimizer' ), ], 'widths' => [ 'fill' => __( 'Fills the space between its neighbours', 'ai-eshop-optimizer' ), 'medium' => __( 'Medium, centred (about 640 px)', 'ai-eshop-optimizer' ), ], 'menu_styles' => [ 'plain' => __( 'Plain: the navigation menu', 'ai-eshop-optimizer' ), 'mega' => __( 'Mega menu: Rooster One\'s panels', 'ai-eshop-optimizer' ), ], 'menu_phones' => [ 'overlay' => __( 'On phones: the theme\'s menu', 'ai-eshop-optimizer' ), 'drawer' => __( 'On phones: the mega menu\'s drawer', 'ai-eshop-optimizer' ), ], 'contact_hours' => [ 'inline' => __( 'Opening hours: beside the number', 'ai-eshop-optimizer' ), 'dialog' => __( 'Opening hours: in a window, on a press out of hours', 'ai-eshop-optimizer' ), ], 'social' => [ 'facebook' => 'Facebook', 'instagram' => 'Instagram', 'tiktok' => 'TikTok', 'youtube' => 'YouTube', 'x' => 'X', 'linkedin' => 'LinkedIn', 'pinterest' => 'Pinterest', ], ]; } public static function sanitize( $value ): array { if ( is_array( $value ) && isset( $value['order'] ) ) { $value = self::from_form( $value ); } return HeaderLayout::normalise( $value ); } private static function from_form( array $form ): array { $items = []; foreach ( (array) $form['order'] as $key ) { $item = is_scalar( $key ) ? sanitize_key( (string) $key ) : ''; if ( '' === $item ) { continue; } $entry = [ 'item' => $item ]; foreach ( [ 'row', 'zone', 'text', 'width', 'href', 'icon', 'links', 'style', 'menus', 'phone', 'label', 'location', 'hours' ] as $field ) { if ( is_array( $form[ $field ] ?? null ) && array_key_exists( $item, $form[ $field ] ) ) { $entry[ $field ] = $form[ $field ][ $item ]; } } $items[] = $entry; } return [ 'rows' => is_array( $form['rows'] ?? null ) ? $form['rows'] : [], 'items' => $items ]; } public const ICONS = [ 'phone', 'mail', 'map-pin', 'truck', 'clock', 'headphones', 'gift', 'info', 'user', 'calendar', 'shopping-bag', 'tag', 'star', 'heart' ]; public static function control( string $id, string $name, $value, array $field ): string { $layout = HeaderLayout::normalise( $value ); $plain = ! class_exists( Fields::class ) || ! empty( $field['plain'] ); $choices = self::choices(); $items = self::catalogue(); $out = '
'; $out .= '
' . esc_html__( 'The rows', 'ai-eshop-optimizer' ) . ''; foreach ( HeaderLayout::ROWS as $row ) { $settings = $layout['rows'][ $row ]; $base = $name . '[rows][' . $row . ']'; $label = $choices['rows'][ $row ]; $out .= '

' . esc_html( $label ) . ' ' . self::select( $plain, $base . '[surface]', $id . '-' . $row . '-surface', $choices['surfaces'], $settings['surface'], sprintf( __( '%s: colours', 'ai-eshop-optimizer' ), $label ) ) . ' ' . self::select( $plain, $base . '[size]', $id . '-' . $row . '-size', $choices['sizes'], $settings['size'], sprintf( __( '%s: height', 'ai-eshop-optimizer' ), $label ) ) . ' ' . '

'; } $out .= '
'; $rows = []; foreach ( $layout['items'] as $entry ) { $item = (string) $entry['item']; $label = $items[ $item ]['label']; $control = self::select( $plain, $name . '[row][' . $item . ']', $id . '-row-' . $item, $choices['rows'], (string) $entry['row'], sprintf( __( '%s: row', 'ai-eshop-optimizer' ), $label ) ) . ' ' . self::select( $plain, $name . '[zone][' . $item . ']', $id . '-zone-' . $item, $choices['zones'], (string) $entry['zone'], sprintf( __( '%s: place in the row', 'ai-eshop-optimizer' ), $label ) ); if ( array_key_exists( 'text', $entry ) ) { $max = str_starts_with( $item, 'text-' ) ? HeaderLayout::TEXT_MAX : HeaderLayout::LABEL_MAX; $hint = [ 'search' => __( 'Placeholder', 'ai-eshop-optimizer' ), 'catalogue' => __( 'Catalogue', 'ai-eshop-optimizer' ) ][ $item ] ?? __( 'Text', 'ai-eshop-optimizer' ); $control .= ' ' . self::input( $plain, $name . '[text][' . $item . ']', $id . '-text-' . $item, (string) $entry['text'], $hint, sprintf( __( '%s: text', 'ai-eshop-optimizer' ), $label ), $max ); } if ( array_key_exists( 'width', $entry ) ) { $control .= ' ' . self::select( $plain, $name . '[width][' . $item . ']', $id . '-width-' . $item, $choices['widths'], (string) $entry['width'], sprintf( __( '%s: width', 'ai-eshop-optimizer' ), $label ) ); } if ( array_key_exists( 'style', $entry ) ) { $control .= ' ' . self::select( $plain, $name . '[style][' . $item . ']', $id . '-style-' . $item, $choices['menu_styles'], (string) $entry['style'], sprintf( __( '%s: style', 'ai-eshop-optimizer' ), $label ) ) . ' ' . self::select( $plain, $name . '[phone][' . $item . ']', $id . '-phone-' . $item, $choices['menu_phones'], (string) $entry['phone'], sprintf( __( '%s: on phones', 'ai-eshop-optimizer' ), $label ) ) . ' ' . self::input( $plain, $name . '[menus][' . $item . ']', $id . '-menus-' . $item, (string) $entry['menus'], __( 'Family menus: slugs, comma-separated', 'ai-eshop-optimizer' ), sprintf( __( '%s: family menus', 'ai-eshop-optimizer' ), $label ), 200 ); } if ( array_key_exists( 'href', $entry ) ) { $control .= ' ' . self::input( $plain, $name . '[href][' . $item . ']', $id . '-href-' . $item, (string) $entry['href'], __( 'Link: https://, tel: or mailto:', 'ai-eshop-optimizer' ), sprintf( __( '%s: link', 'ai-eshop-optimizer' ), $label ), 200 ); $icons = [ '' => __( 'No icon', 'ai-eshop-optimizer' ) ]; foreach ( array_unique( array_filter( array_merge( self::ICONS, [ (string) $entry['icon'] ] ) ) ) as $icon ) { $icons[ $icon ] = $icon; } $control .= ' ' . self::select( $plain, $name . '[icon][' . $item . ']', $id . '-icon-' . $item, $icons, (string) $entry['icon'], sprintf( __( '%s: icon', 'ai-eshop-optimizer' ), $label ) ); } if ( array_key_exists( 'label', $entry ) ) { $control .= ' ' . self::input( $plain, $name . '[label][' . $item . ']', $id . '-label-' . $item, (string) $entry['label'], __( 'Label, e.g. Customer service', 'ai-eshop-optimizer' ), sprintf( __( '%s: label', 'ai-eshop-optimizer' ), $label ), HeaderLayout::LABEL_MAX ); } if ( array_key_exists( 'location', $entry ) ) { $where = sprintf( __( '%s: location', 'ai-eshop-optimizer' ), $label ); $places = self::locations( (string) $entry['location'] ); $control .= ' ' . ( $places ? self::select( $plain, $name . '[location][' . $item . ']', $id . '-location-' . $item, $places, (string) $entry['location'], $where ) : self::input( $plain, $name . '[location][' . $item . ']', $id . '-location-' . $item, (string) $entry['location'], __( 'Location: empty for the main one', 'ai-eshop-optimizer' ), $where, 64 ) ); } if ( array_key_exists( 'hours', $entry ) ) { $control .= ' ' . self::select( $plain, $name . '[hours][' . $item . ']', $id . '-hours-' . $item, $choices['contact_hours'], (string) $entry['hours'], sprintf( __( '%s: opening hours', 'ai-eshop-optimizer' ), $label ) ); } $rows[] = [ 'key' => $item, 'label' => $label, 'control' => $control ]; } if ( $plain ) { $out .= ''; foreach ( $rows as $row ) { $out .= ''; } $out .= ''; } else { $out .= Fields::ordered_list( $rows, [ 'id' => $id . '-list', 'label' => __( 'Header items', 'ai-eshop-optimizer' ), 'name' => $name . '[order]', 'default' => HeaderLayout::ITEMS ] ); } $social = (array) ( self::entry( $layout, 'social' )['links'] ?? [] ); $out .= '
' . esc_html__( 'Social icons: their addresses (an empty one is not shown; with none here, the site\'s own are shown: Design, Social links)', 'ai-eshop-optimizer' ) . '

'; foreach ( $choices['social'] as $network => $label ) { $out .= ' ' . self::input( $plain, $name . '[links][social][' . $network . ']', $id . '-social-' . $network, (string) ( $social[ $network ] ?? '' ), 'https://', $label, 200 ) . ' '; } return $out . '

'; } private static function locations( string $current ): array { $list = apply_filters( 'rooster/header/contact_locations', [] ); if ( ! is_array( $list ) || [] === $list ) { return []; } $out = [ '' => __( 'The shop\'s main location', 'ai-eshop-optimizer' ) ]; foreach ( $list as $key => $title ) { $key = sanitize_key( (string) $key ); if ( '' !== $key && is_scalar( $title ) ) { $out[ $key ] = wp_strip_all_tags( (string) $title ); } } if ( '' !== $current && ! isset( $out[ $current ] ) ) { $out[ $current ] = $current; } return $out; } private static function entry( array $layout, string $item ): array { foreach ( $layout['items'] as $entry ) { if ( $item === $entry['item'] ) { return $entry; } } return []; } private static function select( bool $plain, string $name, string $id, array $options, string $value, string $label ): string { if ( ! $plain ) { return Ui::select( $name, $options, $value, [ 'id' => $id, 'aria-label' => $label ] ); } $html = ''; } private static function input( bool $plain, string $name, string $id, string $value, string $placeholder, string $label, int $max ): string { if ( ! $plain ) { return Ui::input( $name, $value, [ 'id' => $id, 'short' => true, 'maxlength' => $max, 'placeholder' => $placeholder, 'aria-label' => $label ] ); } return ''; } }