| @@ -1,14 +1,8 @@ | ||
| 1 | 1 | <?php |
| 2 | 2 | |
| 3 | 3 | namespace FluentCart\App\Services\Theme; |
| 4 | 4 | |
| 5 | -use FluentCart\App\Services\Theme\Readers\AstraSettingsReader; | |
| 6 | -use FluentCart\App\Services\Theme\Readers\BlocksySettingsReader; | |
| 7 | -use FluentCart\App\Services\Theme\Readers\BricksSettingsReader; | |
| 8 | -use FluentCart\App\Services\Theme\Readers\DiviSettingsReader; | |
| 9 | -use FluentCart\App\Services\Theme\Readers\GeneratePressSettingsReader; | |
| 10 | -use FluentCart\App\Services\Theme\Readers\KadenceSettingsReader; | |
| 11 | 5 | use FluentCart\Framework\Support\Arr; |
| 12 | 6 | |
| 13 | 7 | /** |
| 14 | 8 | * Reads the active theme's colour palette and resolves it into the semantic |
| @@ -28,10 +22,9 @@ | ||
| 28 | 22 | * |
| 29 | 23 | * These are the slugs themes agree on — `base`/`contrast` come from Twenty |
| 30 | 24 | * Twenty-Four and the themes that copied it, `primary`/`accent` from the |
| 31 | 25 | * classic-adjacent ones, and GeneratePress publishes the same names as |
| 32 | - * references its own stylesheets resolve (measured through its Global | |
| 33 | - * Colors; see vendorValues()). | |
| 26 | + * references its own stylesheets resolve. | |
| 34 | 27 | * |
| 35 | 28 | * Three vendors are supported by name — the ones popular enough to be worth |
| 36 | 29 | * carrying, each mapping written down from the theme's own sources rather |
| 37 | 30 | * than inferred from the numbering: |
| @@ -88,50 +81,8 @@ | ||
| 88 | 81 | */ |
| 89 | 82 | protected static $cachedVendorValues = null; |
| 90 | 83 | |
| 91 | 84 | /** |
| 92 | - * Request-level cache for the roles the theme's own settings state. | |
| 93 | - * | |
| 94 | - * @var array|null | |
| 95 | - */ | |
| 96 | - protected static $cachedSettingsRoles = null; | |
| 97 | - | |
| 98 | - /** | |
| 99 | - * Theme settings readers, keyed by the theme name passed to the | |
| 100 | - * `fluent_cart/theme/settings_roles` filter. The first that applies wins. | |
| 101 | - * | |
| 102 | - * Only one theme runs on a site, so the order only matters when several | |
| 103 | - * vendors' functions are loaded at once (the test stubs). The stricter | |
| 104 | - * checks go first: Blocksy's and Kadence's each need a live instance from | |
| 105 | - * the vendor's API, Divi's needs the `$shortname` global to name Divi, and | |
| 106 | - * Bricks' needs its running `Bricks\Theme` singleton, and GeneratePress's | |
| 107 | - * needs its dynamic-CSS printer hooked on `wp_enqueue_scripts`, none of | |
| 108 | - * which a loaded-but-idle stub provides, while Astra's is a bare | |
| 109 | - * `function_exists()` that stays true once its stub is loaded — so Astra | |
| 110 | - * is asked last. Among the strict ones the order is arbitrary. | |
| 111 | - * | |
| 112 | - * @var array | |
| 113 | - */ | |
| 114 | - protected static $settingsReaders = [ | |
| 115 | - 'blocksy' => BlocksySettingsReader::class, | |
| 116 | - 'kadence' => KadenceSettingsReader::class, | |
| 117 | - 'divi' => DiviSettingsReader::class, | |
| 118 | - 'bricks' => BricksSettingsReader::class, | |
| 119 | - 'generatepress' => GeneratePressSettingsReader::class, | |
| 120 | - 'astra' => AstraSettingsReader::class, | |
| 121 | - ]; | |
| 122 | - | |
| 123 | - /** | |
| 124 | - * The roles a theme settings reader may state. | |
| 125 | - * | |
| 126 | - * @var array | |
| 127 | - */ | |
| 128 | - protected static $settingsRoleKeys = [ | |
| 129 | - 'surface', 'text', 'accent', 'border', | |
| 130 | - 'button_bg', 'button_text', 'button_hover_bg', 'button_hover_text', | |
| 131 | - ]; | |
| 132 | - | |
| 133 | - /** | |
| 134 | 85 | * Drop the request-level caches. |
| 135 | 86 | * |
| 136 | 87 | * Reading theme.json and resolving eleven roles is repeated work within a |
| 137 | 88 | * request, so both are cached. Anything that changes what those reads would |
| @@ -144,9 +95,8 @@ | ||
| 144 | 95 | { |
| 145 | 96 | self::$cachedPalette = null; |
| 146 | 97 | self::$cachedRoles = null; |
| 147 | 98 | self::$cachedVendorValues = null; |
| 148 | - self::$cachedSettingsRoles = null; | |
| 149 | 99 | } |
| 150 | 100 | |
| 151 | 101 | /** |
| 152 | 102 | * The active theme's colour palette, normalised to hex. |
| @@ -311,19 +261,10 @@ | ||
| 311 | 261 | * option being read. Astra prints `--ast-global-color-N` from |
| 312 | 262 | * `astra_get_option('global-color-palette')` (its |
| 313 | 263 | * `generate_global_palette_style()`), and Kadence prints |
| 314 | 264 | * `--global-paletteN` from `kadence()->palette_option('paletteN')` (its |
| 315 | - * styles component; see KadenceSettingsReader::paletteValues()). | |
| 316 | - * Blocksy's editor palette already ships with hex fallbacks, but its Customizer settings point at the bare | |
| 317 | - * `--theme-palette-color-N`, so its palette is read too, from | |
| 318 | - * `blocksy_manager()->colors->get_color_palette()` (the list it prints | |
| 319 | - * the properties from; see BlocksySettingsReader::paletteValues()). | |
| 320 | - * Bricks prints `--bricks-color-{id}` from its colour palette (or the | |
| 321 | - * property a palette colour's raw value names; see | |
| 322 | - * BricksSettingsReader::paletteValues()). GeneratePress prints `--{slug}` | |
| 323 | - * on :root from its Global Colors and publishes them to the editor | |
| 324 | - * palette as bare `var(--{slug})` (see | |
| 325 | - * GeneratePressSettingsReader::paletteValues()). | |
| 265 | + * styles component). Blocksy needs no entry: its palette already ships | |
| 266 | + * with hex fallbacks. | |
| 326 | 267 | * |
| 327 | 268 | * The values are only ever used as the fallback half of `var(--x, #hex)`, |
| 328 | 269 | * so a wrong or stale answer cannot repaint anything — the browser keeps |
| 329 | 270 | * resolving the live property — it can only mis-measure, which is where |
| @@ -347,24 +288,19 @@ | ||
| 347 | 288 | $values['--ast-global-color-' . $index] = (string)$color; |
| 348 | 289 | } |
| 349 | 290 | } |
| 350 | 291 | |
| 351 | - foreach (BlocksySettingsReader::paletteValues() as $property => $color) { | |
| 352 | - $values[$property] = $color; | |
| 292 | + if (function_exists('Kadence\\kadence')) { | |
| 293 | + try { | |
| 294 | + foreach (range(1, 15) as $index) { | |
| 295 | + $values['--global-palette' . $index] = (string)\Kadence\kadence()->palette_option('palette' . $index); | |
| 296 | + } | |
| 297 | + } catch (\Throwable $e) { | |
| 298 | + // The vendor's API misbehaving means no vendor values — the | |
| 299 | + // stand-down paths below already handle that. | |
| 300 | + } | |
| 353 | 301 | } |
| 354 | 302 | |
| 355 | - foreach (KadenceSettingsReader::paletteValues() as $property => $color) { | |
| 356 | - $values[$property] = $color; | |
| 357 | - } | |
| 358 | - | |
| 359 | - foreach (BricksSettingsReader::paletteValues() as $property => $color) { | |
| 360 | - $values[$property] = $color; | |
| 361 | - } | |
| 362 | - | |
| 363 | - foreach (GeneratePressSettingsReader::paletteValues() as $property => $color) { | |
| 364 | - $values[$property] = $color; | |
| 365 | - } | |
| 366 | - | |
| 367 | 303 | /** |
| 368 | 304 | * Filter the vendor-declared custom property values used to measure |
| 369 | 305 | * bare palette references, keyed by property name (`--x` => `#hex`). |
| 370 | 306 | * |
| @@ -455,115 +391,8 @@ | ||
| 455 | 391 | return ''; |
| 456 | 392 | } |
| 457 | 393 | |
| 458 | 394 | /** |
| 459 | - * Normalise a colour a theme setting states into a value we can write. | |
| 460 | - * | |
| 461 | - * A hex is itself, lowercased. A custom-property reference stays a live | |
| 462 | - * reference; a bare one gains the vendor's current value as its hex | |
| 463 | - * fallback (see vendorValues()), so the browser still follows the | |
| 464 | - * property while PHP measures the fallback. Anything else — rgba(), | |
| 465 | - * named colours, expressions — is refused: the result is written into a | |
| 466 | - * declaration on every storefront page and must pass | |
| 467 | - * FrontendTheme::sanitizeDeclarationValue(). | |
| 468 | - * | |
| 469 | - * @param mixed $raw | |
| 470 | - * @return string Hex, `var(--x)`, `var(--x, #hex)`, or ''. | |
| 471 | - */ | |
| 472 | - public static function settingValue($raw): string | |
| 473 | - { | |
| 474 | - if (!is_string($raw)) { | |
| 475 | - return ''; | |
| 476 | - } | |
| 477 | - | |
| 478 | - $raw = trim($raw); | |
| 479 | - | |
| 480 | - if (preg_match('/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/', $raw)) { | |
| 481 | - return ColorMath::hex($raw); | |
| 482 | - } | |
| 483 | - | |
| 484 | - $reference = self::safeReference($raw); | |
| 485 | - | |
| 486 | - if ($reference === '' || self::measurable($reference) !== '') { | |
| 487 | - return $reference; | |
| 488 | - } | |
| 489 | - | |
| 490 | - $property = substr($reference, 4, -1); | |
| 491 | - $vendorHex = (string)Arr::get(self::vendorValues(), $property, ''); | |
| 492 | - | |
| 493 | - return $vendorHex !== '' ? 'var(' . $property . ', ' . $vendorHex . ')' : $reference; | |
| 494 | - } | |
| 495 | - | |
| 496 | - /** | |
| 497 | - * The colours the active theme's own settings state, by role. | |
| 498 | - * | |
| 499 | - * Palette slots say which colours a theme offers; its settings say which | |
| 500 | - * one the owner put where. A reader for the active theme (see | |
| 501 | - * Readers\ThemeSettingsReader) reports what the owner set — Astra's, | |
| 502 | - * Blocksy's, Kadence's, Divi's, Bricks' or GeneratePress's Accent, Body Text, Content Background, Borders | |
| 503 | - * and Button colours — and those outrank the palette-slot guesses in anchors() and resolve(). | |
| 504 | - * Only the site editor's own button (buttonGlobals()) outranks them. | |
| 505 | - * | |
| 506 | - * Roles: surface, text, accent, border, button_bg, button_text, | |
| 507 | - * button_hover_bg, button_hover_text. A role the theme does not state is | |
| 508 | - * absent, and is derived exactly as it would be without a reader. | |
| 509 | - * | |
| 510 | - * @return array Role => hex or reference. | |
| 511 | - */ | |
| 512 | - public static function settingsRoles(): array | |
| 513 | - { | |
| 514 | - if (self::$cachedSettingsRoles !== null) { | |
| 515 | - return self::$cachedSettingsRoles; | |
| 516 | - } | |
| 517 | - | |
| 518 | - $roles = []; | |
| 519 | - $theme = ''; | |
| 520 | - | |
| 521 | - foreach (self::$settingsReaders as $name => $reader) { | |
| 522 | - if ($reader::applies()) { | |
| 523 | - $theme = $name; | |
| 524 | - $roles = $reader::roles(); | |
| 525 | - break; | |
| 526 | - } | |
| 527 | - } | |
| 528 | - | |
| 529 | - /** | |
| 530 | - * Filter the colours the active theme's settings state, by role. | |
| 531 | - * | |
| 532 | - * Runs whether or not a built-in reader applied, so a theme FluentCart | |
| 533 | - * does not read can be supplied here; return [] to turn the reader off | |
| 534 | - * and fall back to palette slots. Values must be a hex or a | |
| 535 | - * `var(--x)` / `var(--x, #hex)` reference — anything else is dropped, | |
| 536 | - * and a bare reference to a vendor property gains its hex fallback. | |
| 537 | - * | |
| 538 | - * @param array $roles Role => colour. Keys: surface, text, accent, | |
| 539 | - * border, button_bg, button_text, | |
| 540 | - * button_hover_bg, button_hover_text. | |
| 541 | - * @param array $context ['theme' => reader name ('blocksy', 'kadence', 'divi', 'bricks', 'generatepress', 'astra'), or '' when | |
| 542 | - * no built-in reader applies]. | |
| 543 | - */ | |
| 544 | - $roles = apply_filters('fluent_cart/theme/settings_roles', $roles, [ | |
| 545 | - 'theme' => $theme, | |
| 546 | - ]); | |
| 547 | - | |
| 548 | - // A filter is just another source of values: only known roles with a | |
| 549 | - // writable colour survive. | |
| 550 | - $clean = []; | |
| 551 | - | |
| 552 | - if (is_array($roles)) { | |
| 553 | - foreach (self::$settingsRoleKeys as $role) { | |
| 554 | - $value = self::settingValue(Arr::get($roles, $role, '')); | |
| 555 | - | |
| 556 | - if ($value !== '') { | |
| 557 | - $clean[$role] = $value; | |
| 558 | - } | |
| 559 | - } | |
| 560 | - } | |
| 561 | - | |
| 562 | - return self::$cachedSettingsRoles = $clean; | |
| 563 | - } | |
| 564 | - | |
| 565 | - /** | |
| 566 | 395 | * Whether the active theme gave us anything usable. |
| 567 | 396 | * |
| 568 | 397 | * @return bool |
| 569 | 398 | */ |
| @@ -598,12 +427,8 @@ | ||
| 598 | 427 | if (Arr::get($globals, 'background', '') !== '' || Arr::get($globals, 'text', '') !== '') { |
| 599 | 428 | return true; |
| 600 | 429 | } |
| 601 | 430 | |
| 602 | - if (self::settingsRoles()) { | |
| 603 | - return true; | |
| 604 | - } | |
| 605 | - | |
| 606 | 431 | return Arr::get(self::buttonGlobals(), 'background', '') !== ''; |
| 607 | 432 | } |
| 608 | 433 | |
| 609 | 434 | /** |
| @@ -693,17 +518,13 @@ | ||
| 693 | 518 | * Backgrounds usually arrive as a preset reference |
| 694 | 519 | * (`var:preset|color|contrast`), which resolves through the palette the |
| 695 | 520 | * same way the page pair's do. |
| 696 | 521 | * |
| 697 | - * The `:hover` pair (`styles.elements.button.:hover.color`) is read the | |
| 698 | - * same way, into `hover_background` / `hover_text`. | |
| 699 | - * | |
| 700 | - * @return array ['background' => hex, 'text' => hex, 'hover_background' => hex, | |
| 701 | - * 'hover_text' => hex]; any may be ''. | |
| 522 | + * @return array ['background' => hex, 'text' => hex]; either may be ''. | |
| 702 | 523 | */ |
| 703 | 524 | public static function buttonGlobals(): array |
| 704 | 525 | { |
| 705 | - $colors = ['background' => '', 'text' => '', 'hover_background' => '', 'hover_text' => '']; | |
| 526 | + $colors = ['background' => '', 'text' => '']; | |
| 706 | 527 | |
| 707 | 528 | if (!class_exists('WP_Theme_JSON_Resolver')) { |
| 708 | 529 | return $colors; |
| 709 | 530 | } |
| @@ -718,21 +539,15 @@ | ||
| 718 | 539 | if (!is_object($data) || !method_exists($data, 'get_raw_data')) { |
| 719 | 540 | continue; |
| 720 | 541 | } |
| 721 | 542 | |
| 722 | - $raw = (array)$data->get_raw_data(); | |
| 723 | - $pairs = [ | |
| 724 | - '' => Arr::get($raw, 'styles.elements.button.color', []), | |
| 725 | - 'hover_' => Arr::get($raw, 'styles.elements.button.:hover.color', []), | |
| 726 | - ]; | |
| 543 | + $pair = Arr::get((array)$data->get_raw_data(), 'styles.elements.button.color', []); | |
| 727 | 544 | |
| 728 | - foreach ($pairs as $prefix => $pair) { | |
| 729 | - foreach (['background', 'text'] as $half) { | |
| 730 | - $value = self::resolveReference((string)Arr::get((array)$pair, $half, '')); | |
| 545 | + foreach (['background', 'text'] as $half) { | |
| 546 | + $value = self::resolveReference((string)Arr::get((array)$pair, $half, '')); | |
| 731 | 547 | |
| 732 | - if ($value !== '') { | |
| 733 | - $colors[$prefix . $half] = $value; | |
| 734 | - } | |
| 548 | + if ($value !== '') { | |
| 549 | + $colors[$half] = $value; | |
| 735 | 550 | } |
| 736 | 551 | } |
| 737 | 552 | } |
| 738 | 553 | |
| @@ -812,9 +627,8 @@ | ||
| 812 | 627 | public static function anchors(): array |
| 813 | 628 | { |
| 814 | 629 | $map = self::anchorMap(); |
| 815 | 630 | $globals = self::globalColors(); |
| 816 | - $settings = self::settingsRoles(); | |
| 817 | 631 | |
| 818 | 632 | // Surface and body text resolve as a PAIR from one source, never mixed |
| 819 | 633 | // from two. Global styles outrank the palette — the palette lists the |
| 820 | 634 | // AVAILABLE colours, global styles say what the body actually RENDERS, |
| @@ -826,33 +640,12 @@ | ||
| 826 | 640 | // stylesheets' paired fallbacks never fire because both values exist. |
| 827 | 641 | $gsSurface = (string)Arr::get($globals, 'background', ''); |
| 828 | 642 | $gsText = (string)Arr::get($globals, 'text', ''); |
| 829 | 643 | |
| 830 | - $setSurface = (string)Arr::get($settings, 'surface', ''); | |
| 831 | - | |
| 832 | 644 | if ($gsSurface !== '' && $gsText !== '') { |
| 833 | 645 | // The rendered pair. |
| 834 | 646 | $surface = $gsSurface; |
| 835 | 647 | $text = $gsText; |
| 836 | - } elseif ($setSurface !== '') { | |
| 837 | - // The pair the theme's settings state (Astra's Content Background | |
| 838 | - // and Body Text). The same pair law: a lone stated text is never | |
| 839 | - // mixed onto a guessed surface. A lone surface takes the palette's | |
| 840 | - // own text when it reads there (4.5:1) — Astra saves Body Text | |
| 841 | - // empty and still means its text swatch — and only an unreadable | |
| 842 | - // one is replaced by a measured partner. | |
| 843 | - $surface = $setSurface; | |
| 844 | - $text = (string)Arr::get($settings, 'text', ''); | |
| 845 | - $surfaceHex = self::measurable($surface); | |
| 846 | - | |
| 847 | - if ($text === '' && $surfaceHex !== '') { | |
| 848 | - $paletteText = self::value(Arr::get($map, 'text', '')); | |
| 849 | - $paletteTextHex = self::measurable($paletteText); | |
| 850 | - | |
| 851 | - $text = $paletteTextHex !== '' && ColorMath::contrast($surfaceHex, $paletteTextHex) >= 4.5 | |
| 852 | - ? $paletteText | |
| 853 | - : ColorMath::readableOn($surfaceHex, '#F3F4F6', '#2F3448'); | |
| 854 | - } | |
| 855 | 648 | } else { |
| 856 | 649 | // The published pair. A lone global-styles fragment is dropped |
| 857 | 650 | // rather than paired with a guess. |
| 858 | 651 | $surface = self::value(Arr::get($map, 'surface', '')); |
| @@ -866,14 +659,10 @@ | ||
| 866 | 659 | $text = ColorMath::readableOn($surface, '#F3F4F6', '#2F3448'); |
| 867 | 660 | } |
| 868 | 661 | } |
| 869 | 662 | |
| 870 | - $accent = (string)Arr::get($settings, 'accent', ''); | |
| 663 | + $accent = self::value(Arr::get($map, 'accent', '')); | |
| 871 | 664 | |
| 872 | - if ($accent === '') { | |
| 873 | - $accent = self::value(Arr::get($map, 'accent', '')); | |
| 874 | - } | |
| 875 | - | |
| 876 | 665 | // The button follows the same law as the page pair: what the owner |
| 877 | 666 | // set in the site editor (Styles → Buttons) outranks every guess the |
| 878 | 667 | // palette could offer — but only led by its background. A background |
| 879 | 668 | // brings its own text partner, or gets one measured against it in |
| @@ -884,12 +673,8 @@ | ||
| 884 | 673 | |
| 885 | 674 | if ($button['background'] !== '') { |
| 886 | 675 | $buttonBg = $button['background']; |
| 887 | 676 | $buttonText = $button['text']; |
| 888 | - } elseif (Arr::get($settings, 'button_bg', '') !== '') { | |
| 889 | - // Next, the button the theme's settings state, with its partner. | |
| 890 | - $buttonBg = (string)$settings['button_bg']; | |
| 891 | - $buttonText = (string)Arr::get($settings, 'button_text', ''); | |
| 892 | 677 | } else { |
| 893 | 678 | $buttonBg = self::value(Arr::get($map, 'button_bg', '')); |
| 894 | 679 | |
| 895 | 680 | if ($buttonBg === '') { |
| @@ -949,12 +734,9 @@ | ||
| 949 | 734 | 'surface_mute' => ColorMath::mix($textHex, $surfaceHex, 8), |
| 950 | 735 | 'divider' => ColorMath::mix($textHex, $surfaceHex, 10), |
| 951 | 736 | 'border' => ColorMath::mix($textHex, $surfaceHex, 18), |
| 952 | 737 | 'text_placeholder' => ColorMath::mix($textHex, $surfaceHex, 45), |
| 953 | - // Muted text is still text: it moves toward the body text only | |
| 954 | - // as far as it must to read (4.5:1). A mid-grey body text | |
| 955 | - // (Divi's #666666) otherwise mixed down to 2.92:1. | |
| 956 | - 'text_muted' => ColorMath::readableMix($textHex, $surfaceHex, 68), | |
| 738 | + 'text_muted' => ColorMath::mix($textHex, $surfaceHex, 68), | |
| 957 | 739 | ] |
| 958 | 740 | : [ |
| 959 | 741 | 'surface_alt' => '', |
| 960 | 742 | 'surface_mute' => '', |
| @@ -963,16 +745,8 @@ | ||
| 963 | 745 | 'text_placeholder' => '', |
| 964 | 746 | 'text_muted' => '', |
| 965 | 747 | ]; |
| 966 | 748 | |
| 967 | - // A border the theme's settings state is the border (Astra's Borders); | |
| 968 | - // only an unstated one is mixed. | |
| 969 | - $settings = self::settingsRoles(); | |
| 970 | - | |
| 971 | - if (Arr::get($settings, 'border', '') !== '') { | |
| 972 | - $derived['border'] = (string)$settings['border']; | |
| 973 | - } | |
| 974 | - | |
| 975 | 749 | // Contrast needs a real colour to measure against for the same reason. |
| 976 | 750 | // And the button is owned as a pair or not at all: a background whose |
| 977 | 751 | // value cannot be measured (a bare reference — the theme resolves it on |
| 978 | 752 | // the page, and the store owner can recolour it to anything) is one no |
| @@ -978,62 +752,19 @@ | ||
| 978 | 752 | // the page, and the store owner can recolour it to anything) is one no |
| 979 | 753 | // readable text can be paired with here, so neither half is written and |
| 980 | 754 | // the stylesheets' own paired fallback styles the button instead. Text |
| 981 | 755 | // the owner chose alongside the background (the site editor's button |
| 982 | - // pair, or a theme settings reader's) is worn as given unless it cannot | |
| 983 | - // be read on it (below 3:1); a missing or unreadable partner is measured. | |
| 756 | + // pair) is worn as given; only a missing partner is measured. | |
| 984 | 757 | $buttonBgHex = self::measurable($buttonBg); |
| 985 | - $ownText = self::statedTextOn((string)Arr::get($anchors, 'button_text', ''), $buttonBgHex); | |
| 758 | + $ownText = (string)Arr::get($anchors, 'button_text', ''); | |
| 986 | 759 | |
| 987 | 760 | if ($buttonBgHex !== '') { |
| 988 | - $derived['button_text'] = $ownText !== '' ? $ownText : ColorMath::readableText($buttonBgHex); | |
| 761 | + $derived['button_text'] = $ownText !== '' ? $ownText : ColorMath::readableOn($buttonBgHex); | |
| 989 | 762 | } else { |
| 990 | 763 | $anchors['button_bg'] = ''; |
| 991 | 764 | $derived['button_text'] = ''; |
| 992 | 765 | } |
| 993 | 766 | |
| 994 | - // The hover follows the button it belongs to: unwritten when the button | |
| 995 | - // is. The site editor's `:hover` pair outranks any guess; otherwise the | |
| 996 | - // button colour moves away from itself — darker for a light button, | |
| 997 | - // lighter for a dark one. Hover text given with it is worn as given; | |
| 998 | - // otherwise the resting text carries over while it still reads (WCAG | |
| 999 | - // 4.5:1), and a partner is measured when it does not. | |
| 1000 | - $derived['button_hover_bg'] = ''; | |
| 1001 | - $derived['button_hover_text'] = ''; | |
| 1002 | - | |
| 1003 | - if ($buttonBgHex !== '') { | |
| 1004 | - $stated = self::buttonGlobals(); | |
| 1005 | - $hoverBg = $stated['hover_background']; | |
| 1006 | - $hoverText = $stated['hover_text']; | |
| 1007 | - | |
| 1008 | - // The theme settings' hover belongs to the theme settings' button: | |
| 1009 | - // it is only used when that button is the one being worn, never | |
| 1010 | - // paired onto a button the site editor states. | |
| 1011 | - $settingsButton = $stated['background'] === '' && Arr::get($settings, 'button_bg', '') !== ''; | |
| 1012 | - | |
| 1013 | - if ($hoverBg === '' && $settingsButton && Arr::get($settings, 'button_hover_bg', '') !== '') { | |
| 1014 | - $hoverBg = (string)$settings['button_hover_bg']; | |
| 1015 | - | |
| 1016 | - if ($hoverText === '') { | |
| 1017 | - $hoverText = (string)Arr::get($settings, 'button_hover_text', ''); | |
| 1018 | - } | |
| 1019 | - } | |
| 1020 | - | |
| 1021 | - if ($hoverBg === '') { | |
| 1022 | - $hoverBg = ColorMath::shiftFromItself($buttonBgHex, 12); | |
| 1023 | - } | |
| 1024 | - | |
| 1025 | - $hoverBgHex = self::measurable($hoverBg); | |
| 1026 | - $hoverText = self::statedTextOn($hoverText, $hoverBgHex); | |
| 1027 | - | |
| 1028 | - if ($hoverText === '' && $hoverBgHex !== '') { | |
| 1029 | - $hoverText = self::hoverTextFor($hoverBgHex, (string)$derived['button_text']); | |
| 1030 | - } | |
| 1031 | - | |
| 1032 | - $derived['button_hover_bg'] = $hoverBg; | |
| 1033 | - $derived['button_hover_text'] = $hoverText; | |
| 1034 | - } | |
| 1035 | - | |
| 1036 | 767 | // The outline secondary button is FluentCart's own invention, and its |
| 1037 | 768 | // outline is the hierarchy: it always keeps the page surface as its |
| 1038 | 769 | // background. Wearing the theme's stated pair outright painted BOTH |
| 1039 | 770 | // CTAs identically (Twenty Twenty-Five states black-on-white, so Add |
| @@ -1068,53 +799,8 @@ | ||
| 1068 | 799 | */ |
| 1069 | 800 | self::$cachedRoles = apply_filters('fluent_cart/theme/roles', array_merge($anchors, $derived)); |
| 1070 | 801 | |
| 1071 | 802 | return self::$cachedRoles; |
| 1072 | - } | |
| 1073 | - | |
| 1074 | - /** | |
| 1075 | - * A text colour a theme or owner stated for a background, if it can be worn. | |
| 1076 | - * | |
| 1077 | - * Stated text is a choice and is worn as given — including brand pairs | |
| 1078 | - * just under AA (white on #ff5500 is 3.21:1). Only one that cannot be read | |
| 1079 | - * on its background (below 3:1, the large-text floor) is refused, so a | |
| 1080 | - * measured partner takes its place. Twenty Twenty-Five's site-editor pair | |
| 1081 | - * #111111 on #503aa8 (2.26:1) is the case this catches. A text or a | |
| 1082 | - * background that cannot be measured here is not second-guessed. | |
| 1083 | - * | |
| 1084 | - * @param string $text The stated text, or ''. | |
| 1085 | - * @param string $backgroundHex The measured background, or ''. | |
| 1086 | - * @return string The text, or '' when it must be replaced. | |
| 1087 | - */ | |
| 1088 | - public static function statedTextOn(string $text, string $backgroundHex): string | |
| 1089 | - { | |
| 1090 | - if ($text === '' || $backgroundHex === '') { | |
| 1091 | - return $text; | |
| 1092 | - } | |
| 1093 | - | |
| 1094 | - $textHex = self::measurable($text); | |
| 1095 | - | |
| 1096 | - if ($textHex === '') { | |
| 1097 | - return $text; | |
| 1098 | - } | |
| 1099 | - | |
| 1100 | - return ColorMath::contrast($backgroundHex, $textHex) >= 3 ? $text : ''; | |
| 1101 | - } | |
| 1102 | - | |
| 1103 | - /** | |
| 1104 | - * The text for a hover background nobody gave a text: the button's | |
| 1105 | - * resting text carries over while it still reads (WCAG 4.5:1), and a | |
| 1106 | - * partner is measured when it does not. | |
| 1107 | - * | |
| 1108 | - * @param string $hoverBgHex | |
| 1109 | - * @param string $restingText | |
| 1110 | - * @return string | |
| 1111 | - */ | |
| 1112 | - public static function hoverTextFor(string $hoverBgHex, string $restingText): string | |
| 1113 | - { | |
| 1114 | - return ColorMath::contrast($hoverBgHex, $restingText) >= 4.5 | |
| 1115 | - ? $restingText | |
| 1116 | - : ColorMath::readableText($hoverBgHex); | |
| 1117 | 803 | } |
| 1118 | 804 | |
| 1119 | 805 | /** |
| 1120 | 806 | * A one-line description of what the active theme offers, shown under the |