PluginProbe
404 Solution / trunk
404 Solution vtrunk
4.3.5 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 4.2.0 4.1.19 4.1.18 4.1.17 4.1.16 4.1.15 4.1.13 4.1.12 4.1.11 4.1.10 4.1.9 4.1.8 4.1.7 4.1.6 4.1.5 4.1.4 4.1.3 trunk 2.30.0 All 109 releases
404-solution / includes / engine / SpellingMatchingEngine.php

SpellingMatchingEngine.php in 404 Solution trunk, at includes/engine/SpellingMatchingEngine.php

97 lines 3.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined('ABSPATH')) {
4 exit;
5 }
6
7 /**
8 * Matching engine that delegates to SpellChecker::getPermalinkUsingSpelling().
9 *
10 * Uses Levenshtein distance / N-gram filtering to find the closest matching
11 * published page. Expensive, so shouldRun() filters out URL shapes that are
12 * unlikely to be useful typo corrections.
13 */
14 class ABJ_404_Solution_SpellingMatchingEngine implements ABJ_404_Solution_MatchingEngine {
15
16 /** @var ABJ_404_Solution_SpellChecker */
17 private $spellChecker;
18
19 /**
20 * @param ABJ_404_Solution_SpellChecker $spellChecker
21 */
22 public function __construct(ABJ_404_Solution_SpellChecker $spellChecker) {
23 $this->spellChecker = $spellChecker;
24 }
25
26 /**
27 * The label stored in the redirect row's `engine` column for anything this
28 * engine scored. Static because SpellChecker records a rejected match under
29 * the same name (see ABJ_404_Solution_NearMissRecorder) before this engine
30 * ever sees the result, and the two must not drift apart: an automatic
31 * redirect and a captured near miss produced by the same code have to read
32 * the same in the admin Engine column.
33 *
34 * @return string
35 */
36 public static function engineName(): string {
37 return __('spell check', '404-solution');
38 }
39
40 /** @return string */
41 public function getName(): string {
42 return self::engineName();
43 }
44
45 /** @param ABJ_404_Solution_MatchRequest $request */
46 public function shouldRun(ABJ_404_Solution_MatchRequest $request): bool {
47 $urlSlugOnly = $request->getUrlSlugOnly();
48
49 if ($urlSlugOnly === '') {
50 return false;
51 }
52
53 $segments = array_values(array_filter(explode('/', $urlSlugOnly)));
54 if (count($segments) === 0) {
55 return false;
56 }
57
58 $lastSegment = (string)end($segments);
59
60 if (strlen($lastSegment) > 80) {
61 return false;
62 }
63 if (substr_count($lastSegment, '-') >= 6) {
64 return false;
65 }
66 if (preg_match('/\d{4,}/', $lastSegment)) {
67 return false;
68 }
69 if (!preg_match('/[a-zA-Z]/', $lastSegment)) {
70 return false;
71 }
72
73 return true;
74 }
75
76 /** @param ABJ_404_Solution_MatchRequest $request */
77 public function match(ABJ_404_Solution_MatchRequest $request): ?ABJ_404_Solution_MatchResult {
78 $permalink = $this->spellChecker->getPermalinkUsingSpelling(
79 $request->getUrlSlugOnly(),
80 $request->getRequestedURL(),
81 $request->getOptions()
82 );
83
84 if (empty($permalink)) {
85 return null;
86 }
87
88 $id = isset($permalink['id']) && is_scalar($permalink['id']) ? (string)$permalink['id'] : '';
89 $type = isset($permalink['type']) && is_scalar($permalink['type']) ? (string)$permalink['type'] : '';
90 $link = isset($permalink['link']) && is_string($permalink['link']) ? $permalink['link'] : '';
91 $title = isset($permalink['title']) && is_string($permalink['title']) ? $permalink['title'] : '';
92 $score = isset($permalink['score']) && is_scalar($permalink['score']) ? (float)$permalink['score'] : 0.0;
93
94 return new ABJ_404_Solution_MatchResult($id, $type, $link, $title, $score, $this->getName());
95 }
96 }
97