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