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