| @@ -1134,13 +1134,13 @@ | ||
| 1134 | 1134 | * - Han, Kana, Thai, Lao, Khmer, Myanmar and Tibetan don't separate words at |
| 1135 | 1135 | * all, so no boundary can ever exist around a keyword. Whole-word is |
| 1136 | 1136 | * meaningless there and the keyword is matched as a substring instead. |
| 1137 | 1137 | * |
| 1138 | - * Everything else — underscore, zero-width joiners, non-ASCII digits — stays | |
| 1139 | - * a separator, matching the class the previous implementation tokenised on. | |
| 1140 | - * That keeps this a strict superset of the old matcher: a keyword that used | |
| 1141 | - * to be blocked is still blocked, and padding a keyword with an invisible | |
| 1142 | - * ZWNJ can't slip it past the filter. | |
| 1138 | + * The neighbouring-character guard covers base letters and digits. Marks | |
| 1139 | + * that are part of the keyword remain in the quoted literal, while a mark | |
| 1140 | + * appended after a keyword cannot turn into a bypass. Everything else — | |
| 1141 | + * underscore, zero-width joiners and punctuation — stays a separator, | |
| 1142 | + * matching the class the previous implementation tokenised on. | |
| 1143 | 1143 | * |
| 1144 | 1144 | * @param string $keyword |
| 1145 | 1145 | * @return string |
| 1146 | 1146 | */ |
| @@ -1151,14 +1151,15 @@ | ||
| 1151 | 1151 | if (preg_match('/[\p{Han}\p{Hiragana}\p{Katakana}\p{Thai}\p{Lao}\p{Khmer}\p{Myanmar}\p{Tibetan}]/u', $keyword)) { |
| 1152 | 1152 | return '/' . $quoted . '/ui'; |
| 1153 | 1153 | } |
| 1154 | 1154 | |
| 1155 | - $wordChar = '\p{L}\p{M}\d'; | |
| 1155 | + $edgeChar = '\p{L}\p{M}\d'; | |
| 1156 | + $neighborChar = '\p{L}\d'; | |
| 1156 | 1157 | |
| 1157 | 1158 | // Only guard an edge that is itself a word character, so keywords |
| 1158 | 1159 | // wrapped in punctuation (e.g. "$$$" or "buy!") stay matchable. |
| 1159 | - $lead = preg_match('/^[' . $wordChar . ']/u', $keyword) ? '(?<![' . $wordChar . '])' : ''; | |
| 1160 | - $trail = preg_match('/[' . $wordChar . ']$/u', $keyword) ? '(?![' . $wordChar . '])' : ''; | |
| 1160 | + $lead = preg_match('/^[' . $edgeChar . ']/u', $keyword) ? '(?<![' . $neighborChar . '])' : ''; | |
| 1161 | + $trail = preg_match('/[' . $edgeChar . ']$/u', $keyword) ? '(?![' . $neighborChar . '])' : ''; | |
| 1161 | 1162 | |
| 1162 | 1163 | return '/' . $lead . $quoted . $trail . '/ui'; |
| 1163 | 1164 | } |
| 1164 | 1165 | |