xref: /PHP-7.3/ext/pspell/pspell.c (revision 8d3f8ca1)
1 /*
2    +----------------------------------------------------------------------+
3    | PHP Version 7                                                        |
4    +----------------------------------------------------------------------+
5    | Copyright (c) 1997-2018 The PHP Group                                |
6    +----------------------------------------------------------------------+
7    | This source file is subject to version 3.01 of the PHP license,      |
8    | that is bundled with this package in the file LICENSE, and is        |
9    | available through the world-wide-web at the following url:           |
10    | http://www.php.net/license/3_01.txt                                  |
11    | If you did not receive a copy of the PHP license and are unable to   |
12    | obtain it through the world-wide-web, please send a note to          |
13    | license@php.net so we can mail you a copy immediately.               |
14    +----------------------------------------------------------------------+
15    | Author: Vlad Krupin <phpdevel@echospace.com>                         |
16    +----------------------------------------------------------------------+
17 */
18 
19 #define IS_EXT_MODULE
20 
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
24 
25 #include "php.h"
26 
27 #include <stdlib.h>
28 #include <ctype.h>
29 #include <stdio.h>
30 
31 #if HAVE_PSPELL
32 
33 /* this will enforce compatibility in .12 version (broken after .11.2) */
34 #define USE_ORIGINAL_MANAGER_FUNCS
35 
36 #include "php_pspell.h"
37 #include <pspell.h>
38 #include "ext/standard/info.h"
39 
40 #define PSPELL_FAST 1L
41 #define PSPELL_NORMAL 2L
42 #define PSPELL_BAD_SPELLERS 3L
43 #define PSPELL_SPEED_MASK_INTERNAL 3L
44 #define PSPELL_RUN_TOGETHER 8L
45 
46 /* Largest ignored word can be 999 characters (this seems sane enough),
47  * and it takes 3 bytes to represent that (see pspell_config_ignore)
48  */
49 #define PSPELL_LARGEST_WORD 3
50 
51 static PHP_MINIT_FUNCTION(pspell);
52 static PHP_MINFO_FUNCTION(pspell);
53 static PHP_FUNCTION(pspell_new);
54 static PHP_FUNCTION(pspell_new_personal);
55 static PHP_FUNCTION(pspell_new_config);
56 static PHP_FUNCTION(pspell_check);
57 static PHP_FUNCTION(pspell_suggest);
58 static PHP_FUNCTION(pspell_store_replacement);
59 static PHP_FUNCTION(pspell_add_to_personal);
60 static PHP_FUNCTION(pspell_add_to_session);
61 static PHP_FUNCTION(pspell_clear_session);
62 static PHP_FUNCTION(pspell_save_wordlist);
63 static PHP_FUNCTION(pspell_config_create);
64 static PHP_FUNCTION(pspell_config_runtogether);
65 static PHP_FUNCTION(pspell_config_mode);
66 static PHP_FUNCTION(pspell_config_ignore);
67 static PHP_FUNCTION(pspell_config_personal);
68 static PHP_FUNCTION(pspell_config_dict_dir);
69 static PHP_FUNCTION(pspell_config_data_dir);
70 static PHP_FUNCTION(pspell_config_repl);
71 static PHP_FUNCTION(pspell_config_save_repl);
72 
73 /* {{{ arginfo */
74 ZEND_BEGIN_ARG_INFO_EX(arginfo_pspell_new, 0, 0, 1)
75 	ZEND_ARG_INFO(0, language)
76 	ZEND_ARG_INFO(0, spelling)
77 	ZEND_ARG_INFO(0, jargon)
78 	ZEND_ARG_INFO(0, encoding)
79 	ZEND_ARG_INFO(0, mode)
80 ZEND_END_ARG_INFO()
81 
82 ZEND_BEGIN_ARG_INFO_EX(arginfo_pspell_new_personal, 0, 0, 2)
83 	ZEND_ARG_INFO(0, personal)
84 	ZEND_ARG_INFO(0, language)
85 	ZEND_ARG_INFO(0, spelling)
86 	ZEND_ARG_INFO(0, jargon)
87 	ZEND_ARG_INFO(0, encoding)
88 	ZEND_ARG_INFO(0, mode)
89 ZEND_END_ARG_INFO()
90 
91 ZEND_BEGIN_ARG_INFO_EX(arginfo_pspell_new_config, 0, 0, 1)
92 	ZEND_ARG_INFO(0, config)
93 ZEND_END_ARG_INFO()
94 
95 ZEND_BEGIN_ARG_INFO_EX(arginfo_pspell_check, 0, 0, 2)
96 	ZEND_ARG_INFO(0, pspell)
97 	ZEND_ARG_INFO(0, word)
98 ZEND_END_ARG_INFO()
99 
100 ZEND_BEGIN_ARG_INFO_EX(arginfo_pspell_suggest, 0, 0, 2)
101 	ZEND_ARG_INFO(0, pspell)
102 	ZEND_ARG_INFO(0, word)
103 ZEND_END_ARG_INFO()
104 
105 ZEND_BEGIN_ARG_INFO_EX(arginfo_pspell_store_replacement, 0, 0, 3)
106 	ZEND_ARG_INFO(0, pspell)
107 	ZEND_ARG_INFO(0, misspell)
108 	ZEND_ARG_INFO(0, correct)
109 ZEND_END_ARG_INFO()
110 
111 ZEND_BEGIN_ARG_INFO_EX(arginfo_pspell_add_to_personal, 0, 0, 2)
112 	ZEND_ARG_INFO(0, pspell)
113 	ZEND_ARG_INFO(0, word)
114 ZEND_END_ARG_INFO()
115 
116 ZEND_BEGIN_ARG_INFO_EX(arginfo_pspell_add_to_session, 0, 0, 2)
117 	ZEND_ARG_INFO(0, pspell)
118 	ZEND_ARG_INFO(0, word)
119 ZEND_END_ARG_INFO()
120 
121 ZEND_BEGIN_ARG_INFO_EX(arginfo_pspell_clear_session, 0, 0, 1)
122 	ZEND_ARG_INFO(0, pspell)
123 ZEND_END_ARG_INFO()
124 
125 ZEND_BEGIN_ARG_INFO_EX(arginfo_pspell_save_wordlist, 0, 0, 1)
126 	ZEND_ARG_INFO(0, pspell)
127 ZEND_END_ARG_INFO()
128 
129 ZEND_BEGIN_ARG_INFO_EX(arginfo_pspell_config_create, 0, 0, 1)
130 	ZEND_ARG_INFO(0, language)
131 	ZEND_ARG_INFO(0, spelling)
132 	ZEND_ARG_INFO(0, jargon)
133 	ZEND_ARG_INFO(0, encoding)
134 ZEND_END_ARG_INFO()
135 
136 ZEND_BEGIN_ARG_INFO_EX(arginfo_pspell_config_runtogether, 0, 0, 2)
137 	ZEND_ARG_INFO(0, conf)
138 	ZEND_ARG_INFO(0, runtogether)
139 ZEND_END_ARG_INFO()
140 
141 ZEND_BEGIN_ARG_INFO_EX(arginfo_pspell_config_mode, 0, 0, 2)
142 	ZEND_ARG_INFO(0, conf)
143 	ZEND_ARG_INFO(0, mode)
144 ZEND_END_ARG_INFO()
145 
146 ZEND_BEGIN_ARG_INFO_EX(arginfo_pspell_config_ignore, 0, 0, 2)
147 	ZEND_ARG_INFO(0, conf)
148 	ZEND_ARG_INFO(0, ignore)
149 ZEND_END_ARG_INFO()
150 
151 ZEND_BEGIN_ARG_INFO_EX(arginfo_pspell_config_personal, 0, 0, 2)
152 	ZEND_ARG_INFO(0, conf)
153 	ZEND_ARG_INFO(0, personal)
154 ZEND_END_ARG_INFO()
155 
156 ZEND_BEGIN_ARG_INFO_EX(arginfo_pspell_config_dict_dir, 0, 0, 2)
157 	ZEND_ARG_INFO(0, conf)
158 	ZEND_ARG_INFO(0, directory)
159 ZEND_END_ARG_INFO()
160 
161 ZEND_BEGIN_ARG_INFO_EX(arginfo_pspell_config_data_dir, 0, 0, 2)
162 	ZEND_ARG_INFO(0, conf)
163 	ZEND_ARG_INFO(0, directory)
164 ZEND_END_ARG_INFO()
165 
166 ZEND_BEGIN_ARG_INFO_EX(arginfo_pspell_config_repl, 0, 0, 2)
167 	ZEND_ARG_INFO(0, conf)
168 	ZEND_ARG_INFO(0, repl)
169 ZEND_END_ARG_INFO()
170 
171 ZEND_BEGIN_ARG_INFO_EX(arginfo_pspell_config_save_repl, 0, 0, 2)
172 	ZEND_ARG_INFO(0, conf)
173 	ZEND_ARG_INFO(0, save)
174 ZEND_END_ARG_INFO()
175 /* }}} */
176 
177 /* {{{ pspell_functions[]
178  */
179 static const zend_function_entry pspell_functions[] = {
180 	PHP_FE(pspell_new,					arginfo_pspell_new)
181 	PHP_FE(pspell_new_personal,			arginfo_pspell_new_personal)
182 	PHP_FE(pspell_new_config,			arginfo_pspell_new_config)
183 	PHP_FE(pspell_check,				arginfo_pspell_check)
184 	PHP_FE(pspell_suggest,				arginfo_pspell_suggest)
185 	PHP_FE(pspell_store_replacement,	arginfo_pspell_store_replacement)
186 	PHP_FE(pspell_add_to_personal,		arginfo_pspell_add_to_personal)
187 	PHP_FE(pspell_add_to_session,		arginfo_pspell_add_to_session)
188 	PHP_FE(pspell_clear_session,		arginfo_pspell_clear_session)
189 	PHP_FE(pspell_save_wordlist,		arginfo_pspell_save_wordlist)
190 	PHP_FE(pspell_config_create,		arginfo_pspell_config_create)
191 	PHP_FE(pspell_config_runtogether,	arginfo_pspell_config_runtogether)
192 	PHP_FE(pspell_config_mode,			arginfo_pspell_config_mode)
193 	PHP_FE(pspell_config_ignore,		arginfo_pspell_config_ignore)
194 	PHP_FE(pspell_config_personal,		arginfo_pspell_config_personal)
195 	PHP_FE(pspell_config_dict_dir,		arginfo_pspell_config_dict_dir)
196 	PHP_FE(pspell_config_data_dir,		arginfo_pspell_config_data_dir)
197 	PHP_FE(pspell_config_repl,			arginfo_pspell_config_repl)
198 	PHP_FE(pspell_config_save_repl,		arginfo_pspell_config_save_repl)
199 	PHP_FE_END
200 };
201 /* }}} */
202 
203 static int le_pspell, le_pspell_config;
204 
205 zend_module_entry pspell_module_entry = {
206     STANDARD_MODULE_HEADER,
207 	"pspell", pspell_functions, PHP_MINIT(pspell), NULL, NULL, NULL, PHP_MINFO(pspell), PHP_PSPELL_VERSION, STANDARD_MODULE_PROPERTIES
208 };
209 
210 #ifdef COMPILE_DL_PSPELL
ZEND_GET_MODULE(pspell)211 ZEND_GET_MODULE(pspell)
212 #endif
213 
214 static void php_pspell_close(zend_resource *rsrc)
215 {
216 	PspellManager *manager = (PspellManager *)rsrc->ptr;
217 
218 	delete_pspell_manager(manager);
219 }
220 
php_pspell_close_config(zend_resource * rsrc)221 static void php_pspell_close_config(zend_resource *rsrc)
222 {
223 	PspellConfig *config = (PspellConfig *)rsrc->ptr;
224 
225 	delete_pspell_config(config);
226 }
227 
228 #define PSPELL_FETCH_CONFIG  do { \
229 	zval *res = zend_hash_index_find(&EG(regular_list), conf); \
230 	if (res == NULL || Z_RES_P(res)->type != le_pspell_config) { \
231 		php_error_docref(NULL, E_WARNING, ZEND_LONG_FMT " is not a PSPELL config index", conf); \
232 		RETURN_FALSE; \
233 	} \
234 	config = (PspellConfig *)Z_RES_P(res)->ptr; \
235 } while (0)
236 
237 #define PSPELL_FETCH_MANAGER do { \
238 	zval *res = zend_hash_index_find(&EG(regular_list), scin); \
239 	if (res == NULL || Z_RES_P(res)->type != le_pspell) { \
240 		php_error_docref(NULL, E_WARNING, ZEND_LONG_FMT " is not a PSPELL result index", scin); \
241 		RETURN_FALSE; \
242 	} \
243 	manager = (PspellManager *)Z_RES_P(res)->ptr; \
244 } while (0);
245 
246 /* {{{ PHP_MINIT_FUNCTION
247  */
PHP_MINIT_FUNCTION(pspell)248 static PHP_MINIT_FUNCTION(pspell)
249 {
250 	REGISTER_LONG_CONSTANT("PSPELL_FAST", PSPELL_FAST, CONST_PERSISTENT | CONST_CS);
251 	REGISTER_LONG_CONSTANT("PSPELL_NORMAL", PSPELL_NORMAL, CONST_PERSISTENT | CONST_CS);
252 	REGISTER_LONG_CONSTANT("PSPELL_BAD_SPELLERS", PSPELL_BAD_SPELLERS, CONST_PERSISTENT | CONST_CS);
253 	REGISTER_LONG_CONSTANT("PSPELL_RUN_TOGETHER", PSPELL_RUN_TOGETHER, CONST_PERSISTENT | CONST_CS);
254 	le_pspell = zend_register_list_destructors_ex(php_pspell_close, NULL, "pspell", module_number);
255 	le_pspell_config = zend_register_list_destructors_ex(php_pspell_close_config, NULL, "pspell config", module_number);
256 	return SUCCESS;
257 }
258 /* }}} */
259 
260 /* {{{ proto int pspell_new(string language [, string spelling [, string jargon [, string encoding [, int mode]]]])
261    Load a dictionary */
PHP_FUNCTION(pspell_new)262 static PHP_FUNCTION(pspell_new)
263 {
264 	char *language, *spelling = NULL, *jargon = NULL, *encoding = NULL;
265 	size_t language_len, spelling_len = 0, jargon_len = 0, encoding_len = 0;
266 	zend_long mode = Z_L(0),  speed = Z_L(0);
267 	int argc = ZEND_NUM_ARGS();
268 	zval *ind;
269 
270 #ifdef PHP_WIN32
271 	TCHAR aspell_dir[200];
272 	TCHAR data_dir[220];
273 	TCHAR dict_dir[220];
274 	HKEY hkey;
275 	DWORD dwType,dwLen;
276 #endif
277 
278 	PspellCanHaveError *ret;
279 	PspellManager *manager;
280 	PspellConfig *config;
281 
282 	if (zend_parse_parameters(argc, "s|sssl", &language, &language_len, &spelling, &spelling_len,
283 		&jargon, &jargon_len, &encoding, &encoding_len, &mode) == FAILURE) {
284 		return;
285 	}
286 
287 	config = new_pspell_config();
288 
289 #ifdef PHP_WIN32
290 	/* If aspell was installed using installer, we should have a key
291 	 * pointing to the location of the dictionaries
292 	 */
293 	if (0 == RegOpenKey(HKEY_LOCAL_MACHINE, "Software\\Aspell", &hkey)) {
294 		LONG result;
295 		dwLen = sizeof(aspell_dir) - 1;
296 		result = RegQueryValueEx(hkey, "", NULL, &dwType, (LPBYTE)&aspell_dir, &dwLen);
297 		RegCloseKey(hkey);
298 		if (result == ERROR_SUCCESS) {
299 			strlcpy(data_dir, aspell_dir, sizeof(data_dir));
300 			strlcat(data_dir, "\\data", sizeof(data_dir));
301 			strlcpy(dict_dir, aspell_dir, sizeof(dict_dir));
302 			strlcat(dict_dir, "\\dict", sizeof(dict_dir));
303 
304 			pspell_config_replace(config, "data-dir", data_dir);
305 			pspell_config_replace(config, "dict-dir", dict_dir);
306 		}
307 	}
308 #endif
309 
310 	pspell_config_replace(config, "language-tag", language);
311 
312 	if (spelling_len) {
313 		pspell_config_replace(config, "spelling", spelling);
314 	}
315 
316 	if (jargon_len) {
317 		pspell_config_replace(config, "jargon", jargon);
318 	}
319 
320 	if (encoding_len) {
321 		pspell_config_replace(config, "encoding", encoding);
322 	}
323 
324 	if (argc > 4) {
325 		speed = mode & PSPELL_SPEED_MASK_INTERNAL;
326 
327 		/* First check what mode we want (how many suggestions) */
328 		if (speed == PSPELL_FAST) {
329 			pspell_config_replace(config, "sug-mode", "fast");
330 		} else if (speed == PSPELL_NORMAL) {
331 			pspell_config_replace(config, "sug-mode", "normal");
332 		} else if (speed == PSPELL_BAD_SPELLERS) {
333 			pspell_config_replace(config, "sug-mode", "bad-spellers");
334 		}
335 
336 		/* Then we see if run-together words should be treated as valid components */
337 		if (mode & PSPELL_RUN_TOGETHER) {
338 			pspell_config_replace(config, "run-together", "true");
339 		}
340 	}
341 
342 	ret = new_pspell_manager(config);
343 	delete_pspell_config(config);
344 
345 	if (pspell_error_number(ret) != 0) {
346 		php_error_docref(NULL, E_WARNING, "PSPELL couldn't open the dictionary. reason: %s", pspell_error_message(ret));
347 		delete_pspell_can_have_error(ret);
348 		RETURN_FALSE;
349 	}
350 
351 	manager = to_pspell_manager(ret);
352 	ind = zend_list_insert(manager, le_pspell);
353 	RETURN_LONG(Z_RES_HANDLE_P(ind));
354 }
355 /* }}} */
356 
357 /* {{{ proto int pspell_new_personal(string personal, string language [, string spelling [, string jargon [, string encoding [, int mode]]]])
358    Load a dictionary with a personal wordlist*/
PHP_FUNCTION(pspell_new_personal)359 static PHP_FUNCTION(pspell_new_personal)
360 {
361 	char *personal, *language, *spelling = NULL, *jargon = NULL, *encoding = NULL;
362 	size_t personal_len, language_len, spelling_len = 0, jargon_len = 0, encoding_len = 0;
363 	zend_long mode = Z_L(0),  speed = Z_L(0);
364 	int argc = ZEND_NUM_ARGS();
365 	zval *ind;
366 
367 #ifdef PHP_WIN32
368 	TCHAR aspell_dir[200];
369 	TCHAR data_dir[220];
370 	TCHAR dict_dir[220];
371 	HKEY hkey;
372 	DWORD dwType,dwLen;
373 #endif
374 
375 	PspellCanHaveError *ret;
376 	PspellManager *manager;
377 	PspellConfig *config;
378 
379 	if (zend_parse_parameters(argc, "ps|sssl", &personal, &personal_len, &language, &language_len,
380 		&spelling, &spelling_len, &jargon, &jargon_len, &encoding, &encoding_len, &mode) == FAILURE) {
381 		return;
382 	}
383 
384 	config = new_pspell_config();
385 
386 #ifdef PHP_WIN32
387 	/* If aspell was installed using installer, we should have a key
388 	 * pointing to the location of the dictionaries
389 	 */
390 	if (0 == RegOpenKey(HKEY_LOCAL_MACHINE, "Software\\Aspell", &hkey)) {
391 		LONG result;
392 		dwLen = sizeof(aspell_dir) - 1;
393 		result = RegQueryValueEx(hkey, "", NULL, &dwType, (LPBYTE)&aspell_dir, &dwLen);
394 		RegCloseKey(hkey);
395 		if (result == ERROR_SUCCESS) {
396 			strlcpy(data_dir, aspell_dir, sizeof(data_dir));
397 			strlcat(data_dir, "\\data", sizeof(data_dir));
398 			strlcpy(dict_dir, aspell_dir, sizeof(dict_dir));
399 			strlcat(dict_dir, "\\dict", sizeof(dict_dir));
400 
401 			pspell_config_replace(config, "data-dir", data_dir);
402 			pspell_config_replace(config, "dict-dir", dict_dir);
403 		}
404 	}
405 #endif
406 
407 	if (php_check_open_basedir(personal)) {
408 		delete_pspell_config(config);
409 		RETURN_FALSE;
410 	}
411 
412 	pspell_config_replace(config, "personal", personal);
413 	pspell_config_replace(config, "save-repl", "false");
414 
415 	pspell_config_replace(config, "language-tag", language);
416 
417 	if (spelling_len) {
418 		pspell_config_replace(config, "spelling", spelling);
419 	}
420 
421 	if (jargon_len) {
422 		pspell_config_replace(config, "jargon", jargon);
423 	}
424 
425 	if (encoding_len) {
426 		pspell_config_replace(config, "encoding", encoding);
427 	}
428 
429 	if (argc > 5) {
430 		speed = mode & PSPELL_SPEED_MASK_INTERNAL;
431 
432 		/* First check what mode we want (how many suggestions) */
433 		if (speed == PSPELL_FAST) {
434 			pspell_config_replace(config, "sug-mode", "fast");
435 		} else if (speed == PSPELL_NORMAL) {
436 			pspell_config_replace(config, "sug-mode", "normal");
437 		} else if (speed == PSPELL_BAD_SPELLERS) {
438 			pspell_config_replace(config, "sug-mode", "bad-spellers");
439 		}
440 
441 		/* Then we see if run-together words should be treated as valid components */
442 		if (mode & PSPELL_RUN_TOGETHER) {
443 			pspell_config_replace(config, "run-together", "true");
444 		}
445 	}
446 
447 	ret = new_pspell_manager(config);
448 	delete_pspell_config(config);
449 
450 	if (pspell_error_number(ret) != 0) {
451 		php_error_docref(NULL, E_WARNING, "PSPELL couldn't open the dictionary. reason: %s", pspell_error_message(ret));
452 		delete_pspell_can_have_error(ret);
453 		RETURN_FALSE;
454 	}
455 
456 	manager = to_pspell_manager(ret);
457 	ind = zend_list_insert(manager, le_pspell);
458 	RETURN_LONG(Z_RES_HANDLE_P(ind));
459 }
460 /* }}} */
461 
462 /* {{{ proto int pspell_new_config(int config)
463    Load a dictionary based on the given config */
PHP_FUNCTION(pspell_new_config)464 static PHP_FUNCTION(pspell_new_config)
465 {
466 	zend_long conf;
467 	zval *ind;
468 	PspellCanHaveError *ret;
469 	PspellManager *manager;
470 	PspellConfig *config;
471 
472 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &conf) == FAILURE) {
473 		return;
474 	}
475 
476 	PSPELL_FETCH_CONFIG;
477 
478 	ret = new_pspell_manager(config);
479 
480 	if (pspell_error_number(ret) != 0) {
481 		php_error_docref(NULL, E_WARNING, "PSPELL couldn't open the dictionary. reason: %s", pspell_error_message(ret));
482 		delete_pspell_can_have_error(ret);
483 		RETURN_FALSE;
484 	}
485 
486 	manager = to_pspell_manager(ret);
487 	ind = zend_list_insert(manager, le_pspell);
488 	RETURN_LONG(Z_RES_HANDLE_P(ind));
489 }
490 /* }}} */
491 
492 /* {{{ proto bool pspell_check(int pspell, string word)
493    Returns true if word is valid */
PHP_FUNCTION(pspell_check)494 static PHP_FUNCTION(pspell_check)
495 {
496 	size_t word_len;
497 	zend_long scin;
498 	char *word;
499 	PspellManager *manager;
500 
501 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "ls", &scin, &word, &word_len) == FAILURE) {
502 		return;
503 	}
504 
505 	PSPELL_FETCH_MANAGER;
506 
507 	if (pspell_manager_check(manager, word)) {
508 		RETURN_TRUE;
509 	} else {
510 		RETURN_FALSE;
511 	}
512 }
513 /* }}} */
514 
515 /* {{{ proto array pspell_suggest(int pspell, string word)
516    Returns array of suggestions */
PHP_FUNCTION(pspell_suggest)517 static PHP_FUNCTION(pspell_suggest)
518 {
519 	zend_long scin;
520 	char *word;
521 	size_t word_len;
522 	PspellManager *manager;
523 	const PspellWordList *wl;
524 	const char *sug;
525 
526 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "ls", &scin, &word, &word_len) == FAILURE) {
527 		return;
528 	}
529 
530 	PSPELL_FETCH_MANAGER;
531 
532 	array_init(return_value);
533 
534 	wl = pspell_manager_suggest(manager, word);
535 	if (wl) {
536 		PspellStringEmulation *els = pspell_word_list_elements(wl);
537 		while ((sug = pspell_string_emulation_next(els)) != 0) {
538 			add_next_index_string(return_value,(char *)sug);
539 		}
540 		delete_pspell_string_emulation(els);
541 	} else {
542 		php_error_docref(NULL, E_WARNING, "PSPELL had a problem. details: %s", pspell_manager_error_message(manager));
543 		RETURN_FALSE;
544 	}
545 }
546 /* }}} */
547 
548 /* {{{ proto bool pspell_store_replacement(int pspell, string misspell, string correct)
549    Notify the dictionary of a user-selected replacement */
PHP_FUNCTION(pspell_store_replacement)550 static PHP_FUNCTION(pspell_store_replacement)
551 {
552 	size_t miss_len, corr_len;
553 	zend_long scin;
554 	char *miss, *corr;
555 	PspellManager *manager;
556 
557 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "lss", &scin, &miss, &miss_len, &corr, &corr_len) == FAILURE) {
558 		return;
559 	}
560 
561 	PSPELL_FETCH_MANAGER;
562 
563 	pspell_manager_store_replacement(manager, miss, corr);
564 	if (pspell_manager_error_number(manager) == 0) {
565 		RETURN_TRUE;
566 	} else {
567 		php_error_docref(NULL, E_WARNING, "pspell_store_replacement() gave error: %s", pspell_manager_error_message(manager));
568 		RETURN_FALSE;
569 	}
570 }
571 /* }}} */
572 
573 /* {{{ proto bool pspell_add_to_personal(int pspell, string word)
574    Adds a word to a personal list */
PHP_FUNCTION(pspell_add_to_personal)575 static PHP_FUNCTION(pspell_add_to_personal)
576 {
577 	size_t word_len;
578 	zend_long scin;
579 	char *word;
580 	PspellManager *manager;
581 
582 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "ls", &scin, &word, &word_len) == FAILURE) {
583 		return;
584 	}
585 
586 	PSPELL_FETCH_MANAGER;
587 
588 	/*If the word is empty, we have to return; otherwise we'll segfault! ouch!*/
589 	if (word_len == 0) {
590 		RETURN_FALSE;
591 	}
592 
593 	pspell_manager_add_to_personal(manager, word);
594 	if (pspell_manager_error_number(manager) == 0) {
595 		RETURN_TRUE;
596 	} else {
597 		php_error_docref(NULL, E_WARNING, "pspell_add_to_personal() gave error: %s", pspell_manager_error_message(manager));
598 		RETURN_FALSE;
599 	}
600 }
601 /* }}} */
602 
603 /* {{{ proto bool pspell_add_to_session(int pspell, string word)
604    Adds a word to the current session */
PHP_FUNCTION(pspell_add_to_session)605 static PHP_FUNCTION(pspell_add_to_session)
606 {
607 	size_t word_len;
608 	zend_long scin;
609 	char *word;
610 	PspellManager *manager;
611 
612 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "ls", &scin, &word, &word_len) == FAILURE) {
613 		return;
614 	}
615 
616 	PSPELL_FETCH_MANAGER;
617 
618 	/*If the word is empty, we have to return; otherwise we'll segfault! ouch!*/
619 	if (word_len == 0) {
620 		RETURN_FALSE;
621 	}
622 
623 	pspell_manager_add_to_session(manager, word);
624 	if (pspell_manager_error_number(manager) == 0) {
625 		RETURN_TRUE;
626 	} else {
627 		php_error_docref(NULL, E_WARNING, "pspell_add_to_session() gave error: %s", pspell_manager_error_message(manager));
628 		RETURN_FALSE;
629 	}
630 }
631 /* }}} */
632 
633 /* {{{ proto bool pspell_clear_session(int pspell)
634    Clears the current session */
PHP_FUNCTION(pspell_clear_session)635 static PHP_FUNCTION(pspell_clear_session)
636 {
637 	zend_long scin;
638 	PspellManager *manager;
639 
640 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &scin) == FAILURE) {
641 		return;
642 	}
643 
644 	PSPELL_FETCH_MANAGER;
645 
646 	pspell_manager_clear_session(manager);
647 	if (pspell_manager_error_number(manager) == 0) {
648 		RETURN_TRUE;
649 	} else {
650 		php_error_docref(NULL, E_WARNING, "pspell_clear_session() gave error: %s", pspell_manager_error_message(manager));
651 		RETURN_FALSE;
652 	}
653 }
654 /* }}} */
655 
656 /* {{{ proto bool pspell_save_wordlist(int pspell)
657    Saves the current (personal) wordlist */
PHP_FUNCTION(pspell_save_wordlist)658 static PHP_FUNCTION(pspell_save_wordlist)
659 {
660 	zend_long scin;
661 	PspellManager *manager;
662 
663 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &scin) == FAILURE) {
664 		return;
665 	}
666 
667 	PSPELL_FETCH_MANAGER;
668 
669 	pspell_manager_save_all_word_lists(manager);
670 
671 	if (pspell_manager_error_number(manager) == 0) {
672 		RETURN_TRUE;
673 	} else {
674 		php_error_docref(NULL, E_WARNING, "pspell_save_wordlist() gave error: %s", pspell_manager_error_message(manager));
675 		RETURN_FALSE;
676 	}
677 
678 }
679 /* }}} */
680 
681 /* {{{ proto int pspell_config_create(string language [, string spelling [, string jargon [, string encoding]]])
682    Create a new config to be used later to create a manager */
PHP_FUNCTION(pspell_config_create)683 static PHP_FUNCTION(pspell_config_create)
684 {
685 	char *language, *spelling = NULL, *jargon = NULL, *encoding = NULL;
686 	size_t language_len, spelling_len = 0, jargon_len = 0, encoding_len = 0;
687 	zval *ind;
688 	PspellConfig *config;
689 
690 #ifdef PHP_WIN32
691 	TCHAR aspell_dir[200];
692 	TCHAR data_dir[220];
693 	TCHAR dict_dir[220];
694 	HKEY hkey;
695 	DWORD dwType,dwLen;
696 #endif
697 
698 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|sss", &language, &language_len, &spelling, &spelling_len,
699 		&jargon, &jargon_len, &encoding, &encoding_len) == FAILURE) {
700 		return;
701 	}
702 
703 	config = new_pspell_config();
704 
705 #ifdef PHP_WIN32
706     /* If aspell was installed using installer, we should have a key
707      * pointing to the location of the dictionaries
708      */
709 	if (0 == RegOpenKey(HKEY_LOCAL_MACHINE, "Software\\Aspell", &hkey)) {
710 		LONG result;
711 		dwLen = sizeof(aspell_dir) - 1;
712 		result = RegQueryValueEx(hkey, "", NULL, &dwType, (LPBYTE)&aspell_dir, &dwLen);
713 		RegCloseKey(hkey);
714 		if (result == ERROR_SUCCESS) {
715 			strlcpy(data_dir, aspell_dir, sizeof(data_dir));
716 			strlcat(data_dir, "\\data", sizeof(data_dir));
717 			strlcpy(dict_dir, aspell_dir, sizeof(dict_dir));
718 			strlcat(dict_dir, "\\dict", sizeof(dict_dir));
719 
720 			pspell_config_replace(config, "data-dir", data_dir);
721 			pspell_config_replace(config, "dict-dir", dict_dir);
722 		}
723 	}
724 #endif
725 
726 	pspell_config_replace(config, "language-tag", language);
727 
728  	if (spelling_len) {
729 		pspell_config_replace(config, "spelling", spelling);
730 	}
731 
732 	if (jargon_len) {
733 		pspell_config_replace(config, "jargon", jargon);
734 	}
735 
736 	if (encoding_len) {
737 		pspell_config_replace(config, "encoding", encoding);
738 	}
739 
740 	/* By default I do not want to write anything anywhere because it'll try to write to $HOME
741 	which is not what we want */
742 	pspell_config_replace(config, "save-repl", "false");
743 
744 	ind = zend_list_insert(config, le_pspell_config);
745 	RETURN_LONG(Z_RES_HANDLE_P(ind));
746 }
747 /* }}} */
748 
749 /* {{{ proto bool pspell_config_runtogether(int conf, bool runtogether)
750    Consider run-together words as valid components */
PHP_FUNCTION(pspell_config_runtogether)751 static PHP_FUNCTION(pspell_config_runtogether)
752 {
753 	zend_long conf;
754 	zend_bool runtogether;
755 	PspellConfig *config;
756 
757 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "lb", &conf, &runtogether) == FAILURE) {
758 		return;
759 	}
760 
761 	PSPELL_FETCH_CONFIG;
762 
763 	pspell_config_replace(config, "run-together", runtogether ? "true" : "false");
764 
765 	RETURN_TRUE;
766 }
767 /* }}} */
768 
769 /* {{{ proto bool pspell_config_mode(int conf, int mode)
770    Select mode for config (PSPELL_FAST, PSPELL_NORMAL or PSPELL_BAD_SPELLERS) */
PHP_FUNCTION(pspell_config_mode)771 static PHP_FUNCTION(pspell_config_mode)
772 {
773 	zend_long conf, mode;
774 	PspellConfig *config;
775 
776 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "ll", &conf, &mode) == FAILURE) {
777 		return;
778 	}
779 
780 	PSPELL_FETCH_CONFIG;
781 
782 	/* First check what mode we want (how many suggestions) */
783 	if (mode == PSPELL_FAST) {
784 		pspell_config_replace(config, "sug-mode", "fast");
785 	} else if (mode == PSPELL_NORMAL) {
786 		pspell_config_replace(config, "sug-mode", "normal");
787 	} else if (mode == PSPELL_BAD_SPELLERS) {
788 		pspell_config_replace(config, "sug-mode", "bad-spellers");
789 	}
790 
791 	RETURN_TRUE;
792 }
793 /* }}} */
794 
795 /* {{{ proto bool pspell_config_ignore(int conf, int ignore)
796    Ignore words <= n chars */
PHP_FUNCTION(pspell_config_ignore)797 static PHP_FUNCTION(pspell_config_ignore)
798 {
799 	char ignore_str[MAX_LENGTH_OF_LONG + 1];
800 	zend_long conf, ignore = 0L;
801 	PspellConfig *config;
802 
803 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "ll", &conf, &ignore) == FAILURE) {
804 		return;
805 	}
806 
807 	PSPELL_FETCH_CONFIG;
808 
809 	snprintf(ignore_str, sizeof(ignore_str), ZEND_LONG_FMT, ignore);
810 
811 	pspell_config_replace(config, "ignore", ignore_str);
812 	RETURN_TRUE;
813 }
814 /* }}} */
815 
pspell_config_path(INTERNAL_FUNCTION_PARAMETERS,char * option)816 static void pspell_config_path(INTERNAL_FUNCTION_PARAMETERS, char *option)
817 {
818 	zend_long conf;
819 	char *value;
820 	size_t value_len;
821 	PspellConfig *config;
822 
823 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "lp", &conf, &value, &value_len) == FAILURE) {
824 		return;
825 	}
826 
827 	PSPELL_FETCH_CONFIG;
828 
829 	if (php_check_open_basedir(value)) {
830 		RETURN_FALSE;
831 	}
832 
833 	pspell_config_replace(config, option, value);
834 
835 	RETURN_TRUE;
836 }
837 
838 /* {{{ proto bool pspell_config_personal(int conf, string personal)
839    Use a personal dictionary for this config */
PHP_FUNCTION(pspell_config_personal)840 static PHP_FUNCTION(pspell_config_personal)
841 {
842 	pspell_config_path(INTERNAL_FUNCTION_PARAM_PASSTHRU, "personal");
843 }
844 /* }}} */
845 
846 /* {{{ proto bool pspell_config_dict_dir(int conf, string directory)
847    location of the main word list */
PHP_FUNCTION(pspell_config_dict_dir)848 static PHP_FUNCTION(pspell_config_dict_dir)
849 {
850 	pspell_config_path(INTERNAL_FUNCTION_PARAM_PASSTHRU, "dict-dir");
851 }
852 /* }}} */
853 
854 /* {{{ proto bool pspell_config_data_dir(int conf, string directory)
855     location of language data files */
PHP_FUNCTION(pspell_config_data_dir)856 static PHP_FUNCTION(pspell_config_data_dir)
857 {
858 	pspell_config_path(INTERNAL_FUNCTION_PARAM_PASSTHRU, "data-dir");
859 }
860 /* }}} */
861 
862 /* {{{ proto bool pspell_config_repl(int conf, string repl)
863    Use a personal dictionary with replacement pairs for this config */
PHP_FUNCTION(pspell_config_repl)864 static PHP_FUNCTION(pspell_config_repl)
865 {
866 	zend_long conf;
867 	char *repl;
868 	size_t repl_len;
869 	PspellConfig *config;
870 
871 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "lp", &conf, &repl, &repl_len) == FAILURE) {
872 		return;
873 	}
874 
875 	PSPELL_FETCH_CONFIG;
876 
877 	pspell_config_replace(config, "save-repl", "true");
878 
879 	if (php_check_open_basedir(repl)) {
880 		RETURN_FALSE;
881 	}
882 
883 	pspell_config_replace(config, "repl", repl);
884 
885 	RETURN_TRUE;
886 }
887 /* }}} */
888 
889 /* {{{ proto bool pspell_config_save_repl(int conf, bool save)
890    Save replacement pairs when personal list is saved for this config */
PHP_FUNCTION(pspell_config_save_repl)891 static PHP_FUNCTION(pspell_config_save_repl)
892 {
893 	zend_long conf;
894 	zend_bool save;
895 	PspellConfig *config;
896 
897 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "lb", &conf, &save) == FAILURE) {
898 		return;
899 	}
900 
901 	PSPELL_FETCH_CONFIG;
902 
903 	pspell_config_replace(config, "save-repl", save ? "true" : "false");
904 
905 	RETURN_TRUE;
906 }
907 /* }}} */
908 
909 /* {{{ PHP_MINFO_FUNCTION
910  */
PHP_MINFO_FUNCTION(pspell)911 static PHP_MINFO_FUNCTION(pspell)
912 {
913 	php_info_print_table_start();
914 	php_info_print_table_row(2, "PSpell Support", "enabled");
915 	php_info_print_table_end();
916 }
917 /* }}} */
918 
919 #endif
920 
921 /*
922  * Local variables:
923  * tab-width: 4
924  * c-basic-offset: 4
925  * End:
926  * vim600: sw=4 ts=4 fdm=marker
927  * vim<600: sw=4 ts=4
928  */
929