Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
AutocompleteMatcher
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
3 / 3
13
100.00% covered (success)
100.00%
1 / 1
 resolveMatchMode
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 stringMatches
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 collectOptionMatches
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
5
1<?php
2
3declare(strict_types=1);
4
5namespace Drupal\name\Utility;
6
7/**
8 * String matching helpers for name field autocomplete.
9 *
10 * @internal
11 */
12final class AutocompleteMatcher {
13
14  /**
15   * Resolves the effective autocomplete match mode for a single component.
16   *
17   * @param array<string, mixed> $settings
18   *   The field settings array from FieldDefinitionInterface::getSettings().
19   * @param string $component
20   *   The component machine name (for example, "given").
21   *
22   * @return string
23   *   Either "starts_with" or "contains". Falls back to "starts_with" for any
24   *   legacy configuration that predates these settings.
25   */
26  public static function resolveMatchMode(array $settings, string $component): string {
27    $override = $settings['autocomplete_match_overrides'][$component] ?? '';
28    $override_valid = $override === 'contains' || $override === 'starts_with';
29    if ($override_valid) {
30      return $override;
31    }
32    return ($settings['autocomplete_match'] ?? 'starts_with') === 'contains'
33      ? 'contains'
34      : 'starts_with';
35  }
36
37  /**
38   * Applies the resolved match mode to an in-memory string comparison.
39   */
40  public static function stringMatches(string $haystack, string $needle, string $mode): bool {
41    if ($needle === '') {
42      return FALSE;
43    }
44    $position = mb_strpos(mb_strtolower($haystack), $needle);
45    if ($position === FALSE) {
46      return FALSE;
47    }
48    return $mode === 'contains' ? TRUE : $position === 0;
49  }
50
51  /**
52   * Adds matching option values while honoring the shared result limit.
53   *
54   * @param array<string, mixed> $options
55   *   The option list keyed by stored value.
56   * @param string $test_string
57   *   The lowercase token to match.
58   * @param string $mode
59   *   The match mode.
60   * @param string $base_string
61   *   The prefix to prepend to matched option keys.
62   * @param array<string, string> $matches
63   *   The current match list.
64   * @param int $limit
65   *   The remaining match limit.
66   */
67  public static function collectOptionMatches(array $options, string $test_string, string $mode, string $base_string, array &$matches, int &$limit): void {
68    foreach ($options as $key => $option) {
69      $value_key       = (string) $key;
70      $value_mismatch  = !self::stringMatches($value_key, $test_string, $mode);
71      $option_mismatch = !self::stringMatches((string) $option, $test_string, $mode);
72      $no_match        = $value_mismatch && $option_mismatch;
73      if ($no_match) {
74        continue;
75      }
76      $matches[$base_string . $value_key] = $value_key;
77      $limit -= 1;
78      if ($limit <= 0) {
79        return;
80      }
81    }
82  }
83
84}