4 Copyright 1999, 2000, 2001, 2002 $ThePhpWikiProgrammingTeam
5 Copyright 2009 Marc-Etienne Vargenau, Alcatel-Lucent
7 This file is part of PhpWiki.
9 PhpWiki is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
14 PhpWiki is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with PhpWiki; if not, write to the Free Software
21 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 * FuzzyPages is plugin which searches for similar page titles.
27 * Pages are considered similar by averaging the similarity scores of
28 * the spelling comparison and the metaphone comparison for each page
29 * title in the database (php's metaphone() is an improved soundex
32 * http://www.php.net/manual/en/function.similar-text.php
33 * http://www.php.net/manual/en/function.metaphone.php
35 class WikiPlugin_FuzzyPages
39 return _("FuzzyPages");
42 function getDescription() {
43 return sprintf(_("Search for page titles similar to %s."),
47 function getVersion() {
48 return preg_replace("/[Revision: $]/", '',
52 function getDefaultArguments() {
53 return array('s' => false,
57 function spelling_similarity($subject) {
58 $spelling_similarity_score = 0;
59 similar_text($subject, $this->_searchterm,
60 $spelling_similarity_score);
61 return $spelling_similarity_score;
64 function sound_similarity($subject) {
65 $sound_similarity_score = 0;
66 similar_text(metaphone($subject), $this->_searchterm_metaphone,
67 $sound_similarity_score);
68 return $sound_similarity_score;
71 function averageSimilarities($subject) {
72 return ($this->spelling_similarity($subject)
73 + $this->sound_similarity($subject)) / 2;
76 function collectSimilarPages(&$list, &$dbi) {
77 if (! defined('MIN_SCORE_CUTOFF'))
78 define('MIN_SCORE_CUTOFF', 33);
80 $this->_searchterm_metaphone = metaphone($this->_searchterm);
82 $allPages = $dbi->getAllPages();
84 while ($pagehandle = $allPages->next()) {
85 $pagename = $pagehandle->getName();
86 $similarity_score = $this->averageSimilarities($pagename);
87 if ($similarity_score > MIN_SCORE_CUTOFF)
88 $list[$pagename] = $similarity_score;
92 function sortCollectedPages(&$list) {
93 arsort($list, SORT_NUMERIC);
96 function addTableCaption(&$table, &$dbi) {
97 if ($dbi->isWikiPage($this->_searchterm))
98 $link = WikiLink($this->_searchterm, 'auto');
100 $link = $this->_searchterm;
101 $caption = fmt("These page titles match fuzzy with '%s'", $link);
102 $table->pushContent(HTML::caption(array('align'=>'top'), $caption));
105 function addTableHead(&$table) {
106 $row = HTML::tr(HTML::th(_("Name")),
107 HTML::th(array('align' => 'right'), _("Score")));
109 $this->_pushDebugHeadingTDinto($row);
111 $table->pushContent(HTML::thead($row));
114 function addTableBody(&$list, &$table) {
115 if (! defined('HIGHLIGHT_ROWS_CUTOFF_SCORE'))
116 define('HIGHLIGHT_ROWS_CUTOFF_SCORE', 60);
118 $tbody = HTML::tbody();
119 foreach ($list as $found_pagename => $score) {
120 $row = HTML::tr(array('class' =>
121 $score > HIGHLIGHT_ROWS_CUTOFF_SCORE
122 ? 'evenrow' : 'oddrow'),
123 HTML::td(WikiLink($found_pagename)),
124 HTML::td(array('align' => 'right'),
128 $this->_pushDebugTDinto($row, $found_pagename);
130 $tbody->pushContent($row);
132 $table->pushContent($tbody);
135 function formatTable(&$list, &$dbi) {
137 $table = HTML::table(array('cellpadding' => 2,
140 'class' => 'pagelist'));
141 $this->addTableCaption($table, $dbi);
142 $this->addTableHead($table);
143 $this->addTableBody($list, $table);
148 function run($dbi, $argstr, &$request, $basepage) {
149 $args = $this->getArgs($argstr, $request);
152 return HTML::div(array('class' => "error"), "Please provide 's' argument to the plugin.");
154 $this->debug = $debug;
156 $this->_searchterm = $s;
157 $this->_list = array();
159 $this->collectSimilarPages($this->_list, $dbi);
160 $this->sortCollectedPages($this->_list);
161 return $this->formatTable($this->_list, $dbi);
166 function _pushDebugHeadingTDinto(&$row) {
167 $row->pushContent(HTML::td(_("Spelling Score")),
168 HTML::td(_("Sound Score")),
169 HTML::td('Metaphones'));
172 function _pushDebugTDinto(&$row, $pagename) {
173 // This actually calculates everything a second time for each pagename
174 // so the individual scores can be displayed separately for debugging.
175 $debug_spelling = round($this->spelling_similarity($pagename), 1);
176 $debug_sound = round($this->sound_similarity($pagename), 1);
177 $debug_metaphone = sprintf("(%s, %s)", metaphone($pagename),
178 $this->_searchterm_metaphone);
180 $row->pushcontent(HTML::td(array('align' => 'center'), $debug_spelling),
181 HTML::td(array('align' => 'center'), $debug_sound),
182 HTML::td($debug_metaphone));
190 // c-hanging-comment-ender-p: nil
191 // indent-tabs-mode: nil