85dcf0fb |
1 | <?php |
2 | /** |
3 | * Command line spellcheck class |
4 | * |
5 | * --- |
6 | * Quick brownn fox brownn |
7 | * |
8 | * brownn squirrel. |
9 | * twentytwo owttnewt |
10 | * --- |
11 | * @(#) International Ispell Version 3.1.20 10/10/95, patch 1 |
12 | * * |
13 | * & brownn 5 7: brown, Browne, browns, brown n, brown-n |
14 | * * |
15 | * & brownn 5 18: brown, Browne, browns, brown n, brown-n |
16 | * |
17 | * |
18 | * & brownn 5 1: brown, Browne, browns, brown n, brown-n |
19 | * * |
20 | * |
21 | * & twentytwo 2 1: twenty two, twenty-two |
22 | * # owttnewt 11 |
23 | * |
24 | * |
25 | * --- |
26 | * $params = array(); |
27 | * $params['spell_command'] = 'ispell -d american -a'; |
28 | * $params['use_proc_open'] = false; // (check_php_version(4,3)) |
29 | * $params['temp_dir'] = '/tmp/'; // $attachment_dir |
30 | * $params['userdic'] = array(); // user's dictionary |
31 | * $params['debug'] = true; |
32 | * |
33 | * $spell = new cmd_spell($params); |
34 | * // check $spell->error buffer |
35 | * |
36 | * $text = "Quick brownn fox brownn\n\nbrownn squirrel.\ntwentytwo owttnewt"; |
37 | * |
38 | * $results = $spell->check_text($text); |
39 | * // check $spell->error buffer |
40 | * // parse $results |
41 | * |
42 | * @copyright © 1999-2006 The SquirrelMail Project Team |
43 | * @license http://opensource.org/licenses/gpl-license.php GNU Public License |
44 | * @version $Id$ |
45 | * @package plugins |
46 | * @subpackage squirrelspell |
47 | */ |
48 | |
49 | /** |
50 | * Command line spellcheck class, compatible with ispell and aspell. |
51 | * @package plugins |
52 | * @subpackage squirrelspell |
53 | */ |
54 | class cmd_spell extends squirrelspell { |
55 | /** |
56 | * @var string |
57 | */ |
58 | var $spell_command = ''; |
59 | var $userdic = array(); |
60 | /** |
61 | * Controls which function is used to execute ispell. proc_open() |
62 | * should be used in PHP 4.3+. exec() can be used in older PHP versions. |
63 | * @var boolean |
64 | */ |
65 | var $use_proc_open = false; |
66 | /** |
67 | * @var string |
68 | */ |
69 | var $temp_dir = ''; |
70 | /** |
71 | */ |
72 | var $debug = false; |
73 | |
74 | var $missed_words = array(); |
75 | |
76 | /** |
77 | * Constructor function |
78 | * @param array $aParams |
79 | */ |
80 | function cmd_spell($aParams=array()) { |
81 | if (! isset($aParams['spell_command'])) { |
82 | return $this->set_error('Spellcheck command is not set.'); |
83 | } else { |
84 | $this->spell_command = $aParams['spell_command']; |
85 | } |
86 | |
87 | if (isset($aParams['userdic'])) { |
88 | $this->userdic = $aParams['userdic']; |
89 | } |
90 | |
91 | if (isset($aParams['use_proc_open'])) { |
92 | $this->use_proc_open = (bool) $aParams['use_proc_open']; |
93 | } |
94 | |
95 | if (isset($aParams['temp_dir'])) { |
96 | $this->temp_dir = $aParams['temp_dir']; |
97 | // add slash to attachment directory, if it does not end with slash. |
98 | if (substr($this->temp_dir, -1) != '/') { |
99 | $this->temp_dir = $this->temp_dir . '/'; |
100 | } |
101 | } elseif (!$this->use_proc_open) { |
102 | return $this->set_error('Temporally directory is not set.'); |
103 | } |
104 | |
105 | if (isset($aParams['debug']) && (bool) $aParams['debug']) { |
106 | $this->debug = true; |
107 | error_reporting(E_ALL); |
108 | ini_set('display_errors',1); |
109 | } |
110 | |
111 | } |
112 | |
113 | /** |
114 | * @param string $sText |
115 | * @return mixed array with command output or false. |
116 | */ |
117 | function proc_open_spell($sText) { |
118 | $descriptorspec = array( |
119 | 0 => array('pipe', 'r'), // stdin is a pipe that the child will read from |
120 | 1 => array('pipe', 'w'), // stdout is a pipe that the child will write to |
121 | 2 => array('pipe', 'w'), // stderr is a pipe that the child will write to |
122 | ); |
123 | |
124 | if ($this->debug) { |
125 | $spell_proc = proc_open($this->spell_command, $descriptorspec, $pipes); |
126 | } else { |
127 | $spell_proc = @proc_open($this->spell_command, $descriptorspec, $pipes); |
128 | } |
129 | |
130 | if ( ! is_resource($spell_proc) ) { |
131 | return $this->set_error(sprintf(_("Could not run the spellchecker command (%s)."), |
132 | $this->spell_command)); |
133 | } |
134 | |
135 | if ( ! @fwrite($pipes[0],$sText) ) { |
136 | $this->set_error(_("Error while writing to pipe.")); |
137 | // close all three $pipes here. |
138 | for($i=0; $i<=2; $i++) { |
139 | // disable all fclose error messages |
140 | @fclose($pipes[$i]); |
141 | } |
142 | return false; |
143 | } |
144 | |
145 | fclose($pipes[0]); |
146 | |
147 | $sqspell_output = array(); |
148 | for($i=1; $i<=2; $i++) { |
149 | while(!feof($pipes[$i])) { |
150 | array_push($sqspell_output, rtrim(fgetss($pipes[$i],999),"\r\n")); |
151 | } |
152 | fclose($pipes[$i]); |
153 | } |
154 | |
155 | if (proc_close($spell_proc)) { |
156 | $error = ''; |
157 | foreach ($sqspell_output as $line) { |
158 | $error.= $line . "\n"; |
159 | } |
160 | return $this->set_error($error); |
161 | } else { |
162 | return $sqspell_output; |
163 | } |
164 | } |
165 | |
166 | /** |
167 | * @param string $sText |
168 | * @return mixed array with command output or false. |
169 | */ |
170 | function exec_spell($sText) { |
171 | // find unused file in attachment directory |
172 | do { |
173 | $floc = $this->temp_dir . md5($sText . microtime()); |
174 | } while (file_exists($floc)); |
175 | |
176 | if ($this->debug) { |
177 | $fp = fopen($floc, 'w'); |
178 | } else { |
179 | $fp = @fopen($floc, 'w'); |
180 | } |
181 | if ( ! is_resource($fp) ) { |
182 | return $this->set_error(sprintf(_("Could not open temporary file '%s'."), |
183 | $floc) ); |
184 | } |
185 | |
186 | if ( ! @fwrite($fp, $sText) ) { |
187 | $this->set_error(sprintf(_("Error while writing to temporary file '%s'."), |
188 | $floc) ); |
189 | // close file descriptor |
190 | fclose($fp); |
191 | return false; |
192 | } |
193 | fclose($fp); |
194 | |
195 | exec("$this->spell_command < $floc 2>&1", $sqspell_output, $exitcode); |
196 | |
197 | unlink($floc); |
198 | |
199 | if ($exitcode) { |
200 | $error = ''; |
201 | foreach ($sqspell_output as $line) { |
202 | $error.= $line . "\n"; |
203 | } |
204 | return $this->set_error($error); |
205 | } else { |
206 | return $sqspell_output; |
207 | } |
208 | } |
209 | |
210 | /** |
211 | * Prepares string for ispell/aspell parsing |
212 | * |
213 | * Function adds an extra space at the beginning of each line. This way |
214 | * ispell/aspell don't treat these as command characters. |
215 | * @param string $sText |
216 | * @return string |
217 | */ |
218 | function prepare_text($sText) { |
219 | // prepend space to every sqspell_new_text line |
220 | $sText = str_replace("\r\n","\n",$sText); |
221 | $ret = ''; |
222 | foreach (explode("\n",$sText) as $line) { |
223 | $ret.= ' ' . $line . "\n"; |
224 | } |
225 | return $ret; |
226 | } |
227 | |
228 | /** |
229 | * Checks block of text |
230 | * @param string $sText text |
231 | * @return array |
232 | */ |
233 | function check_text($sText) { |
234 | $this->missed_words = array(); |
235 | |
236 | $sText = $this->prepare_text($sText); |
237 | |
238 | if ($this->use_proc_open) { |
239 | $sqspell_output = $this->proc_open_spell($sText); |
240 | } else { |
241 | $sqspell_output = $this->exec_spell($sText); |
242 | } |
243 | |
244 | /** |
245 | * Define some variables to be used during the processing. |
246 | */ |
247 | $current_line=0; |
248 | /** |
249 | * Now we process the output of sqspell_command (ispell or aspell in |
250 | * ispell compatibility mode, whichever). I'm going to be scarce on |
251 | * comments here, since you can just look at the ispell/aspell output |
252 | * and figure out what's going on. ;) The best way to describe this is |
253 | * "Dark Magic". |
254 | */ |
255 | for ($i=0; $i<sizeof($sqspell_output); $i++){ |
256 | switch (substr($sqspell_output[$i], 0, 1)){ |
257 | /** |
258 | * Line is empty. |
259 | * Ispell adds empty lines when an end of line is reached |
260 | */ |
261 | case '': |
262 | $current_line++; |
263 | break; |
264 | /** |
265 | * Line begins with "&". |
266 | * This means there's a misspelled word and a few suggestions. |
267 | */ |
268 | case '&': |
269 | list($left, $right) = explode(": ", $sqspell_output[$i]); |
270 | $tmparray = explode(" ", $left); |
271 | $sqspell_word=$tmparray[1]; |
272 | /** |
273 | * Check if the word is in user dictionary. |
274 | */ |
275 | if (! in_array($sqspell_word,$this->userdic)){ |
276 | $sqspell_symb=intval($tmparray[3])-1; |
277 | // add suggestions |
278 | if (!isset($this->missed_words[$sqspell_word])) { |
279 | foreach(explode(',',$right) as $word) { |
280 | $this->missed_words[$sqspell_word]['suggestions'][] = trim($word); |
281 | } |
282 | } |
283 | // add location |
284 | $this->missed_words[$sqspell_word]['locations'][] = "$current_line:$sqspell_symb"; |
285 | } |
286 | break; |
287 | /** |
288 | * Line begins with "#". |
289 | * This means a misspelled word and no suggestions. |
290 | */ |
291 | case '#': |
292 | $tmparray = explode(" ", $sqspell_output[$i]); |
293 | $sqspell_word=$tmparray[1]; |
294 | /** |
295 | * |
296 | * Check if the word is in user dictionary. |
297 | */ |
298 | if (!in_array($sqspell_word,$this->userdic)){ |
299 | $sqspell_symb=intval($tmparray[2])-1; |
300 | // no suggestions |
301 | $this->missed_words[$sqspell_word]['suggestions'] = array(); |
302 | // add location |
303 | $this->missed_words[$sqspell_word]['locations'][] = "$current_line:$sqspell_symb"; |
304 | } |
305 | break; |
306 | } |
307 | } |
308 | return $this->missed_words; |
309 | } |
310 | } |
311 | |
312 | |
313 | /** |
314 | * Define the command used to spellcheck the document. |
315 | */ |
316 | #$sqspell_command=$SQSPELL_APP[$sqspell_use_app]; |