<?php
/*
=====================================================
DataLife Engine - by SoftNews Media Group
-----------------------------------------------------
https://dle-news.ru/
-----------------------------------------------------
Copyright (c) 2004-2025 SoftNews Media Group
=====================================================
This code is protected by copyright
=====================================================
File: links.php
=====================================================
*/
if( !defined('DATALIFEENGINE') ) {
header( "HTTP/1.1 403 Forbidden" );
header ( 'Location: ../../' );
die( "Hacking attempt!" );
}
mb_internal_encoding("UTF-8");
class Trie
{
private $root;
public function __construct() {
$this->root = new TrieNode();
}
public function add($word, $data, $caseInsensitive = false) {
$node = $this->root;
$lowerWord = $caseInsensitive ? mb_strtolower($word) : $word;
$originalWord = $word;
foreach (preg_split('//u', $lowerWord, -1, PREG_SPLIT_NO_EMPTY) as $index => $char) {
if (!isset($node->children[$char])) {
$node->children[$char] = new TrieNode();
}
$node = $node->children[$char];
}
$node->data = [
'original' => $originalWord,
'link' => $data['link'],
'target_blank' => $data['target_blank'],
'title' => $data['title'],
'origpattern' => $data['origpattern'],
'max_count' => $data['max_count'] ?? -1
];
}
public function search($text, $caseInsensitive = false) {
$result = array();
$length = mb_strlen($text);
for ($i = 0; $i < $length; $i++) {
$node = $this->root;
$originalWord = '';
for ($j = $i; $j < $length; $j++) {
$char = mb_substr($text, $j, 1);
$lowerChar = $caseInsensitive ? mb_strtolower($char) : $char;
if (!isset($node->children[$lowerChar])) break;
$node = $node->children[$lowerChar];
$originalWord .= $char;
if ($node->data !== null) {
$prevChar = $i > 0 ? mb_substr($text, $i - 1, 1) : '';
$nextChar = mb_substr($text, $j + 1, 1);
if (!$this->isWordChar($prevChar) && !$this->isWordChar($nextChar)) {
$result[] = array(
'position' => $i,
'word' => $originalWord,
'data' => $node->data);
}
}
}
}
return $this->filterOverlappingMatches($result);
}
private function filterOverlappingMatches($result) {
usort($result, function ($a, $b) {
return $a['position'] - $b['position'];
});
$filtered = array();
$lastEnd = -1;
foreach ($result as $match) {
$start = $match['position'];
$end = $start + mb_strlen($match['word']);
if ($start > $lastEnd) {
$filtered[] = $match;
$lastEnd = $end;
}
}
return $filtered;
}
private function isWordChar($char) {
return preg_match('/\p{L}/u', $char);
}
}
class TrieNode {
public $children = array();
public $data = null;
}
function replace_links($html, $links) {
if(!is_array($links) OR !count($links)) return $html;
$groups = array();
foreach ($links as $item) {
if (comparehosts(urldecode($item['replace']), urldecode($_SERVER['REQUEST_URI']))) continue;
$key = $item['rcount'] . '___' . ($item['case'] ? '1' : '0');
$groups[$key][] = $item;
}
$matchers = array();
foreach ($groups as $key => $group) {
$trie = new Trie();
foreach ($group as $item) {
$trie->add($item['word'], [
'link' => $item['replace'],
'target_blank' => $item['targetblank'],
'title' => $item['title'],
'origpattern' => $item['origpattern'],
'max_count' => $item['rcount']
], $item['case']);
}
$keyParts = explode('___', $key);
$case = (bool)$keyParts[1];
$matchers[] = array(
'trie' => $trie,
'case' => $case,
'max_count' => $group[0]['rcount']
);
}
$dom = new DOMDocument();
$dom->encoding = 'UTF-8';
libxml_use_internal_errors(true);
if ( stripos($html, "<html") !== false ) {
$full_page = true;
$dom->loadHTML('<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">'.$html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
} else {
$full_page = false;
$dom->loadHTML('<!DOCTYPE html><html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"></head><body>' . $html . '</body></html>', LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
}
libxml_clear_errors();
$xpath = new DOMXPath($dom);
$nodes = $xpath->query('//td/text()[not(ancestor::a)] |
//div/text()[not(ancestor::a)] |
//p/text()[not(ancestor::a)] |
//li/text()[not(ancestor::a)] |
//article/text()[not(ancestor::a)] |
//span/text()[not(ancestor::a)]
[not(ancestor::script|ancestor::style|ancestor::meta|ancestor::noscript|ancestor::textarea|ancestor::title)]');
$has_replacement = false;
$replacementCounts = array();
foreach ($nodes as $node) {
$text = $node->nodeValue;
$parent = $node->parentNode;
$offset = 0;
$fragments = array();
foreach ($matchers as $matcher) {
$matches = $matcher['trie']->search($text, $matcher['case']);
foreach ($matches as $match) {
$word = $match['data']['origpattern'];
$maxCount = $match['data']['max_count'];
if (!isset($replacementCounts[$word])) {
$replacementCounts[$word] = 0;
}
if ($maxCount > -1 && $replacementCounts[$word] >= $maxCount) {
continue;
}
if ($match['position'] >= $offset) {
if ($match['position'] > $offset) {
$fragments[] = mb_substr($text, $offset, $match['position'] - $offset);
}
$has_replacement = true;
$a = $dom->createElement('a');
$a->setAttribute('href', htmlspecialchars($match['data']['link']));
if( $match['data']['target_blank'] ) {
$a->setAttribute('target', '_blank');
}
if ($match['data']['title']) {
$a->setAttribute('title', htmlspecialchars($match['data']['title']));
}
$a->textContent = $match['word'];
$fragments[] = $a;
$offset = $match['position'] + mb_strlen($match['word']);
$replacementCounts[$word] ++;
}
}
}
if ($offset < mb_strlen($text)) {
$fragments[] = mb_substr($text, $offset);
}
if (count($fragments) > 1 || !($fragments[0] instanceof DOMText)) {
$fragment = $dom->createDocumentFragment();
foreach ($fragments as $part) {
if ($part instanceof DOMNode) {
$fragment->appendChild($part);
} else {
$fragment->appendChild($dom->createTextNode($part));
}
}
$parent->replaceChild($fragment, $node);
}
}
if(!$has_replacement) return $html;
$newHTML = $dom->saveHTML();
if($full_page) {
$newHTML = str_replace('<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">', '', $newHTML);
} else {
$newHTML = str_replace('<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">', '', $newHTML);
$newHTML = str_replace('<html><head></head><body>', '', $newHTML);
$newHTML = str_replace('</body></html>', '', $newHTML);
$newHTML = str_replace('<!DOCTYPE html>', '', $newHTML);
$newHTML = trim($newHTML);
}
$newHTML = str_ireplace('%7Btheme%7D', '{THEME}', $newHTML);
return $newHTML;
}
function comparehosts($a, $b) {
if (!$a or !$b) return false;
if (strpos($a, "//") === 0) $a = "http:" . $a;
$a = parse_url($a);
$a['path'] = isset($a['path']) ? $a['path'] : '';
if (strpos($b, "//") === 0) $b = "http:" . $b;
$b = parse_url($b);
$b['path'] = isset($b['path']) ? $b['path'] : '';
if (isset($a['query']) and $a['query']) $a = $a['path'] . '?' . $a['query'];
else $a = $a['path'];
if (isset($b['query']) and $b['query']) $b = $b['path'] . '?' . $b['query'];
else $b = $b['path'];
$a = preg_replace('#[/]+#i', '/', $a);
$b = preg_replace('#[/]+#i', '/', $b);
if (!isset($a[0]) or $a[0] != '/') $a = '/' . $a;
if (!$a or !$b) return false;
if ($a == $b) return true;
else return false;
}
function generate_words_variations($string) {
$words = explode(' ', $string);
$parsedWords = [];
$maxOptions = 1;
foreach ($words as $word) {
if (preg_match('/\(([^)]+)\)/', $word, $matches)) {
preg_match('/([^\(]+)\(([^)]+)\)/', $word, $matches);
$base = $matches[1];
$options = explode('|', $matches[2]);
$variants = array_map(function ($opt) use ($base) {
return $base . $opt;
}, $options);
$parsedWords[] = $variants;
$maxOptions = max($maxOptions, count($variants));
} else {
$parsedWords[] = [$word];
}
}
$result = [];
for ($i = 0; $i < $maxOptions; $i++) {
$phraseParts = [];
foreach ($parsedWords as $variants) {
$index = ($i < count($variants)) ? $i : (count($variants) - 1);
$phraseParts[] = $variants[$index];
}
$result[] = implode(' ', $phraseParts);
}
return $result;
}
$replace_links = get_vars( "links" );
if( !is_array( $replace_links ) ) {
$replace_links = array();
$db->query( "SELECT * FROM " . PREFIX . "_links WHERE enabled=1 ORDER BY id DESC" );
while ( $row = $db->get_row() ) {
$processed = array();
$row['word'] = trim(str_replace('"', '"', stripslashes($row['word'])));
if ($row['rcount'] < 1) $rcount = -1;
else $rcount = intval($row['rcount']);
if (!$row['only_one']) $caseInsensitive = true;
else $caseInsensitive = false;
if (preg_match('/^(.*?)\((.*?)\)/', $row['word'], $matches)) {
$variants = generate_words_variations($row['word']);
foreach ($variants as $v) {
$processed[] = array(
'word' => $v,
'origpattern' => $row['word'],
'replace' => $row['link'],
'rcount' => $rcount,
'targetblank' => $row['targetblank'],
'title' => $row['title'],
'case' => $caseInsensitive
);
}
} else {
$processed[] = [
'word' => $row['word'],
'origpattern' => $row['word'],
'replace' => $row['link'],
'rcount' => $rcount,
'targetblank' => $row['targetblank'],
'title' => $row['title'],
'case' => $caseInsensitive
];
}
foreach ($processed as $item) {
if ($row['replacearea'] == 2) {
$replace_links['news'][] = $item;
$replace_links['comments'][] = $item;
} elseif ($row['replacearea'] == 3) {
$replace_links['news'][] = $item;
} elseif ($row['replacearea'] == 4) {
$replace_links['comments'][] = $item;
} elseif ($row['replacearea'] == 5) {
$replace_links['static'][] = $item;
} elseif ($row['replacearea'] == 6) {
$replace_links['news'][] = $item;
$replace_links['comments'][] = $item;
$replace_links['static'][] = $item;
} else {
$replace_links['all'][] = $item;
}
}
}
unset($processed);
set_vars( "links", $replace_links );
$db->free();
}