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