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

Last change on this file since 326 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
RevLine 
[1]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>
[15]9 * @version 1.1
[1]10 */
[42]11
[1]12/* Implementation example:
13--------------------------------------------------------------------------------
[15]14include_once dirname(__FILE__) . '/_config.inc.php';
[1]15include 'codebase/lib/SpellCheck.inc.php';
16
[15]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(
[42]19    'language' => 'en',
[15]20    'personal_wordlist' => '/tmp/my_custom_dict'
21));
[1]22
[15]23// Just for the heck of it add a new word to persistent personal wordlist file.
24$spell->add('mealworm');
25
[1]26$text_to_check = 'donky rinds taste like mealworm paste';
27
28if (!$spell->checkString($text_to_check)) {
29    $suggestions = $spell->getStringSuggestions($text_to_check);
[15]30    echo 'Spelling errors! Here are suggested alternatives:';
[1]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(
[15]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.
[1]47        'mode' => PSPELL_NORMAL, // PSPELL_FAST, PSPELL_NORMAL, or PSPELL_BAD_SPELLERS.
48        'highlight_start' => '<strong style="color:red;">',
49        'highlight_end' => '</strong>',
50    );
[42]51
[1]52    var $_pspell_cfg_handle;
53    var $_pspell_handle;
54    var $_use_personal_wordlist = false;
55    var $_errors = array();
[42]56
[1]57    /**
58     * Constructor.
[42]59     *
[15]60     * @param  array    $params     Array of parameters (key => val pairs).
[1]61     */
[15]62    function SpellCheck($params)
[1]63    {
[136]64        $app =& App::getInstance();
65
[275]66        if (!extension_loaded('pspell')) {
67            trigger_error('Pspell module not installed', E_USER_ERROR);
68        }
69
[42]70        if (!is_array($params) || empty($params)) {
71            trigger_error('SpellCheck parameters not set properly', E_USER_ERROR);
72        }
[1]73
[42]74        $this->setParam($params);
[1]75
[15]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'))) {
[136]83                $app->logMsg(sprintf('Personal wordlist file not writable: %s', $this->getParam('personal_wordlist')), LOG_WARNING, __FILE__, __LINE__);
[1]84            } else {
[15]85                pspell_config_personal($this->_pspell_cfg_handle, $this->getParam('personal_wordlist'));
[1]86                $this->_use_personal_wordlist = true;
[136]87                $app->logMsg(sprintf('Using personal wordlist: %s', $this->getParam('personal_wordlist')), LOG_DEBUG, __FILE__, __LINE__);
[1]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    {
[136]102        $app =& App::getInstance();
103
[1]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 {
[136]108            $app->logMsg(sprintf('Parameters are not an array: %s', $params), LOG_ERR, __FILE__, __LINE__);
[1]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    {
[136]121        $app =& App::getInstance();
122   
[1]123        if (isset($this->_params[$param])) {
124            return $this->_params[$param];
125        } else {
[146]126            $app->logMsg(sprintf('Parameter is not set: %s', $param), LOG_DEBUG, __FILE__, __LINE__);
[1]127            return null;
128        }
129    }
[42]130
[1]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    }
[42]148
[1]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    }
[42]168
[1]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    }
[42]183
[1]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    {
[136]196        $app =& App::getInstance();
197
[1]198        if ($this->_use_personal_wordlist) {
[15]199            if (pspell_add_to_personal($this->_pspell_handle, $word)) {
[136]200                $app->logMsg(sprintf('Added "%s" to personal wordlist: %s', $word, $this->getParam('personal_wordlist')), LOG_DEBUG, __FILE__, __LINE__);
[42]201                return true;
202            } else {
[136]203                $app->logMsg(sprintf('Failed adding "%s" to personal wordlist: %s', $word, $this->getParam('personal_wordlist')), LOG_WARNING, __FILE__, __LINE__);
[42]204                return false;
205            }
[1]206        }
207    }
[42]208
[1]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    {
[136]221        $app =& App::getInstance();
222
[1]223        if ($this->_use_personal_wordlist) {
224            if (pspell_save_wordlist($this->_pspell_handle)) {
[136]225                $app->logMsg(sprintf('Saved personal wordlist: %s', $this->getParam('personal_wordlist')), LOG_DEBUG, __FILE__, __LINE__);
[1]226                return true;
227            } else {
[136]228                $app->logMsg(sprintf('Failed saving personal wordlist: %s', $this->getParam('personal_wordlist')), LOG_ERR, __FILE__, __LINE__);
[1]229                return false;
230            }
231        }
232    }
[42]233
[1]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    }
[42]268
[1]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    }
[42]300
[1]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    }
[42]326
[1]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
[42]342
[1]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>
[270]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;">
[1]350                <?php $original_word = array_shift($words); ?>
351                <option value="<?php echo $original_word ?>">(<?php echo $original_word ?>)</option>
352                <?php
[42]353
[1]354                foreach ($words as $suggestion) {
355                    ?>
356                    <option value="<?php echo $suggestion ?>"><?php echo $suggestion ?></option>
357                    <?php
358                }
[42]359
[1]360                ?>
361                </select>
[176]362                <input type="text" name="spelling_corrections[<?php echo $form_name ?>][<?php echo $i ?>]" value="<?php echo $original_word ?>" class="sc-small" />
[1]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    }
[42]372
[1]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    }
[42]386
[1]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.