diff program/lib/Roundcube/rcube_spellchecker.php @ 0:4681f974d28b

vanilla 1.3.3 distro, I hope
author Charlie Root
date Thu, 04 Jan 2018 15:52:31 -0500
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/program/lib/Roundcube/rcube_spellchecker.php	Thu Jan 04 15:52:31 2018 -0500
@@ -0,0 +1,418 @@
+<?php
+
+/**
+ +-----------------------------------------------------------------------+
+ | This file is part of the Roundcube Webmail client                     |
+ | Copyright (C) 2011-2013, Kolab Systems AG                             |
+ | Copyright (C) 2008-2013, The Roundcube Dev Team                       |
+ |                                                                       |
+ | Licensed under the GNU General Public License version 3 or            |
+ | any later version with exceptions for skins & plugins.                |
+ | See the README file for a full license statement.                     |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Spellchecking using different backends                              |
+ +-----------------------------------------------------------------------+
+ | Author: Aleksander Machniak <machniak@kolabsys.com>                   |
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+*/
+
+/**
+ * Helper class for spellchecking with Googielspell and PSpell support.
+ *
+ * @package    Framework
+ * @subpackage Utils
+ */
+class rcube_spellchecker
+{
+    private $matches = array();
+    private $engine;
+    private $backend;
+    private $lang;
+    private $rc;
+    private $error;
+    private $options = array();
+    private $dict;
+    private $have_dict;
+
+
+    /**
+     * Constructor
+     *
+     * @param string $lang Language code
+     */
+    function __construct($lang = 'en')
+    {
+        $this->rc     = rcube::get_instance();
+        $this->engine = $this->rc->config->get('spellcheck_engine', 'googie');
+        $this->lang   = $lang ?: 'en';
+
+        $this->options = array(
+            'ignore_syms' => $this->rc->config->get('spellcheck_ignore_syms'),
+            'ignore_nums' => $this->rc->config->get('spellcheck_ignore_nums'),
+            'ignore_caps' => $this->rc->config->get('spellcheck_ignore_caps'),
+            'dictionary'  => $this->rc->config->get('spellcheck_dictionary'),
+        );
+
+        $cls = 'rcube_spellcheck_' . $this->engine;
+        if (class_exists($cls)) {
+            $this->backend = new $cls($this, $this->lang);
+            $this->backend->options = $this->options;
+        }
+        else {
+            $this->error = "Unknown spellcheck engine '$this->engine'";
+        }
+    }
+
+    /**
+     * Return a list of supported languages
+     */
+    function languages()
+    {
+        // trust configuration
+        $configured = $this->rc->config->get('spellcheck_languages');
+        if (!empty($configured) && is_array($configured) && !$configured[0]) {
+            return $configured;
+        }
+        else if (!empty($configured)) {
+            $langs = (array)$configured;
+        }
+        else if ($this->backend) {
+            $langs = $this->backend->languages();
+        }
+
+        // load index
+        @include(RCUBE_LOCALIZATION_DIR . 'index.inc');
+
+        // add correct labels
+        $languages = array();
+        foreach ($langs as $lang) {
+            $langc = strtolower(substr($lang, 0, 2));
+            $alias = $rcube_language_aliases[$langc];
+            if (!$alias) {
+                $alias = $langc.'_'.strtoupper($langc);
+            }
+            if ($rcube_languages[$lang]) {
+                $languages[$lang] = $rcube_languages[$lang];
+            }
+            else if ($rcube_languages[$alias]) {
+                $languages[$lang] = $rcube_languages[$alias];
+            }
+            else {
+                $languages[$lang] = ucfirst($lang);
+            }
+        }
+
+        // remove possible duplicates (#1489395)
+        $languages = array_unique($languages);
+
+        asort($languages);
+
+        return $languages;
+    }
+
+    /**
+     * Set content and check spelling
+     *
+     * @param string $text    Text content for spellchecking
+     * @param bool   $is_html Enables HTML-to-Text conversion
+     *
+     * @return bool True when no mispelling found, otherwise false
+     */
+    function check($text, $is_html = false)
+    {
+        // convert to plain text
+        if ($is_html) {
+            $this->content = $this->html2text($text);
+        }
+        else {
+            $this->content = $text;
+        }
+
+        if ($this->backend) {
+            $this->matches = $this->backend->check($this->content);
+        }
+
+        return $this->found() == 0;
+    }
+
+    /**
+     * Number of mispellings found (after check)
+     *
+     * @return int Number of mispellings
+     */
+    function found()
+    {
+        return count($this->matches);
+    }
+
+    /**
+     * Returns suggestions for the specified word
+     *
+     * @param string $word The word
+     *
+     * @return array Suggestions list
+     */
+    function get_suggestions($word)
+    {
+        if ($this->backend) {
+            return $this->backend->get_suggestions($word);
+        }
+
+        return array();
+    }
+
+    /**
+     * Returns misspelled words
+     *
+     * @param string $text The content for spellchecking. If empty content
+     *                     used for check() method will be used.
+     *
+     * @return array List of misspelled words
+     */
+    function get_words($text = null, $is_html=false)
+    {
+        if ($is_html) {
+            $text = $this->html2text($text);
+        }
+
+        if ($this->backend) {
+            return $this->backend->get_words($text);
+        }
+
+        return array();
+    }
+
+    /**
+     * Returns checking result in XML (Googiespell) format
+     *
+     * @return string XML content
+     */
+    function get_xml()
+    {
+        // send output
+        $out = '<?xml version="1.0" encoding="'.RCUBE_CHARSET.'"?><spellresult charschecked="'.mb_strlen($this->content).'">';
+
+        foreach ((array)$this->matches as $item) {
+            $out .= '<c o="'.$item[1].'" l="'.$item[2].'">';
+            $out .= is_array($item[4]) ? implode("\t", $item[4]) : $item[4];
+            $out .= '</c>';
+        }
+
+        $out .= '</spellresult>';
+
+        return $out;
+    }
+
+    /**
+     * Returns checking result (misspelled words with suggestions)
+     *
+     * @return array Spellchecking result. An array indexed by word.
+     */
+    function get()
+    {
+        $result = array();
+
+        foreach ((array)$this->matches as $item) {
+            if ($this->engine == 'pspell') {
+                $word = $item[0];
+            }
+            else {
+                $word = mb_substr($this->content, $item[1], $item[2], RCUBE_CHARSET);
+            }
+
+            if (is_array($item[4])) {
+                $suggestions = $item[4];
+            }
+            else if (empty($item[4])) {
+                $suggestions = array();
+            }
+            else {
+                $suggestions = explode("\t", $item[4]);
+            }
+
+            $result[$word] = $suggestions;
+        }
+
+        return $result;
+    }
+
+    /**
+     * Returns error message
+     *
+     * @return string Error message
+     */
+    function error()
+    {
+        return $this->error ?: ($this->backend ? $this->backend->error() : false);
+    }
+
+    private function html2text($text)
+    {
+        $h2t = new rcube_html2text($text, false, false, 0);
+        return $h2t->get_text();
+    }
+
+    /**
+     * Check if the specified word is an exception according to 
+     * spellcheck options.
+     *
+     * @param string  $word  The word
+     *
+     * @return bool True if the word is an exception, False otherwise
+     */
+    public function is_exception($word)
+    {
+        // Contain only symbols (e.g. "+9,0", "2:2")
+        if (!$word || preg_match('/^[0-9@#$%^&_+~*<>=:;?!,.-]+$/', $word))
+            return true;
+
+        // Contain symbols (e.g. "g@@gle"), all symbols excluding separators
+        if (!empty($this->options['ignore_syms']) && preg_match('/[@#$%^&_+~*=-]/', $word))
+            return true;
+
+        // Contain numbers (e.g. "g00g13")
+        if (!empty($this->options['ignore_nums']) && preg_match('/[0-9]/', $word))
+            return true;
+
+        // Blocked caps (e.g. "GOOGLE")
+        if (!empty($this->options['ignore_caps']) && $word == mb_strtoupper($word))
+            return true;
+
+        // Use exceptions from dictionary
+        if (!empty($this->options['dictionary'])) {
+            $this->load_dict();
+
+            // @TODO: should dictionary be case-insensitive?
+            if (!empty($this->dict) && in_array($word, $this->dict))
+                return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Add a word to dictionary
+     *
+     * @param string  $word  The word to add
+     */
+    public function add_word($word)
+    {
+        $this->load_dict();
+
+        foreach (explode(' ', $word) as $word) {
+            // sanity check
+            if (strlen($word) < 512) {
+                $this->dict[] = $word;
+                $valid = true;
+            }
+        }
+
+        if ($valid) {
+            $this->dict = array_unique($this->dict);
+            $this->update_dict();
+        }
+    }
+
+    /**
+     * Remove a word from dictionary
+     *
+     * @param string  $word  The word to remove
+     */
+    public function remove_word($word)
+    {
+        $this->load_dict();
+
+        if (($key = array_search($word, $this->dict)) !== false) {
+            unset($this->dict[$key]);
+            $this->update_dict();
+        }
+    }
+
+    /**
+     * Update dictionary row in DB
+     */
+    private function update_dict()
+    {
+        if (strcasecmp($this->options['dictionary'], 'shared') != 0) {
+            $userid = $this->rc->get_user_id();
+        }
+
+        $plugin = $this->rc->plugins->exec_hook('spell_dictionary_save', array(
+            'userid' => $userid, 'language' => $this->lang, 'dictionary' => $this->dict));
+
+        if (!empty($plugin['abort'])) {
+            return;
+        }
+
+        if ($this->have_dict) {
+            if (!empty($this->dict)) {
+                $this->rc->db->query(
+                    "UPDATE " . $this->rc->db->table_name('dictionary', true)
+                    ." SET `data` = ?"
+                    ." WHERE `user_id` " . ($plugin['userid'] ? "= ".$this->rc->db->quote($plugin['userid']) : "IS NULL")
+                        ." AND `language` = ?",
+                    implode(' ', $plugin['dictionary']), $plugin['language']);
+            }
+            // don't store empty dict
+            else {
+                $this->rc->db->query(
+                    "DELETE FROM " . $this->rc->db->table_name('dictionary', true)
+                    ." WHERE `user_id` " . ($plugin['userid'] ? "= ".$this->rc->db->quote($plugin['userid']) : "IS NULL")
+                        ." AND `language` = ?",
+                    $plugin['language']);
+            }
+        }
+        else if (!empty($this->dict)) {
+            $this->rc->db->query(
+                "INSERT INTO " . $this->rc->db->table_name('dictionary', true)
+                ." (`user_id`, `language`, `data`) VALUES (?, ?, ?)",
+                $plugin['userid'], $plugin['language'], implode(' ', $plugin['dictionary']));
+        }
+    }
+
+    /**
+     * Get dictionary from DB
+     */
+    private function load_dict()
+    {
+        if (is_array($this->dict)) {
+            return $this->dict;
+        }
+
+        if (strcasecmp($this->options['dictionary'], 'shared') != 0) {
+            $userid = $this->rc->get_user_id();
+        }
+
+        $plugin = $this->rc->plugins->exec_hook('spell_dictionary_get', array(
+            'userid' => $userid, 'language' => $this->lang, 'dictionary' => array()));
+
+        if (empty($plugin['abort'])) {
+            $dict = array();
+            $sql_result = $this->rc->db->query(
+                "SELECT `data` FROM " . $this->rc->db->table_name('dictionary', true)
+                ." WHERE `user_id` ". ($plugin['userid'] ? "= ".$this->rc->db->quote($plugin['userid']) : "IS NULL")
+                    ." AND `language` = ?",
+                $plugin['language']);
+
+            if ($sql_arr = $this->rc->db->fetch_assoc($sql_result)) {
+                $this->have_dict = true;
+                if (!empty($sql_arr['data'])) {
+                    $dict = explode(' ', $sql_arr['data']);
+                }
+            }
+
+            $plugin['dictionary'] = array_merge((array)$plugin['dictionary'], $dict);
+        }
+
+        if (!empty($plugin['dictionary']) && is_array($plugin['dictionary'])) {
+            $this->dict = $plugin['dictionary'];
+        }
+        else {
+            $this->dict = array();
+        }
+
+        return $this->dict;
+    }
+}