source: trunk/lib/SpellCheck.inc.php @ 275

Last change on this file since 275 was 275, checked in by quinn, 17 years ago

Added match_remote_ip_exempt_usernames function for trendease (ported from codebase 1.1dev).

File size: 14.0 KB
Line 
1<?php
2/**
3 * SpellCheck.inc.php
4 * code by strangecode :: www.strangecode.com :: this document contains copyrighted information
5 *
6 * Interface to PHP's pspell functions.
7 *
8 * @author  Quinn Comendant <quinn@strangecode.com>
9 * @version 1.1
10 */
11
12/* Implementation example:
13--------------------------------------------------------------------------------
14include_once dirname(__FILE__) . '/_config.inc.php';
15include 'codebase/lib/SpellCheck.inc.php';
16
17// Instantiate with parameters. In this example we'll set the language and the path to the personal wordlist file.
18$spell = new SpellCheck(array(
19    'language' => 'en',
20    'personal_wordlist' => '/tmp/my_custom_dict'
21));
22
23// Just for the heck of it add a new word to persistent personal wordlist file.
24$spell->add('mealworm');
25
26$text_to_check = 'donky rinds taste like mealworm paste';
27
28if (!$spell->checkString($text_to_check)) {
29    $suggestions = $spell->getStringSuggestions($text_to_check);
30    echo 'Spelling errors! Here are suggested alternatives:';
31    print_r($suggestions);
32} else {
33    echo 'No spelling errors';
34}
35
36// Save added words to persistent custom wordlist file.
37$spell->save();
38--------------------------------------------------------------------------------
39*/
40
41class SpellCheck {
42
43    var $_params = array(
44        'language' => 'en',
45        'personal_wordlist' => '', // Text file to save custom words to.
46        'skip_length' => 3, // Words with this many chars or less will not be checked.
47        'mode' => PSPELL_NORMAL, // PSPELL_FAST, PSPELL_NORMAL, or PSPELL_BAD_SPELLERS.
48        'highlight_start' => '<strong style="color:red;">',
49        'highlight_end' => '</strong>',
50    );
51
52    var $_pspell_cfg_handle;
53    var $_pspell_handle;
54    var $_use_personal_wordlist = false;
55    var $_errors = array();
56
57    /**
58     * Constructor.
59     *
60     * @param  array    $params     Array of parameters (key => val pairs).
61     */
62    function SpellCheck($params)
63    {
64        $app =& App::getInstance();
65
66        if (!extension_loaded('pspell')) {
67            trigger_error('Pspell module not installed', E_USER_ERROR);
68        }
69
70        if (!is_array($params) || empty($params)) {
71            trigger_error('SpellCheck parameters not set properly', E_USER_ERROR);
72        }
73
74        $this->setParam($params);
75
76        $this->_pspell_cfg_handle = pspell_config_create($this->getParam('language'));
77
78        pspell_config_ignore($this->_pspell_cfg_handle, $this->getParam('skip_length'));
79        pspell_config_mode($this->_pspell_cfg_handle, $this->getParam('mode'));
80
81        if ('' != $this->getParam('personal_wordlist')) {
82            if (!is_writable(dirname($this->getParam('personal_wordlist'))) || !is_writable($this->getParam('personal_wordlist'))) {
83                $app->logMsg(sprintf('Personal wordlist file not writable: %s', $this->getParam('personal_wordlist')), LOG_WARNING, __FILE__, __LINE__);
84            } else {
85                pspell_config_personal($this->_pspell_cfg_handle, $this->getParam('personal_wordlist'));
86                $this->_use_personal_wordlist = true;
87                $app->logMsg(sprintf('Using personal wordlist: %s', $this->getParam('personal_wordlist')), LOG_DEBUG, __FILE__, __LINE__);
88            }
89        }
90
91        $this->_pspell_handle = pspell_new_config($this->_pspell_cfg_handle);
92    }
93
94    /**
95     * Set (or overwrite existing) parameters by passing an array of new parameters.
96     *
97     * @access public
98     * @param  array    $params     Array of parameters (key => val pairs).
99     */
100    function setParam($params)
101    {
102        $app =& App::getInstance();
103
104        if (isset($params) && is_array($params)) {
105            // Merge new parameters with old overriding only those passed.
106            $this->_params = array_merge($this->_params, $params);
107        } else {
108            $app->logMsg(sprintf('Parameters are not an array: %s', $params), LOG_ERR, __FILE__, __LINE__);
109        }
110    }
111
112    /**
113     * Return the value of a parameter, if it exists.
114     *
115     * @access public
116     * @param string $param        Which parameter to return.
117     * @return mixed               Configured parameter value.
118     */
119    function getParam($param)
120    {
121        $app =& App::getInstance();
122   
123        if (isset($this->_params[$param])) {
124            return $this->_params[$param];
125        } else {
126            $app->logMsg(sprintf('Parameter is not set: %s', $param), LOG_DEBUG, __FILE__, __LINE__);
127            return null;
128        }
129    }
130
131    /**
132     * Check whether any errors have been triggered.
133     *
134     * @return bool   True if any errors were found, false otherwise.
135     */
136    function anyErrors()
137    {
138        return (sizeof($this->_errors) > 0);
139    }
140
141    /**
142     * Reset the error list.
143     */
144    function resetErrorList()
145    {
146        $this->_errors = array();
147    }
148
149    /**
150     * Check one word.
151     *
152     * @access  public
153     * @param   string  $word
154     * @return  bool    True if word is correct.
155     * @author  Quinn Comendant <quinn@strangecode.com>
156     * @version 1.0
157     * @since   09 Jun 2005 18:23:51
158     */
159    function check($word)
160    {
161        if (pspell_check($this->_pspell_handle, $word)) {
162            return true;
163        } else {
164            $this->_errors[] = $word;
165            return false;
166        }
167    }
168
169    /**
170     * Suggest the correct spelling for one misspelled word.
171     *
172     * @access  public
173     * @param   string  $word
174     * @return  array   Word suggestions.
175     * @author  Quinn Comendant <quinn@strangecode.com>
176     * @version 1.0
177     * @since   09 Jun 2005 18:23:51
178     */
179    function suggest($word)
180    {
181        return pspell_suggest($this->_pspell_handle, $word);
182    }
183
184    /**
185     * Add a word to a personal list.
186     *
187     * @access  public
188     * @param   string  $word
189     * @return  array   Word suggestions.
190     * @author  Quinn Comendant <quinn@strangecode.com>
191     * @version 1.0
192     * @since   09 Jun 2005 18:23:51
193     */
194    function add($word)
195    {
196        $app =& App::getInstance();
197
198        if ($this->_use_personal_wordlist) {
199            if (pspell_add_to_personal($this->_pspell_handle, $word)) {
200                $app->logMsg(sprintf('Added "%s" to personal wordlist: %s', $word, $this->getParam('personal_wordlist')), LOG_DEBUG, __FILE__, __LINE__);
201                return true;
202            } else {
203                $app->logMsg(sprintf('Failed adding "%s" to personal wordlist: %s', $word, $this->getParam('personal_wordlist')), LOG_WARNING, __FILE__, __LINE__);
204                return false;
205            }
206        }
207    }
208
209    /**
210     * Save personal list to file.
211     *
212     * @access  public
213     * @param   string  $word
214     * @return  array   Word suggestions.
215     * @author  Quinn Comendant <quinn@strangecode.com>
216     * @version 1.0
217     * @since   09 Jun 2005 18:23:51
218     */
219    function save()
220    {
221        $app =& App::getInstance();
222
223        if ($this->_use_personal_wordlist) {
224            if (pspell_save_wordlist($this->_pspell_handle)) {
225                $app->logMsg(sprintf('Saved personal wordlist: %s', $this->getParam('personal_wordlist')), LOG_DEBUG, __FILE__, __LINE__);
226                return true;
227            } else {
228                $app->logMsg(sprintf('Failed saving personal wordlist: %s', $this->getParam('personal_wordlist')), LOG_ERR, __FILE__, __LINE__);
229                return false;
230            }
231        }
232    }
233
234    /**
235     * Returns an array of suggested words for each mispelled word in the given text.
236     * The first word of the returned array is the (possibly) misspelled word.
237     *
238     * @access  public
239     * @param   string  $string String to get suggestions for.
240     * @return  mixed   Array of suggested words or false if none.
241     * @author  Quinn Comendant <quinn@strangecode.com>
242     * @version 1.0
243     * @since   09 Jun 2005 21:29:49
244     */
245    function getStringSuggestions($string)
246    {
247        $corrections = array();
248        $words = preg_split('/([\W]+?)/', $string, -1, PREG_SPLIT_DELIM_CAPTURE);
249        // Remove non-word elements.
250        $words = preg_grep('/\w+/', $words);
251
252        if (is_array($words) && !empty($words)) {
253            foreach ($words as $i => $word) {
254                if (!$this->check($word)) {
255                    $corrections[$i] = $this->suggest($word);
256                    // Keep the original spelling as one of the suggestions.
257                    array_unshift($corrections[$i], $word);
258                    array_unique($corrections[$i]);
259                }
260            }
261        }
262        if (is_array($corrections) && !empty($corrections)) {
263            return $corrections;
264        } else {
265            return false;
266        }
267    }
268
269    /**
270     * Checks all words in a given string.
271     *
272     * @access  public
273     * @param   string  $string     String to check.
274     * @return  void
275     * @author  Quinn Comendant <quinn@strangecode.com>
276     * @version 1.0
277     * @since   09 Jun 2005 22:11:27
278     */
279    function checkString($string)
280    {
281        $errors = array();
282        $words = preg_split('/([\W]+?)/', $string, -1, PREG_SPLIT_DELIM_CAPTURE);
283        // Remove non-word elements.
284        $check_words = preg_grep('/\w+/', $words);
285
286        if (is_array($check_words) && !empty($check_words)) {
287            foreach ($check_words as $i => $word) {
288                if (!$this->check($word)) {
289                    $errors[] = $word;
290                }
291            }
292        }
293        if (empty($errors)) {
294            return true;
295        } else {
296            $this->_errors = $errors + $this->_errors;
297            return false;
298        }
299    }
300
301    /**
302     * Returns a given string with misspelled words highlighted.
303     *
304     * @access  public
305     * @param   string  $string     Text to highlight.
306     * @return  string  Highlighted text.
307     * @author  Quinn Comendant <quinn@strangecode.com>
308     * @version 1.0
309     * @since   09 Jun 2005 21:29:49
310     */
311    function getStringHighlighted($string, $show_footnote=false)
312    {
313        $words = preg_split('/([\W]+?)/', $string, -1, PREG_SPLIT_DELIM_CAPTURE);
314        $check_words = preg_grep('/\w+/', $words);
315        $cnt = 0;
316        if (is_array($check_words) && !empty($check_words)) {
317            foreach ($check_words as $i => $word) {
318                if (!$this->check($word)) {
319                    $footnote = $show_footnote ? '<sup>' . ++$cnt . '</sup>' : '';
320                    $words[$i] = $this->getParam('highlight_start') . $word . $this->getParam('highlight_end') . $footnote;
321                }
322            }
323        }
324        return join('', $words);
325    }
326
327    /**
328     * Prints the HTML for correcting all mispellings found in the text of one $_FORM element.
329     *
330     * @access  public
331     * @param   string  $form_name  Name of the form to check.
332     * @return  void
333     * @author  Quinn Comendant <quinn@strangecode.com>
334     * @version 1.0
335     * @since   09 Jun 2005 21:29:49
336     */
337    function printCorrectionForm($form_name)
338    {
339        ?>
340        <input name="<?php echo $form_name ?>" type="hidden" value="<?php echo getFormData($form_name) ?>" />
341        <?php
342
343        $form_words = $this->getStringSuggestions(getFormData($form_name));
344        if (is_array($form_words) && !empty($form_words)) {
345            ?><ol><?php
346            foreach ($form_words as $i => $words) {
347                ?>
348                <li>
349                <select name="spelling_suggestions[<?php echo $form_name ?>][<?php echo $i ?>]" onchange"document.forms[0].elements['spelling_corrections[<?php echo $form_name ?>][<?php echo $i ?>]'].value = this.value;">
350                <?php $original_word = array_shift($words); ?>
351                <option value="<?php echo $original_word ?>">(<?php echo $original_word ?>)</option>
352                <?php
353
354                foreach ($words as $suggestion) {
355                    ?>
356                    <option value="<?php echo $suggestion ?>"><?php echo $suggestion ?></option>
357                    <?php
358                }
359
360                ?>
361                </select>
362                <input type="text" name="spelling_corrections[<?php echo $form_name ?>][<?php echo $i ?>]" value="<?php echo $original_word ?>" class="sc-small" />
363                <?php if ($this->_use_personal_wordlist) { ?>
364                <input name="save_to_personal_wordlist[]" type="checkbox" value="<?php echo $i ?>" /><?php echo _("Learn spelling") ?>
365                <?php } ?>
366                </li>
367                <?php
368            }
369            ?></ol><?php
370        }
371    }
372
373    /**
374     * Tests if any form spelling corrections have been submitted.
375     *
376     * @access  public
377     * @return  bool    True if form spelling has been checked.
378     * @author  Quinn Comendant <quinn@strangecode.com>
379     * @version 1.0
380     * @since   09 Jun 2005 23:15:35
381     */
382    function anyFormCorrections()
383    {
384        return (false !== getFormData('spelling_suggestions', false)) || (false !== getFormData('spelling_corrections', false));
385    }
386
387    /**
388     * Replace the misspelled words in the text of a specified form with the corrections.
389     *
390     * @access  public
391     * @param   string  $form_name      Name of form to apply corrections to.
392     * @return  string  Corrected form text.
393     * @author  Quinn Comendant <quinn@strangecode.com>
394     * @version 1.0
395     * @since   09 Jun 2005 23:18:51
396     */
397    function applyFormCorrections($form_name)
398    {
399        $form_words = preg_split('/([\W]+?)/', getFormData($form_name), -1, PREG_SPLIT_DELIM_CAPTURE);
400        $suggestions = getFormData('spelling_suggestions');
401        $corrections = getFormData('spelling_corrections');
402
403        $form_words = array_diff($corrections[$form_name], array('')) + $suggestions[$form_name] + $form_words;
404        ksort($form_words);
405
406        if ($this->_use_personal_wordlist) {
407            $save_to_personal_wordlist = getFormData('save_to_personal_wordlist');
408            if (is_array($save_to_personal_wordlist) && !empty($save_to_personal_wordlist)) {
409                foreach ($save_to_personal_wordlist as $cust) {
410                    $this->add($form_words[$cust]);
411                }
412            }
413            $this->save();
414        }
415
416        if (is_array($form_words) && !empty($form_words)) {
417            return join('', $form_words);
418        } else {
419            return getFormData($form_name);
420        }
421    }
422
423} // End.
424
425?>
Note: See TracBrowser for help on using the repository browser.