query->get('q') ?: ''; if ($q == '') { return new JsonResponse([]); } // Get the initialized webform element. $element = $webform->getElement($key); if (!$element) { return new JsonResponse([]); } // Set default autocomplete properties. $element += [ '#autocomplete_existing' => FALSE, '#autocomplete_items' => [], '#autocomplete_match' => 3, '#autocomplete_limit' => 10, '#autocomplete_match_operator' => 'CONTAINS', ]; // Check minimum number of characters. if (mb_strlen($q) < (int) $element['#autocomplete_match']) { return new JsonResponse([]); } $matches = []; // Get existing matches. if (!empty($element['#autocomplete_existing'])) { $matches += $this->getMatchesFromExistingValues($q, $webform->id(), $key, $element['#autocomplete_match_operator'], $element['#autocomplete_limit']); } // Get items (aka options) matches. if (!empty($element['#autocomplete_items'])) { $element['#options'] = $element['#autocomplete_items']; $options = WebformOptions::getElementOptions($element); $matches += $this->getMatchesFromOptions($q, $options, $element['#autocomplete_match_operator'], $element['#autocomplete_limit']); } // Sort matches by label and enforce the limit. if ($matches) { uasort($matches, function (array $a, array $b) { return $a['label'] > $b['label']; }); $matches = array_values($matches); $matches = array_slice($matches, 0, $element['#autocomplete_limit']); } return new JsonResponse($matches); } /** * Get matches from existing submission values. * * @param string $q * String to filter option's label by. * @param string $webform_id * The webform id. * @param string $key * The element's key. * @param string $operator * Match operator either CONTAINS or STARTS_WITH. * @param int $limit * Limit number of matches. * * @return array * An array of matches. */ protected function getMatchesFromExistingValues($q, $webform_id, $key, $operator = 'CONTAINS', $limit = 10) { // Query webform submission for existing values. $query = Database::getConnection()->select('webform_submission_data') ->fields('webform_submission_data', ['value']) ->condition('webform_id', $webform_id) ->condition('name', $key) ->condition('value', ($operator == 'START_WITH') ? "$q%" : "%$q%", 'LIKE') ->orderBy('value'); if ($limit) { $query->range(0, $limit); } // Convert query results values to matches array. $values = $query->execute()->fetchCol(); $matches = []; foreach ($values as $value) { $matches[$value] = ['value' => $value, 'label' => $value]; } return $matches; } /** * Get matches from options. * * @param string $q * String to filter option's label by. * @param array $options * An associative array of webform options. * @param string $operator * Match operator either CONTAINS or STARTS_WITH. * @param int $limit * Limit number of matches. * * @return array * An array of matches sorted by label. */ protected function getMatchesFromOptions($q, array $options, $operator = 'CONTAINS', $limit = 10) { // Make sure options are populated. if (empty($options)) { return []; } $matches = []; // Filter and convert options to autocomplete matches. $this->getMatchesFromOptionsRecursive($q, $options, $matches, $operator); // Sort matches. ksort($matches); // Apply match limit. if ($limit) { $matches = array_slice($matches, 0, $limit); } return array_values($matches); } /** * Get matches from options recursive. * * @param string $q * String to filter option's label by. * @param array $options * An associative array of webform options. * @param array $matches * An associative array of autocomplete matches. * @param string $operator * Match operator either CONTAINS or STARTS_WITH. */ protected function getMatchesFromOptionsRecursive($q, array $options, array &$matches, $operator = 'CONTAINS') { foreach ($options as $label) { if (is_array($label)) { $this->getMatchesFromOptionsRecursive($q, $label, $matches, $operator); continue; } // Cast TranslatableMarkup to string. $label = (string) $label; if ($operator == 'STARTS_WITH' && stripos($label, $q) === 0) { $matches[$label] = [ 'value' => $label, 'label' => $label, ]; } // Default to CONTAINS even when operator is empty. elseif (stripos($label, $q) !== FALSE) { $matches[$label] = [ 'value' => $label, 'label' => $label, ]; } } } }