1<?php 2 3/** 4 +-----------------------------------------------------------------------+ 5 | This file is part of the Roundcube Webmail client | 6 | | 7 | Copyright (C) The Roundcube Dev Team | 8 | Copyright (C) Kolab Systems AG | 9 | | 10 | Licensed under the GNU General Public License version 3 or | 11 | any later version with exceptions for skins & plugins. | 12 | See the README file for a full license statement. | 13 | | 14 | PURPOSE: | 15 | Spellchecking backend implementation to work with Enchant | 16 +-----------------------------------------------------------------------+ 17 | Author: Aleksander Machniak <machniak@kolabsys.com> | 18 +-----------------------------------------------------------------------+ 19*/ 20 21/** 22 * Spellchecking backend implementation to work with Pspell 23 * 24 * @package Framework 25 * @subpackage Utils 26 */ 27class rcube_spellchecker_enchant extends rcube_spellchecker_engine 28{ 29 private $enchant_broker; 30 private $enchant_dictionary; 31 private $matches = []; 32 33 /** 34 * Return a list of languages supported by this backend 35 * 36 * @see rcube_spellchecker_engine::languages() 37 */ 38 function languages() 39 { 40 $this->init(); 41 42 if (!$this->enchant_broker) { 43 return []; 44 } 45 46 $langs = []; 47 if ($dicts = enchant_broker_list_dicts($this->enchant_broker)) { 48 foreach ($dicts as $dict) { 49 $langs[] = preg_replace('/-.*$/', '', $dict['lang_tag']); 50 } 51 } 52 53 return array_unique($langs); 54 } 55 56 /** 57 * Initializes Enchant dictionary 58 */ 59 private function init() 60 { 61 if (!$this->enchant_broker) { 62 if (!extension_loaded('enchant')) { 63 $this->error = "Enchant extension not available"; 64 return; 65 } 66 67 $this->enchant_broker = enchant_broker_init(); 68 } 69 70 if (!enchant_broker_dict_exists($this->enchant_broker, $this->lang)) { 71 $this->error = "Unable to load dictionary for selected language using Enchant"; 72 return; 73 } 74 75 $this->enchant_dictionary = enchant_broker_request_dict($this->enchant_broker, $this->lang); 76 } 77 78 /** 79 * Set content and check spelling 80 * 81 * @see rcube_spellchecker_engine::check() 82 */ 83 function check($text) 84 { 85 $this->init(); 86 87 if (!$this->enchant_dictionary) { 88 return []; 89 } 90 91 // tokenize 92 $text = preg_split($this->separator, $text, NULL, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE); 93 94 $diff = 0; 95 $matches = []; 96 97 foreach ($text as $w) { 98 $word = trim($w[0]); 99 $pos = $w[1] - $diff; 100 $len = mb_strlen($word); 101 102 if ($this->dictionary->is_exception($word)) { 103 // skip exceptions 104 } 105 else if (!enchant_dict_check($this->enchant_dictionary, $word)) { 106 $suggestions = enchant_dict_suggest($this->enchant_dictionary, $word); 107 108 if (is_array($suggestions) && count($suggestions) > self::MAX_SUGGESTIONS) { 109 $suggestions = array_slice($suggestions, 0, self::MAX_SUGGESTIONS); 110 } 111 112 $matches[] = [$word, $pos, $len, null, $suggestions]; 113 } 114 115 $diff += (strlen($word) - $len); 116 } 117 118 $this->matches = $matches; 119 return $matches; 120 } 121 122 /** 123 * Returns suggestions for the specified word 124 * 125 * @see rcube_spellchecker_engine::get_words() 126 */ 127 function get_suggestions($word) 128 { 129 $this->init(); 130 131 if (!$this->enchant_dictionary) { 132 return []; 133 } 134 135 $suggestions = enchant_dict_suggest($this->enchant_dictionary, $word); 136 137 if (is_array($suggestions) && count($suggestions) > self::MAX_SUGGESTIONS) { 138 $suggestions = array_slice($suggestions, 0, self::MAX_SUGGESTIONS); 139 } 140 141 return is_array($suggestions) ? $suggestions : []; 142 } 143 144 /** 145 * Returns misspelled words 146 * 147 * @see rcube_spellchecker_engine::get_suggestions() 148 */ 149 function get_words($text = null) 150 { 151 $result = []; 152 153 if ($text) { 154 // init spellchecker 155 $this->init(); 156 157 if (!$this->enchant_dictionary) { 158 return []; 159 } 160 161 // With Enchant we don't need to get suggestions to return misspelled words 162 $text = preg_split($this->separator, $text, NULL, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE); 163 164 foreach ($text as $w) { 165 $word = trim($w[0]); 166 167 // skip exceptions 168 if ($this->dictionary->is_exception($word)) { 169 continue; 170 } 171 172 if (!enchant_dict_check($this->enchant_dictionary, $word)) { 173 $result[] = $word; 174 } 175 } 176 177 return $result; 178 } 179 180 foreach ($this->matches as $m) { 181 $result[] = $m[0]; 182 } 183 184 return $result; 185 } 186} 187