1: <?php
2: /*
3: You may not change or alter any portion of this comment or credits
4: of supporting developers from this source code or any supporting source code
5: which is considered copyrighted (c) material of the original comment or credit authors.
6:
7: This program is distributed in the hope that it will be useful,
8: but WITHOUT ANY WARRANTY; without even the implied warranty of
9: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10: */
11:
12: namespace Xmf;
13:
14: /**
15: * Highlighter
16: *
17: * @category Xmf\Highlighter
18: * @package Xmf
19: * @author Richard Griffith <richard@geekwright.com>
20: * @copyright 2011-2018 XOOPS Project (https://xoops.org)
21: * @license GNU GPL 2.0 or later (https://www.gnu.org/licenses/gpl-2.0.html)
22: * @link https://xoops.org
23: */
24: class Highlighter
25: {
26: /**
27: * mbstring encoding
28: */
29: const ENCODING = 'UTF-8';
30:
31: /**
32: * Apply highlight to words in body text
33: *
34: * Surround occurrences of words in body with pre in front and post
35: * behind. Considers only occurrences of words outside of HTML tags.
36: *
37: * @param string|string[] $words words to highlight
38: * @param string $body body of html text to highlight
39: * @param string $pre string to begin a highlight
40: * @param string $post string to end a highlight
41: *
42: * @return string highlighted body
43: */
44: public static function apply($words, $body, $pre = '<mark>', $post = '</mark>')
45: {
46: if (!is_array($words)) {
47: $words = preg_replace('/[\s]+/', ' ', $words);
48: $words = explode(' ', $words);
49: }
50: foreach ($words as $word) {
51: $body = static::splitOnTag($word, $body, $pre, $post);
52: }
53:
54: return $body;
55: }
56:
57: /**
58: * find needle in between html tags and add highlighting
59: *
60: * @param string $needle string to find
61: * @param string $haystack html text to find needle in
62: * @param string $pre insert before needle
63: * @param string $post insert after needle
64: *
65: * @return mixed return from preg_replace_callback()
66: */
67: protected static function splitOnTag($needle, $haystack, $pre, $post)
68: {
69: $encoding = static::ENCODING;
70: return preg_replace_callback(
71: '#((?:(?!<[/a-z]).)*)([^>]*>|$)#si',
72: function ($capture) use ($needle, $pre, $post, $encoding) {
73: $haystack = $capture[1];
74: if (function_exists('mb_substr')) {
75: $p1 = mb_stripos($haystack, $needle, 0, $encoding);
76: $l1 = mb_strlen($needle, $encoding);
77: $ret = '';
78: while ($p1 !== false) {
79: $ret .= mb_substr($haystack, 0, $p1, $encoding) . $pre
80: . mb_substr($haystack, $p1, $l1, $encoding) . $post;
81: $haystack = mb_substr($haystack, $p1 + $l1, mb_strlen($haystack), $encoding);
82: $p1 = mb_stripos($haystack, $needle, 0, $encoding);
83: }
84: } else {
85: $p1 = stripos($haystack, $needle);
86: $l1 = strlen($needle);
87: $ret = '';
88: while ($p1 !== false) {
89: $ret .= substr($haystack, 0, $p1) . $pre . substr($haystack, $p1, $l1) . $post;
90: $haystack = substr($haystack, $p1 + $l1);
91: $p1 = stripos($haystack, $needle);
92: }
93: }
94: $ret .= $haystack . $capture[2];
95:
96: return $ret;
97: },
98: $haystack
99: );
100: }
101: }
102: