View source
<?php
namespace Drupal\typed_data;
use Drupal\Component\Render\HtmlEscapedText;
use Drupal\Component\Render\MarkupInterface;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\TypedData\Exception\MissingDataException;
use Drupal\typed_data\Exception\InvalidArgumentException;
class PlaceholderResolver implements PlaceholderResolverInterface {
protected $dataFetcher;
protected $dataFilterManager;
public function __construct(DataFetcherInterface $data_fetcher, DataFilterManagerInterface $data_filter_manager) {
$this->dataFetcher = $data_fetcher;
$this->dataFilterManager = $data_filter_manager;
}
public function resolvePlaceholders($text, array $data = [], BubbleableMetadata $bubbleable_metadata = NULL, array $options = []) {
$options += [
'langcode' => NULL,
'clear' => FALSE,
];
$placeholder_by_data = $this
->scan($text);
if (empty($placeholder_by_data)) {
return [];
}
$replacements = [];
foreach ($placeholder_by_data as $data_name => $placeholders) {
foreach ($placeholders as $placeholder_main_part => $placeholder) {
try {
if (!isset($data[$data_name])) {
throw new MissingDataException("There is no data with the name '{$data_name}' available.");
}
list($property_sub_paths, $filters) = $this
->parseMainPlaceholderPart($placeholder_main_part, $placeholder);
$fetched_data = $this->dataFetcher
->fetchDataBySubPaths($data[$data_name], $property_sub_paths, $bubbleable_metadata, $options['langcode']);
if ($filters) {
$value = $fetched_data
->getValue();
$definition = $fetched_data
->getDataDefinition();
foreach ($filters as $filter_data) {
list($filter_id, $arguments) = $filter_data;
$filter = $this->dataFilterManager
->createInstance($filter_id);
if (!$filter
->allowsNullValues() && !isset($value)) {
throw new MissingDataException("There is no data value for filter '{$filter_id}' to work on.");
}
$value = $filter
->filter($definition, $value, $arguments, $bubbleable_metadata);
$definition = $filter
->filtersTo($definition, $arguments);
}
}
else {
$value = $fetched_data
->getString();
}
$replacements[$placeholder] = $value instanceof MarkupInterface ? $value : new HtmlEscapedText($value);
} catch (InvalidArgumentException $e) {
if (!empty($options['clear'])) {
$replacements[$placeholder] = '';
}
} catch (MissingDataException $e) {
if (!empty($options['clear'])) {
$replacements[$placeholder] = '';
}
}
}
}
return $replacements;
}
protected function parseMainPlaceholderPart($main_part, $placeholder) {
if (!$main_part) {
return [
[],
[],
];
}
$properties = explode('.', $main_part);
$last_part = array_pop($properties);
$filter_expressions = array_filter(explode('|', $last_part));
if ($main_part[0] != '|') {
$properties[] = rtrim(array_shift($filter_expressions));
}
$filters = [];
foreach ($filter_expressions as $expression) {
$matches = [];
preg_match_all('/
([^\\(]+)
\\( # ( - pattern start
(.+)
\\) # ) - pattern end
/x', $expression, $matches);
$filter_id = isset($matches[1][0]) ? $matches[1][0] : $expression;
$filter_id = str_replace(' ', '', $filter_id);
$args = array_map(function ($arg) {
return trim(trim($arg), "'");
}, explode(',', isset($matches[2][0]) ? $matches[2][0] : ''));
$filters[] = [
$filter_id,
$args,
];
}
return [
$properties,
$filters,
];
}
public function replacePlaceHolders($text, array $data = [], BubbleableMetadata $bubbleable_metadata = NULL, array $options = []) {
$replacements = $this
->resolvePlaceholders($text, $data, $bubbleable_metadata, $options);
$placeholders = array_keys($replacements);
$values = array_values($replacements);
return str_replace($placeholders, $values, $text);
}
public function scan($text) {
$number_of_tokens = preg_match_all('/
\\{\\{\\s* # {{ - pattern start
((?:@\\S+:)?[^\\s\\{\\}.|]*) # $match[1] $name not containing whitespace . | { or }, with optional prefix
( # $match[2] begins
(
(\\.|\\s*\\|\\s*) # . with no spaces on either side, or | as separator
[^\\s\\{\\}.|] # after separator we need at least one character
)
([^\\{\\}]*) # but then almost anything goes up until pattern end
)? # $match[2] is optional
\\s*\\}\\} # }} - pattern end
/x', $text, $matches);
$names = $matches[1];
$tokens = $matches[2];
$results = [];
for ($i = 0; $i < $number_of_tokens; $i++) {
$main_part = trim($tokens[$i], ". \t\n\r\0\v");
$results[$names[$i]][$main_part] = $matches[0][$i];
}
return $results;
}
}