get_field_value( $field_id ); if ( null === $value || '' === $value ) { return; } echo slbuild_escape( (string) $value, $context ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- slbuild_escape() is the escaping function } /** * Return a section field value, escaped for the given context. * * @param string $field_id * @param string $context * @return string */ function slbuild_get_field( string $field_id, string $context = 'html' ): string { $value = SLBUILD_SectionLoader::instance()->get_field_value( $field_id ); if ( null === $value || '' === $value ) { return ''; } return slbuild_escape( (string) $value, $context ); } /** * Output a richtext field value, passed through wp_kses_post. * Only for richtext fields — not for plain text. * * @param string $field_id */ function slbuild_raw( string $field_id ): void { $value = SLBUILD_SectionLoader::instance()->get_field_value( $field_id ); if ( null === $value || '' === $value ) { return; } echo wp_kses_post( (string) $value ); } /** * Output a block field value inside a block loop, escaped for context. * * @param string $field_id * @param string $context */ function slbuild_block_field( string $field_id, string $context = 'html' ): void { $value = SLBUILD_SectionLoader::instance()->get_block_field_value( $field_id ); if ( null === $value || '' === $value ) { return; } echo slbuild_escape( (string) $value, $context ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- slbuild_escape() is the escaping function } /** * Return a block field value inside a block loop, escaped for context. * * @param string $field_id * @param string $context * @return string */ function slbuild_get_block_field( string $field_id, string $context = 'html' ): string { $value = SLBUILD_SectionLoader::instance()->get_block_field_value( $field_id ); if ( null === $value || '' === $value ) { return ''; } return slbuild_escape( (string) $value, $context ); } /** * Get the blocks array for the current section instance. * Use in a foreach loop inside a section template. * * @return array */ function slbuild_get_blocks(): array { return SLBUILD_SectionLoader::instance()->get_blocks(); } /** * Render all sections for the current page (or given post). * Call this from the theme template where sections should appear. * * @param int|null $post_id */ function slbuild_render_page( $post_id = null ): void { SLBUILD_SectionLoader::instance()->render_page( $post_id ); } /** * Render all section instances for a global region (header, footer, etc.). * * @param string $region Region slug — matches the saved wp_options key, e.g. 'header', 'footer'. */ function slbuild_render_global_region( string $region ): void { SLBUILD_SectionLoader::instance()->render_global_region( $region ); } /** * Read a single field value from a global region's saved instance data. * * Use this in functions.php, WC template overrides, or any PHP that needs * a global setting without rendering a section — e.g. free shipping threshold, * font choice, shop column count. * * @param string $region Region slug, e.g. 'theme-settings'. * @param string $field_id Field ID from the section schema, e.g. 'free_shipping_threshold'. * @param mixed $default Fallback value if the field is not saved. * @return mixed The saved field value, or $default. */ function slbuild_global_setting( string $region, string $field_id, $default = '' ) { static $cache = array(); if ( ! isset( $cache[ $region ] ) ) { $store = new SLBUILD_DataStore(); $raw = $store->get_global( $region ); if ( is_array( $raw ) && ! empty( $raw['layout'] ) && ! empty( $raw['instances'] ) ) { // Grab settings from the first instance in the layout. $first = $raw['layout'][0] ?? null; $inst_id = $first['instance'] ?? ''; $cache[ $region ] = $inst_id && isset( $raw['instances'][ $inst_id ]['settings'] ) ? $raw['instances'][ $inst_id ]['settings'] : array(); } else { $cache[ $region ] = array(); } } $val = $cache[ $region ][ $field_id ] ?? null; return ( $val !== null && $val !== '' ) ? $val : $default; } /** * Saved colour overrides per scheme, from the theme-settings global region. * Only valid hex colours survive; anything else is dropped. * * @return array Map of scheme => key => hex colour. */ function slbuild_scheme_overrides(): array { static $overrides = null; if ( null !== $overrides ) { return $overrides; } $overrides = array(); if ( ! function_exists( 'slbuild_global_setting' ) ) { return $overrides; } foreach ( array( 'white', 'light', 'dark', 'primary', 'ink', 'custom' ) as $scheme ) { foreach ( array( 'bg', 'text', 'sub', 'eyebrow', 'btn', 'btn2', 'card', 'border' ) as $key ) { $hex = sanitize_hex_color( (string) slbuild_global_setting( 'theme-settings', $scheme . '_' . $key, '' ) ); if ( $hex ) { $overrides[ $scheme ][ $key ] = $hex; } } } return $overrides; } /** * CSS for the saved colour overrides. Each override becomes one fixed class, * slbuild-ov-{scheme}-{key}, so the stylesheet never depends on runtime-built * utility class names. * * @return string */ function slbuild_scheme_override_css(): string { $css = ''; foreach ( slbuild_scheme_overrides() as $scheme => $keys ) { foreach ( $keys as $key => $hex ) { $sel = '.slbuild-ov-' . $scheme . '-' . $key; if ( 'bg' === $key || 'card' === $key ) { $css .= $sel . '{background-color:' . $hex . '}'; } elseif ( 'border' === $key ) { $css .= $sel . '{border-color:' . $hex . '}'; } elseif ( 'btn' === $key ) { $css .= $sel . '{background-color:' . $hex . ';color:' . slbuild_contrast_color( $hex ) . '}' . $sel . ':hover{filter:brightness(.92)}'; } elseif ( 'btn2' === $key ) { $css .= $sel . '{border:1px solid ' . $hex . ';color:' . $hex . '}' . $sel . ':hover{background-color:' . $hex . ';color:' . slbuild_contrast_color( $hex ) . '}'; } else { $css .= $sel . '{color:' . $hex . '}'; } } } return $css; } /** * Black or white, whichever reads better on the given hex background. * * @param string $hex A value already checked by sanitize_hex_color(). * @return string */ function slbuild_contrast_color( string $hex ): string { $hex = ltrim( $hex, '#' ); if ( 3 === strlen( $hex ) ) { $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2]; } $luma = ( 0.299 * hexdec( substr( $hex, 0, 2 ) ) + 0.587 * hexdec( substr( $hex, 2, 2 ) ) + 0.114 * hexdec( substr( $hex, 4, 2 ) ) ) / 255; return $luma > 0.6 ? '#1d1d1f' : '#ffffff'; } /** * Return Tailwind class strings for a named color scheme. * Keys: bg, text, sub, eyebrow, btn, btn2, card, border. * * @param string $scheme One of: white, light, dark, primary, ink. * @return array */ function slbuild_get_color_scheme( string $scheme = 'white' ): array { static $defaults = null; static $saved = null; if ( null === $defaults ) { $defaults = array( 'white' => array( 'bg' => 'bg-white', 'text' => 'text-[#1d1d1f]', 'sub' => 'text-[#86868b]', 'eyebrow' => 'text-[#0071e3]', 'btn' => 'bg-[#0071e3] hover:bg-[#0077ed] text-white', 'btn2' => 'border border-[#0071e3] text-[#0071e3] hover:bg-[#0071e3] hover:text-white', 'card' => 'bg-[#f5f5f7]', 'border' => 'border-[#d2d2d7]', ), 'light' => array( 'bg' => 'bg-[#f5f5f7]', 'text' => 'text-[#1d1d1f]', 'sub' => 'text-[#86868b]', 'eyebrow' => 'text-[#0071e3]', 'btn' => 'bg-[#0071e3] hover:bg-[#0077ed] text-white', 'btn2' => 'border border-[#0071e3] text-[#0071e3] hover:bg-[#0071e3] hover:text-white', 'card' => 'bg-white', 'border' => 'border-[#d2d2d7]', ), 'dark' => array( 'bg' => 'bg-[#1d1d1f]', 'text' => 'text-white', 'sub' => 'text-[#ababab]', 'eyebrow' => 'text-[#ababab]', 'btn' => 'bg-white hover:bg-[#f5f5f7] text-[#1d1d1f]', 'btn2' => 'border border-white text-white hover:bg-white hover:text-[#1d1d1f]', 'card' => 'bg-[#2c2c2e]', 'border' => 'border-[#3a3a3c]', ), 'primary' => array( 'bg' => 'bg-[#0071e3]', 'text' => 'text-white', 'sub' => 'text-white/70', 'eyebrow' => 'text-white/70', 'btn' => 'bg-white hover:bg-[#f5f5f7] text-[#0071e3]', 'btn2' => 'border border-white text-white hover:bg-white hover:text-[#0071e3]', 'card' => 'bg-[#0064cc]', 'border' => 'border-white/20', ), 'ink' => array( 'bg' => 'bg-[#2c2c2e]', 'text' => 'text-white', 'sub' => 'text-[#ababab]', 'eyebrow' => 'text-[#ababab]', 'btn' => 'bg-[#0071e3] hover:bg-[#0077ed] text-white', 'btn2' => 'border border-white text-white hover:bg-white hover:text-[#1d1d1f]', 'card' => 'bg-[#3a3a3c]', 'border' => 'border-[#48484a]', ), 'custom' => array( 'bg' => 'bg-white', 'text' => 'text-[#1d1d1f]', 'sub' => 'text-[#86868b]', 'eyebrow' => 'text-[#0071e3]', 'btn' => 'bg-[#0071e3] hover:bg-[#0077ed] text-white', 'btn2' => 'border border-[#0071e3] text-[#0071e3] hover:bg-[#0071e3] hover:text-white', 'card' => 'bg-[#f5f5f7]', 'border' => 'border-[#d2d2d7]', ), ); } // Merge saved scheme overrides from the theme-settings global region. // An override is a plain hex colour; it is styled by a generated rule (see // slbuild_scheme_override_css()), so only the class name is needed here. if ( null === $saved ) { $saved = array(); foreach ( slbuild_scheme_overrides() as $s => $keys ) { foreach ( array_keys( $keys ) as $key ) { $saved[ $s ][ $key ] = 'slbuild-ov-' . $s . '-' . $key; } } } $base = $defaults[ $scheme ] ?? $defaults['white']; return isset( $saved[ $scheme ] ) ? array_merge( $base, $saved[ $scheme ] ) : $base; } /** * Read a schema-level metadata key for the currently-rendering section. * Reads top-level keys like heading_level, above_fold — not instance field values. * * @param string $key Schema key to read. * @return mixed|null */ function slbuild_meta( string $key ) { $schema = SLBUILD_SectionLoader::instance()->get_schema(); return $schema[ $key ] ?? null; } /** * Output a complete tag for an image field. * Defaults to loading="lazy"; switches to loading="eager" when the section * schema declares "above_fold": true (hero / first-visible sections). * Width and height are added when the attachment ID can be resolved from the URL. * * @param string $field_id Image field id. * @param string $alt_field_id Companion alt-text field id (empty = no alt). * @param string $loading Force 'lazy' or 'eager'; empty = auto from schema. */ function slbuild_image( string $field_id, string $alt_field_id = '', string $loading = '' ): void { $loader = SLBUILD_SectionLoader::instance(); $url = $loader->get_field_value( $field_id ); if ( ! $url ) { return; } $url = (string) $url; if ( '' === $loading ) { $schema = $loader->get_schema(); $loading = ! empty( $schema['above_fold'] ) ? 'eager' : 'lazy'; } $alt = ''; if ( '' !== $alt_field_id ) { $alt_val = $loader->get_field_value( $alt_field_id ); $alt = $alt_val ? (string) $alt_val : ''; } $width = 0; $height = 0; $attach_id = attachment_url_to_postid( $url ); if ( $attach_id ) { $src = wp_get_attachment_image_src( $attach_id, 'full' ); if ( $src ) { $width = (int) $src[1]; $height = (int) $src[2]; } } $attr = 'src="' . esc_url( $url ) . '"'; $attr .= ' alt="' . esc_attr( $alt ) . '"'; if ( $width ) $attr .= ' width="' . $width . '"'; if ( $height ) $attr .= ' height="' . $height . '"'; $attr .= ' loading="' . esc_attr( $loading ) . '"'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo ''; } /** * Emit schema.org Product JSON-LD for a single product. * Call once per product inside a product-grid section template. * * @param WC_Product $product */ function slbuild_product_jsonld( $product ): void { if ( ! ( $product instanceof WC_Product ) ) { return; } $data = array( '@context' => 'https://schema.org', '@type' => 'Product', 'name' => $product->get_name(), 'image' => wp_get_attachment_url( $product->get_image_id() ) ?: '', 'url' => get_permalink( $product->get_id() ), 'offers' => array( '@type' => 'Offer', 'price' => $product->get_price(), 'priceCurrency' => get_woocommerce_currency(), 'availability' => $product->is_in_stock() ? 'https://schema.org/InStock' : 'https://schema.org/OutOfStock', ), ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo '' . "\n"; } /** * Escape a value for the given output context. * * @param string $value * @param string $context 'html', 'attr', 'url', 'js', 'css_var' * @return string */ function slbuild_escape( string $value, string $context ): string { switch ( $context ) { case 'attr': return esc_attr( $value ); case 'url': return esc_url( $value ); case 'js': return esc_js( $value ); case 'css_var': // Safe for use inside a CSS custom property value — strips anything // that could break out of the declaration or inject a script tag. return preg_replace( '/[^a-zA-Z0-9#%.\-_()\/ ]/', '', $value ); case 'html': default: return esc_html( $value ); } }