Fixed spelling in comment.
[squirrelmail.git] / plugins / squirrelspell / class / cmd_spell.php
1 <?php
2 /**
3 * Command line spellcheck class
4 *
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 &copy; 1999-2007 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];