View source
<?php
namespace Drupal\alinks;
use Drupal\alinks\Entity\Keyword;
use Drupal\Component\Utility\Html;
use Drupal\Core\Url;
use Drupal\taxonomy\Entity\Term;
use Wamania\Snowball\English;
class AlinkPostRenderer {
protected $content;
protected $keywords;
protected $existingLinks;
protected $stemmer;
protected $stemmerCache = [];
protected $xpathSelector = "//text()[not(ancestor::a) and not(ancestor::script) and not(ancestor::*[@data-alink-ignore])]";
public function __construct($content, $context = NULL, $xpathSelector = NULL) {
if (!empty($context['#entity_type']) && !empty($context['#' . $context['#entity_type']])) {
$entity = $context['#' . $context['#entity_type']];
$class = 'Wamania\\Snowball\\' . $entity
->language()
->getName();
if (class_exists($class)) {
$this->stemmer = new $class();
}
else {
$this->stemmer = new English();
}
}
$this->content = $content;
if ($xpathSelector) {
$this->xpathSelector = $xpathSelector;
}
}
protected function getKeywords() {
if ($this->keywords === NULL) {
$ids = \Drupal::entityQuery('alink_keyword')
->condition('status', 1)
->execute();
$this->keywords = Keyword::loadMultiple($ids);
$vocabularies = \Drupal::config('alinks.settings')
->get('vocabularies');
if ($vocabularies) {
$terms = \Drupal::entityQuery('taxonomy_term')
->condition('vid', $vocabularies, 'IN')
->execute();
$terms = Term::loadMultiple($terms);
foreach ($terms as $term) {
$this->keywords[] = Keyword::create([
'name' => $term
->label(),
'link' => [
'uri' => 'internal:/' . $term
->toUrl()
->getInternalPath(),
],
]);
}
}
foreach ($this->keywords as &$keyword) {
$keyword->stemmed_keyword = $this->stemmer
->stem($keyword
->getText());
}
}
return $this->keywords;
}
public function setKeywords($keywords) {
$this->keywords = $keywords;
foreach ($this->keywords as &$keyword) {
$keyword->stemmed_keyword = $this->stemmer
->stem($keyword
->getText());
}
}
public function replace() {
$dom = Html::load($this->content);
$xpath = new \DOMXPath($dom);
$this->existingLinks = $this
->extractExistingLinks($xpath);
$this->keywords = array_filter($this
->getKeywords(), function (Keyword $word) {
return !isset($this->existingLinks[$word
->getUrl()]);
});
foreach ($xpath
->query($this->xpathSelector) as $node) {
$text = $node->wholeText;
$replace = FALSE;
if (empty(trim($text))) {
continue;
}
foreach ($this->keywords as $key => $word) {
$text = $this
->replaceFirst($word, '<a href="' . $word
->getUrl() . '">' . $word
->getText() . '</a>', $text, $count);
if ($count) {
$replace = TRUE;
$this
->addExistingLink($word);
break;
}
}
if ($replace) {
$this
->replaceNodeContent($node, $text);
}
}
return Html::serialize($dom);
}
protected function processDomNodeList($element) {
foreach ($element as $item) {
if ($item instanceof \DOMElement) {
if ($item
->hasChildNodes()) {
foreach ($item->childNodes as $childNode) {
if ($childNode instanceof \DOMText) {
foreach ($this
->getKeywords() as $word) {
$childNode->nodeValue = $this
->replaceFirst($word, '<a href="' . $word
->getUrl() . '">' . $word
->getText() . '</a>', $childNode->nodeValue);
}
}
}
}
}
}
return $element;
}
protected function replaceAll(Keyword $search, $replace, $subject, &$count = 0) {
$subject = str_replace($search
->getText(), $replace, $subject, $count);
if ($count == 0) {
}
return $subject;
}
protected function replaceFirst(Keyword $search, $replace, $subject, &$count = 0) {
$search_escaped = preg_quote($search
->getText(), '/');
$subject = preg_replace('/\\b' . $search_escaped . '\\b/u', $replace, $subject, 1, $count);
if ($count == 0) {
$terms = str_replace([
'.',
',',
';',
'!',
'?',
], '', $subject);
$terms = explode(' ', $terms);
$terms = array_filter(array_map('trim', $terms));
$terms = array_combine($terms, $terms);
$terms = array_map(function ($term) {
if (!isset($this->stemmerCache[$term])) {
$this->stemmerCache[$term] = $this->stemmer
->stem($term);
}
return $this->stemmerCache[$term];
}, $terms);
foreach ($terms as $original_term => $term) {
if ($term === $search->stemmed_keyword) {
$search_escaped = preg_quote($original_term, '/');
$subject = preg_replace('/\\b' . $search_escaped . '\\b/u', '<a href="' . $search
->getUrl() . '">' . $original_term . '</a>', $subject, 1, $count);
}
}
}
return $subject;
}
public static function postRender($content, $context) {
$selector = \Drupal::config('alinks.settings')
->get('xpathSelector');
$renderer = new static($content, $context, $selector);
return $renderer
->replace();
}
protected function normalizeUri($uri) {
if (empty($uri) || !is_null(parse_url($uri, PHP_URL_SCHEME))) {
return $uri;
}
if (strpos($uri, '<front>') === 0) {
$uri = substr($uri, strlen('<front>'));
}
return 'internal:/' . ltrim($uri, '/');
}
protected function extractExistingLinks($xpath) {
$links = [];
foreach ($xpath
->query('//a') as $link) {
try {
$uri = $this
->normalizeUri($link
->getAttribute('href'));
$links[] = Url::fromUri($uri)
->toString();
} catch (\Exception $exception) {
}
}
return array_flip(array_unique($links));
}
protected function addExistingLink(Keyword $word) {
$this->existingLinks[$word
->getUrl()] = TRUE;
$this->keywords = array_filter($this->keywords, function ($keyword) use ($word) {
if ($keyword
->getText() == $word
->getText()) {
return FALSE;
}
if ($keyword
->getUrl() == $word
->getUrl()) {
return FALSE;
}
return TRUE;
});
}
protected function replaceNodeContent(\DOMNode &$node, $content) {
if (strlen($content)) {
$replacement_nodes = Html::load($content)
->getElementsByTagName('body')
->item(0)->childNodes;
}
else {
$replacement_nodes = [
$node->ownerDocument
->createTextNode(''),
];
}
foreach ($replacement_nodes as $replacement_node) {
$replacement_node = $node->ownerDocument
->importNode($replacement_node, TRUE);
$node->parentNode
->insertBefore($replacement_node, $node);
}
$node->parentNode
->removeChild($node);
}
}