8 #include "ext/standard/info.h"
9 #include "Zend/zend_exceptions.h"
10 #include "php_bbcode.h"
12 /* XXX: only required for php_debug_zval_dump */
13 #include "ext/standard/php_var.h"
15 /* XXX: required for pcre_cache_entry */
16 #include "ext/pcre/php_pcre.h"
18 /* If you declare any globals in php_bbcode.h uncomment this:
19 ZEND_DECLARE_MODULE_GLOBALS(bbcode)
22 typedef struct _bbcode_object
{
30 /* True global resources - no need for thread safety here */
31 zend_object_handlers bbcode_handlers
;
34 zend_class_entry
*bbcode_ce
;
36 /* BBCode exception object */
37 zend_class_entry
*bbcode_exception_ce
;
39 /* Define all constants */
40 #ifdef old_code_dropped
41 GEN(TYPE_ARG
) /*BBCODE_TYPE_ARG*/\
42 GEN(TYPE_NOARG
) /*BBCODE_TYPE_NOARG*/\
43 GEN(TYPE_OPTARG
) /*BBCODE_TYPE_OPTARG*/\
47 #define BBCODE_DEF(GEN) \
49 GEN(TYPE_ROOT) /*BBCODE_TYPE_ROOT*/\
50 GEN(TYPE_MULTI) /*BBCODE_TYPE_ARG|BBCODE_TYPE_OPTARG|BBCODE_TYPE_NOARG*/\
51 GEN(TYPE_SINGLE) /*BBCODE_TYPE_SINGLE*/\
53 /* TODO: add again each feature when implemented (before beta) */
56 GEN(QUOTE_DOUBLE
) /*BBCODE_ARG_DOUBLE_QUOTE*/\
57 GEN(QUOTE_SIMPLE
) /*BBCODE_ARG_SINGLE_QUOTE*/\
58 GEN(QUOTE_HTML
) /*BBCODE_ARG_HTML_QUOTE*/\
59 GEN(QUOTE_ESCAPE
) /*BBCODE_ARG_QUOTE_ESCAPING*/\
62 GEN(CORRECT_AUTO
) /*BBCODE_AUTO_CORRECT*/\
63 GEN(CORRECT_REOPEN
) /*BBCODE_CORRECT_REOPEN_TAGS*/\
64 GEN(CORRECT_NOTREE
) /*BBCODE_DISABLE_TREE_BUILD*/\
67 GEN(SMILEY_ON
) /*BBCODE_DEFAULT_SMILEYS_ON*/\
68 GEN(SMILEY_CI
) /*BBCODE_SMILEYS_CASE_INSENSITIVE*/\
71 GEN(CLOSE_AUTO
) /*BBCODE_FLAGS_ONE_OPEN_PER_LEVEL*/\
72 GEN(REMOVE_EMPTY
) /*BBCODE_FLAGS_REMOVE_IF_EMPTY*/\
73 GEN(REMOVE_CONTENT
) /*BBCODE_FLAGS_CDATA_NOT_ALLOWED*/\
74 GEN(REMOVE_REOPEN
) /*BBCODE_FLAGS_DENY_REOPEN_CHILD*/
78 /* Generate enum version */
79 #define BBCODE_GEN_ENUM(NAME) BBCODE_ ## NAME,
81 /* Generate zend const version */
82 #define BBCODE_GEN_CONST(NAME) zend_declare_class_constant_long(bbcode_ce, #NAME, sizeof(#NAME) - 1, BBCODE_ ## NAME TSRMLS_CC);
84 /* Defined in /usr/include/php/Zend/zend_types.h +382 */
85 #define BBCODE_GET_TYPE(TYPE) (TYPE==IS_UNDEF)?"undef":( \
86 (TYPE==IS_NULL)?"null":( \
87 (TYPE==IS_FALSE)?"false":( \
88 (TYPE==IS_TRUE)?"true":( \
89 (TYPE==IS_LONG)?"long":( \
90 (TYPE==IS_DOUBLE)?"double":( \
91 (TYPE==IS_STRING)?"string":( \
92 (TYPE==IS_ARRAY)?"array":( \
93 (TYPE==IS_OBJECT)?"object":( \
94 (TYPE==IS_RESOURCE)?"resource":( \
95 (TYPE==IS_REFERENCE)?"reference":( \
96 (TYPE==IS_CONSTANT_AST)?"constant":"unknown"\
109 /* All flags enum set */
111 BBCODE_DEF(BBCODE_GEN_ENUM
)
115 * TODO: Implement it in an ini file ? (later maybe) */
116 #define BBCODE_DEFAULT_FLAG 0
118 #define BBCODE_DEFAULT_FLAG BBCODE_QUOTE_DOUBLE|BBCODE_QUOTE_SIMPLE|BBCODE_QUOTE_ESCAPE|BBCODE_CORRECT_AUTO|BBCODE_CORRECT_REOPEN|BBCODE_SMILEY_ON|BBCODE_CLOSE_AUTO|BBCODE_REMOVE_EMPTY
122 #define BBCODE_DEBUG 0
124 /* {{{ Function prototypes (bbcode_destroy|bbcode_free|bbcode_clone|bbcode_create) */
125 static void bbcode_destroy(zend_object
*obj TSRMLS_DC
);
126 static void bbcode_free(zend_object
*obj TSRMLS_DC
);
127 static zend_object
*bbcode_clone(zval
*obj TSRMLS_DC
);
128 static zend_object
*bbcode_create(zend_class_entry
*ce TSRMLS_DC
);
131 /* {{{ static inline bbcode_object* bbcode_fetch(zend_object *obj TSRMLS_DC) {
132 * BBCode object fetch function */
133 static inline bbcode_object
* bbcode_fetch(zend_object
*obj TSRMLS_DC
) {
134 return (bbcode_object
*)((char *)obj
- XtOffsetOf(bbcode_object
, std
));
138 /* {{{ Z_BBCODE_P(zv) bbcode_fetch(Z_OBJ_P((zv)))
139 * BBCode object pointer fetch macro */
140 #define Z_BBCODE_P(zv) bbcode_fetch(Z_OBJ_P((zv)))
143 /* {{{ static void bbcode_destroy(zend_object *obj TSRMLS_DC) {
144 * BBCode object destroy call */
145 static void bbcode_destroy(zend_object
*obj TSRMLS_DC
) {
146 /* Trigger user land destructor on obj */
147 zend_objects_destroy_object(obj
);
151 /* {{{ static void bbcode_free(zend_object *obj TSRMLS_DC) {
152 * BBCode object free call */
153 static void bbcode_free(zend_object
*obj TSRMLS_DC
) {
154 /* Retrieve bbcode object from zend object */
155 bbcode_object
*bbobj
= bbcode_fetch(obj
);
157 /* Check if root is present */
159 /* Release the root string */
160 zend_string_release(bbobj
->root
);
163 /* Check if tag is present */
165 /* Release the tag hash */
166 zend_array_destroy(bbobj
->tag
);
169 /* Check if smiley is present */
171 /* Release the smiley hash */
172 zend_array_destroy(bbobj
->smiley
);
175 /* Trigger zend object std destructor on obj */
176 zend_object_std_dtor(obj
);
183 /* {{{ static zend_object *bbcode_clone(zval *obj TSRMLS_DC) {
184 * BBCode object clone call */
185 static zend_object
*bbcode_clone(zval
*obj TSRMLS_DC
) {
186 /* Fetch pointer to old bbcode object */
187 bbcode_object
*oldbbobj
= Z_BBCODE_P(obj
);
189 /* Create pointer on new obj */
190 //bbcode_object *newbbobj = bbcode_fetch(bbcode_create(oldbbobj->std.ce));
191 bbcode_object
*newbbobj
= bbcode_fetch(bbcode_create(Z_OBJ_P(obj
)->ce
));
193 /* Call members cloning function */
194 zend_objects_clone_members(&newbbobj
->std
, &oldbbobj
->std
);
196 /* Duplicate flag value */
197 newbbobj
->flag
= oldbbobj
->flag
;
199 /* Set root as null */
200 newbbobj
->root
= NULL
;
202 /* Check if root is available */
203 if (oldbbobj
->root
) {
204 /* Duplicate root zend string */
205 newbbobj
->root
= zend_string_copy(oldbbobj
->root
);
208 /* Set tag as null */
209 newbbobj
->tag
= NULL
;
211 /* Check if tag is available */
213 /* Duplicate tag zend array */
214 newbbobj
->tag
= zend_array_dup(oldbbobj
->tag
);
217 /* Set smiley as null */
218 newbbobj
->smiley
= NULL
;
220 /* Check if smiley is available */
221 if (oldbbobj
->smiley
) {
222 /* Duplicate smiley zend array */
223 newbbobj
->smiley
= zend_array_dup(oldbbobj
->smiley
);
226 /* Return the new object */
227 return &newbbobj
->std
;
231 /* {{{ static zend_object *bbcode_create(zend_class_entry *ce TSRMLS_DC) {
232 * BBCode object create call */
233 static zend_object
*bbcode_create(zend_class_entry
*ce TSRMLS_DC
) {
234 /* Allocate object */
235 bbcode_object
*obj
= (bbcode_object
*)ecalloc(1, sizeof(bbcode_object
) + zend_object_properties_size(ce
));
237 /* Init std member */
238 zend_object_std_init(&obj
->std
, ce TSRMLS_CC
);
240 /* Set object properties */
241 object_properties_init(&obj
->std
, ce TSRMLS_CC
);
243 /* Duplicate standard object handler */
244 memcpy(&bbcode_handlers
, zend_get_std_object_handlers(), sizeof(zend_object_handlers
));
246 /* Set object offset */
247 bbcode_handlers
.offset
= XtOffsetOf(bbcode_object
, std
);
249 /* Set destructor function */
250 bbcode_handlers
.dtor_obj
= (zend_object_dtor_obj_t
) bbcode_destroy
;
252 /* Set free function */
253 bbcode_handlers
.free_obj
= (zend_object_free_obj_t
) bbcode_free
;
255 /* Set clone function */
256 bbcode_handlers
.clone_obj
= (zend_object_clone_obj_t
) bbcode_clone
;
258 /* Assign custom handler */
259 obj
->std
.handlers
= &bbcode_handlers
;
271 obj
->flag
= BBCODE_DEFAULT_FLAG
;
273 /* Return the std member address */
278 /* {{{ ZEND_BEGIN_ARG_INFO_EX(bbcode_construct_arginfo) */
279 // ZEND_BEGIN_ARG_INFO_EX(name, _unused, return_reference, required_num_args)
280 ZEND_BEGIN_ARG_INFO_EX(bbcode_construct_arginfo
, 0, 0, 1)
281 // ZEND_ARG_ARRAY_INFO(pass_by_ref, name, allow_null)
282 ZEND_ARG_ARRAY_INFO(0, tag
, 0)
283 ZEND_ARG_ARRAY_INFO(0, smiley
, 1)
284 // ZEND_ARG_INFO(pass_by_ref, name)
285 ZEND_ARG_INFO(0, flag
)
289 /* {{{ ZEND_BEGIN_ARG_INFO_EX(bbcode_parse_arginfo) */
290 // ZEND_BEGIN_ARG_INFO_EX(name, _unused, return_reference, required_num_args)
291 ZEND_BEGIN_ARG_INFO_EX(bbcode_parse_arginfo
, 0, 0, 1)
292 // ZEND_ARG_INFO(pass_by_ref, name)
293 ZEND_ARG_INFO(0, str
)
297 /* {{{ static HashTable *bbcode_gen_child(HashTable *ret, HashTable *tag, zend_string *cur TSRMLS_DC) { */
298 static HashTable
*bbcode_gen_child(HashTable
*ret
, HashTable
*tag
, zend_string
*cur TSRMLS_DC
) {
304 /* Iterate on each key val until hash end is reached */
305 ZEND_HASH_FOREACH_STR_KEY_VAL(tag
, key
, val
) {
306 /* Check if key is not a string, value is null or not an array */
307 if (key
== NULL
|| val
== NULL
|| Z_TYPE_P(val
) != IS_ARRAY
) {
308 /* Return failure, useless to generate a hash when we will trigger an error later */
315 /* Check that we have no type or that it's not root type */
316 /* XXX: it's not possible to have in child the root tag */
317 if ((type
= zend_hash_str_find(Z_ARR_P(val
), "type", strlen("type"))) == NULL
|| Z_LVAL_P(type
) != BBCODE_TYPE_ROOT
) {
318 /* Check that we are not on cur key */
319 /* XXX: not really required, but it's stupid to allow same tag in child */
320 //if (strcmp(ZSTR_VAL(cur), ZSTR_VAL(key)) != 0) {
321 /* Init parent val */
324 /* Init toInsert bool */
325 unsigned char toInsert
= 0;
327 /* Check if we find parent key */
328 if ((parent
= zend_hash_str_find(Z_ARR_P(val
), "parent", strlen("parent"))) != NULL
) {
332 /* Iterate on each val until hash end is reached */
333 ZEND_HASH_FOREACH_VAL(Z_ARR_P(parent
), pval
) {
334 /* Check that parent val is a string */
335 if (Z_TYPE_P(pval
) != IS_STRING
) {
339 /* Check that tkey is a string and is identical */
340 if (strcmp(Z_STRVAL_P(pval
), ZSTR_VAL(cur
)) == 0) {
343 } ZEND_HASH_FOREACH_END();
355 ZVAL_STR(&cval
, key
);
357 /* Insert the child val in return array */
358 ZEND_ASSERT(zend_hash_next_index_insert_new(ret
, &cval
) != NULL
);
362 } ZEND_HASH_FOREACH_END();
369 /* {{{ static HashTable *bbcode_gen_parent(HashTable *ret, HashTable *tag, zend_string *cur TSRMLS_DC) { */
370 static HashTable
*bbcode_gen_parent(HashTable
*ret
, HashTable
*tag
, zend_string
*cur TSRMLS_DC
) {
376 /* Iterate on each key val until hash end is reached */
377 ZEND_HASH_FOREACH_STR_KEY_VAL(tag
, key
, val
) {
378 /* Check if key is not a string, value is null or not an array */
379 if (key
== NULL
|| val
== NULL
|| Z_TYPE_P(val
) != IS_ARRAY
) {
380 /* Return failure, useless to generate a hash when we will trigger an error later */
387 /* Check that we have no type or that it's not single type */
388 /* XXX: it's not possible to have in child the root tag */
389 if ((type
= zend_hash_str_find(Z_ARR_P(val
), "type", strlen("type"))) == NULL
|| Z_LVAL_P(type
) != BBCODE_TYPE_SINGLE
) {
390 /* Check that we are not on cur key */
391 /* XXX: not really required, but it's stupid to allow same tag in child */
392 //if (strcmp(ZSTR_VAL(cur), ZSTR_VAL(key)) != 0) {
396 /* Init toInsert bool */
397 unsigned char toInsert
= 0;
399 /* Check if we find child key */
400 if ((child
= zend_hash_str_find(Z_ARR_P(val
), "child", strlen("child"))) != NULL
) {
404 /* Iterate on each val until hash end is reached */
405 ZEND_HASH_FOREACH_VAL(Z_ARR_P(child
), cval
) {
406 /* Check that child val is a string */
407 if (Z_TYPE_P(cval
) != IS_STRING
) {
411 /* Check that tkey is a string and is identical */
412 if (strcmp(Z_STRVAL_P(cval
), ZSTR_VAL(cur
)) == 0) {
415 } ZEND_HASH_FOREACH_END();
423 /* Init parent val */
427 ZVAL_STR(&pval
, key
);
429 /* Insert the parent val in return array */
430 ZEND_ASSERT(zend_hash_next_index_insert_new(ret
, &pval
) != NULL
);
434 } ZEND_HASH_FOREACH_END();
441 /* {{{ static zend_bool *bbcode_check_tag(HashTable *tag TSRMLS_DC) {
442 * Check that tag hash is valid */
443 static zend_string
*bbcode_check_tag(HashTable
*tag TSRMLS_DC
) {
444 /* Key numeric value */
446 /* Key and root string */
447 zend_string
*key
, *root
= NULL
;
457 /* Iterate on each key val until hash end is reached */
458 ZEND_HASH_FOREACH_KEY_VAL(tag
, idx
, key
, val
) {
459 /* Check if key is not a string */
461 /* Display error about long key */
462 php_error_docref(NULL TSRMLS_CC
, E_WARNING
, "Key %ld[%d] from argument tag is not an expected string", idx
, pos
);
465 /* Check that key do not contains a special character */
466 } else if (strchr(ZSTR_VAL(key
), '=') != NULL
|| strchr(ZSTR_VAL(key
), '[') != NULL
|| strchr(ZSTR_VAL(key
), ']') != NULL
|| strchr(ZSTR_VAL(key
), '(') != NULL
|| strchr(ZSTR_VAL(key
), ')') != NULL
|| strchr(ZSTR_VAL(key
), '/') != NULL
|| strchr(ZSTR_VAL(key
), '\\') != NULL
) {
467 /* Display error about key containing a special char */
468 php_error_docref(NULL TSRMLS_CC
, E_WARNING
, "Key %s[%d] from argument tag contains a special forbidden character: =[]()/\\", ZSTR_VAL(key
), pos
);
473 /* Check that value exists */
475 /* Display error about long key */
476 php_error_docref(NULL TSRMLS_CC
, E_WARNING
, "Value for key %s[%d] from argument tag is NULL instead of expected array", ZSTR_VAL(key
), pos
);
479 /* Check that value is an array */
480 } else if (Z_TYPE_P(val
) != IS_ARRAY
) {
481 /* Display error about long key */
482 php_error_docref(NULL TSRMLS_CC
, E_WARNING
, "Value for key %s[%d] from argument tag is %s[%d] instead of expected array", ZSTR_VAL(key
), pos
, BBCODE_GET_TYPE(Z_TYPE_P(val
)), Z_TYPE_P(val
));
487 /* Store child tag array */
488 HashTable
*ctag
= Z_ARR_P(val
);
489 /* Child key numeric value */
491 /* Child key string */
499 /* Child key position */
500 int cpos
= 0, type
, len
;
502 /* Detect if entry has a type, parent, child, open, close, default or arg key to display error if mandatory field is missing */
503 unsigned char hasType
= 0, hasParent
= 0, hasChild
= 0, hasOpen
= 0, hasClose
= 0, hasDefault
= 0, hasArg
= 0;
505 /* Iterate on each ckey cval until hash end is reached */
506 ZEND_HASH_FOREACH_KEY_VAL(ctag
, cidx
, ckey
, cval
) {
507 /* Check if ckey is a string */
509 /* Display error about long ckey */
510 php_error_docref(NULL TSRMLS_CC
, E_WARNING
, "Key %s[%d]/%ld[%d] from argument tag is not an expected string", ZSTR_VAL(key
), pos
, cidx
, cpos
);
515 /* Check if ckey is empty */
516 if (ZSTR_LEN(ckey
) == 0) {
517 /* Display error about long key */
518 php_error_docref(NULL TSRMLS_CC
, E_WARNING
, "Key %s[%d]/%s[%d] from argument tag is empty not one of expected type|parent|child|open|close|default string", ZSTR_VAL(key
), pos
, ZSTR_VAL(ckey
), cpos
);
521 /* Check if current ckey is type */
522 } else if (strcmp("type", ZSTR_VAL(ckey
)) == 0) {
524 type
= Z_LVAL_P(cval
);
526 /* Check that current type is valid */
528 type
!= BBCODE_TYPE_ROOT
&&
529 type
!= BBCODE_TYPE_MULTI
&&
530 type
!= BBCODE_TYPE_SINGLE
532 /* Display error about unexpected type value */
533 php_error_docref(NULL TSRMLS_CC
, E_WARNING
, "Value '%d' for key %s[%d]/%s[%d] from argument tag is not one of expected BBCODE::TYPE_(ROOT|MULTI|SINGLE) constant", type
, ZSTR_VAL(key
), pos
, ZSTR_VAL(ckey
), cpos
);
537 } else if (type
== BBCODE_TYPE_ROOT
) {
544 /* Check if current key is parent */
545 } else if (strcmp("parent", ZSTR_VAL(ckey
)) == 0) {
546 /* Check that value exists */
548 /* Display error about long key */
549 php_error_docref(NULL TSRMLS_CC
, E_WARNING
, "Value for key %s[%d]/%s[%d] from argument tag is NULL instead of expected array", ZSTR_VAL(key
), pos
, ZSTR_VAL(ckey
), cpos
);
552 /* Check that value is an array */
553 } else if (Z_TYPE_P(cval
) != IS_ARRAY
) {
554 /* Display error about long key */
555 php_error_docref(NULL TSRMLS_CC
, E_WARNING
, "Value for key %s[%d]/%s[%d] from argument tag is %s[%d] instead of expected array", ZSTR_VAL(key
), pos
, ZSTR_VAL(ckey
), cpos
, BBCODE_GET_TYPE(Z_TYPE_P(cval
)), Z_TYPE_P(cval
));
558 /* Check that value is an non empty array */
559 } else if (zend_array_count(Z_ARR_P(cval
)) == 0) {
560 /* Display error about empty parent array */
561 php_error_docref(NULL TSRMLS_CC
, E_WARNING
, "Value for key %s[%d]/%s[%d] from argument tag is an empty array instead of expected filled array", ZSTR_VAL(key
), pos
, ZSTR_VAL(ckey
), cpos
);
569 /* Child key position */
572 /* Iterate on each val until hash end is reached */
573 ZEND_HASH_FOREACH_VAL(Z_ARR_P(cval
), pval
) {
574 /* Check that parent val is a string */
575 /* XXX: pval == NULL case is catched here too */
576 if (Z_TYPE_P(pval
) != IS_STRING
) {
577 /* Display error about not string */
578 php_error_docref(NULL TSRMLS_CC
, E_WARNING
, "Value for key %s[%d]/%s[%d]/[%d] from argument tag is %s[%d] instead of expected string", ZSTR_VAL(key
), pos
, ZSTR_VAL(ckey
), cpos
, ppos
, BBCODE_GET_TYPE(Z_TYPE_P(pval
)), Z_TYPE_P(pval
));
586 /* Reset found flag */
587 unsigned char found
= 0;
589 /* Iterate on each key until hash end is reached */
590 ZEND_HASH_FOREACH_STR_KEY(tag
, tkey
) {
591 /* Check that tkey is a string and is identical */
592 if (tkey
!= NULL
&& strcmp(Z_STRVAL_P(pval
), ZSTR_VAL(tkey
)) == 0) {
593 /* We found parent value in tag keys*/
596 } ZEND_HASH_FOREACH_END();
598 /* Check if we found the key */
600 /* Display error about long key */
601 php_error_docref(NULL TSRMLS_CC
, E_WARNING
, "Value for key %s[%d]/%s[%d]/%s[%d] from argument tag is not present in tag keys", ZSTR_VAL(key
), pos
, ZSTR_VAL(ckey
), cpos
, Z_STRVAL_P(pval
), ppos
);
608 } ZEND_HASH_FOREACH_END();
612 /* Check if current key is child */
613 } else if (strcmp("child", ZSTR_VAL(ckey
)) == 0) {
614 /* Check that value exists */
616 /* Display error about long key */
617 php_error_docref(NULL TSRMLS_CC
, E_WARNING
, "Value for key %s[%d]/%s[%d] from argument tag is NULL instead of expected array", ZSTR_VAL(key
), pos
, ZSTR_VAL(ckey
), cpos
);
620 /* Check that value is an array */
621 } else if (Z_TYPE_P(cval
) != IS_ARRAY
) {
622 /* Display error about long key */
623 php_error_docref(NULL TSRMLS_CC
, E_WARNING
, "Value for key %s[%d]/%s[%d] from argument tag is %s[%d] instead of expected array", ZSTR_VAL(key
), pos
, ZSTR_VAL(ckey
), cpos
, BBCODE_GET_TYPE(Z_TYPE_P(cval
)), Z_TYPE_P(cval
));
631 /* Child key position */
634 /* Iterate on each val until hash end is reached */
635 ZEND_HASH_FOREACH_VAL(Z_ARR_P(cval
), ccval
) {
636 /* Check that child val is a string */
637 /* XXX: ccval == NULL case is catched here too */
638 if (Z_TYPE_P(ccval
) != IS_STRING
) {
639 /* Display error about not string */
640 php_error_docref(NULL TSRMLS_CC
, E_WARNING
, "Value for key %s[%d]/%s[%d]/[%d] from argument tag is %s[%d] instead of expected string", ZSTR_VAL(key
), pos
, ZSTR_VAL(ckey
), cpos
, ccpos
, BBCODE_GET_TYPE(Z_TYPE_P(ccval
)), Z_TYPE_P(ccval
));
648 /* Reset found flag */
649 unsigned char found
= 0;
651 /* Iterate on each key until hash end is reached */
652 ZEND_HASH_FOREACH_STR_KEY(tag
, tkey
) {
653 /* Check that tkey is a string and is identical */
654 if (tkey
!= NULL
&& strcmp(Z_STRVAL_P(ccval
), ZSTR_VAL(tkey
)) == 0) {
655 /* We found child value in tag keys*/
658 } ZEND_HASH_FOREACH_END();
660 /* Check if we found the key */
662 /* Display error about long key */
663 php_error_docref(NULL TSRMLS_CC
, E_WARNING
, "Child value %s for key %s[%d]/%s[%d] is not present in tag keys", Z_STRVAL_P(ccval
), ZSTR_VAL(key
), pos
, ZSTR_VAL(ckey
), cpos
);
670 } ZEND_HASH_FOREACH_END();
674 /* Check if current key is open */
675 } else if (strcmp("open", ZSTR_VAL(ckey
)) == 0) {
678 /* Check if current key is close */
679 } else if (strcmp("close", ZSTR_VAL(ckey
)) == 0) {
682 /* Check if current key is default */
683 } else if (strcmp("default", ZSTR_VAL(ckey
)) == 0) {
686 /* Check if current key is arg */
687 } else if (strcmp("arg", ZSTR_VAL(ckey
)) == 0) {
690 /* Check if current key is unknown */
692 /* Display error about long key */
693 php_error_docref(NULL TSRMLS_CC
, E_WARNING
, "Key %s[%d]/%s[%d] from argument tag is not one of expected type|parent|child|open|close|default string", ZSTR_VAL(key
), pos
, ZSTR_VAL(ckey
), cpos
);
700 } ZEND_HASH_FOREACH_END();
702 /* Check if val array is immutable */
703 /* XXX: this strange case happen with empty val array, the zend engine don't initialize a usable hashtable, to avoid triggering a segfault later when using the array, we fix it here */
704 if ((Z_GC_FLAGS_P(val
) & GC_IMMUTABLE
) == GC_IMMUTABLE
) {
705 /* Allocate hash table */
706 /* XXX: this is required to avoid segfault in zend_hash_add when the array is immutable */
707 ALLOC_HASHTABLE(Z_ARR_P(val
));
709 /* Init hash table */
710 zend_hash_init(Z_ARR_P(val
), 0, NULL
, ZVAL_PTR_DTOR
, 0);
712 /* Copy old values in new array */
713 //XXX: disabled for now, array shoudln't need to be copied, immutable flag seems only present on empty arrays
714 //zend_hash_copy(Z_ARR_P(val), ctag, (copy_ctor_func_t) zval_add_ref);
717 /* Check if entry has no type */
718 /* TODO: make depend this code on a parse flag ? */
721 zend_string
*tkey
= zend_string_init("type", strlen("type"), 0);
726 /* Check if key is '' */
727 if (ZSTR_LEN(key
) == 0) {
728 /* Set tval value to root */
729 ZVAL_LONG(&tval
, (type
= BBCODE_TYPE_ROOT
));
733 /* Check if key is img */
734 } else if (strcmp("img", ZSTR_VAL(key
)) == 0) {
735 /* Set tval value to single */
736 ZVAL_LONG(&tval
, (type
= BBCODE_TYPE_SINGLE
));
737 /* Handle other key as multi */
739 /* Set tval value to multi */
740 ZVAL_LONG(&tval
, (type
= BBCODE_TYPE_MULTI
));
743 /* Add new type key */
744 ZEND_ASSERT(zend_hash_add(Z_ARR_P(val
), tkey
, &tval
) != NULL
);
747 zend_string_release(tkey
);
753 /* Check for root type */
754 if (type
== BBCODE_TYPE_ROOT
) {
758 /* Check if has parent */
760 /* Display error about parent entry */
761 php_error_docref(NULL TSRMLS_CC
, E_WARNING
, "Key %s[%d] from argument tag of type BBCODE::TYPE_ROOT cannot have a parent entry", ZSTR_VAL(key
), pos
);
766 /* Check if has open entry */
769 zend_string
*mkey
= zend_string_init("open", strlen("open"), 0);
774 /* Check if tag is empty */
775 if (ZSTR_LEN(key
) == 0) {
777 ZVAL_EMPTY_STRING(&mval
);
780 /* Init buffer and length */
781 buf
= (char *)malloc((len
= strlen("<>") + ZSTR_LEN(key
) + (hasArg
?strlen("%s"):0))*sizeof(char));
783 /* Generate string in */
784 ZEND_ASSERT(sprintf(buf
, "<%s%s>", ZSTR_VAL(key
), hasArg
?"%s":"") == len
);
787 ZVAL_STRINGL(&mval
, buf
, len
);
794 ZEND_ASSERT(zend_hash_add(Z_ARR_P(val
), mkey
, &mval
) != NULL
);
797 zend_string_release(mkey
);
803 /* Check if has close entry */
806 zend_string
*mkey
= zend_string_init("close", strlen("close"), 0);
811 /* Check if tag is empty */
812 if (ZSTR_LEN(key
) == 0) {
814 ZVAL_EMPTY_STRING(&mval
);
817 /* Init buffer and length */
818 buf
= (char *)malloc((len
= strlen("</>") + ZSTR_LEN(key
))*sizeof(char));
820 /* Generate string in */
821 ZEND_ASSERT(sprintf(buf
, "</%s>", ZSTR_VAL(key
)) == len
);
824 ZVAL_STRINGL(&mval
, buf
, len
);
831 ZEND_ASSERT(zend_hash_add(Z_ARR_P(val
), mkey
, &mval
) != NULL
);
834 zend_string_release(mkey
);
840 /* Check if has not default */
843 zend_string
*mkey
= zend_string_init("default", strlen("default"), 0);
849 ZVAL_STRING(&mval
, "%s");
852 ZEND_ASSERT(zend_hash_add(Z_ARR_P(val
), mkey
, &mval
) != NULL
);
855 zend_string_release(mkey
);
861 /* Compute child here */
866 /* Init new child val array */
869 /* Init hash table */
870 zend_hash_init(Z_ARR(cval
), 0, NULL
, ZVAL_PTR_DTOR
, 0);
872 /* Compute and set child here */
873 /* XXX: we don't check or trigger error inside this function as these tests are done in current loop */
874 if (bbcode_gen_child(Z_ARR(cval
), tag
, key
) != NULL
) {
875 /* Child key string */
876 zend_string
*ckey
= zend_string_init("child", strlen("child"), 0);
878 /* Add new child key */
879 ZEND_ASSERT(zend_hash_add(Z_ARR_P(val
), ckey
, &cval
) != NULL
);
882 zend_string_release(ckey
);
886 /* Child generation reached an error case */
887 /* XXX: it will trigger error later */
889 /* Release hash table */
890 zend_array_destroy(Z_ARR(cval
));
893 /* Check for multi type */
894 } else if (type
== BBCODE_TYPE_MULTI
) {
895 /* Check if has open entry */
898 zend_string
*mkey
= zend_string_init("open", strlen("open"), 0);
903 /* Check if tag is empty */
904 if (ZSTR_LEN(key
) == 0) {
906 ZVAL_EMPTY_STRING(&mval
);
909 /* Init buffer and length */
910 buf
= (char *)malloc((len
= strlen("<>") + ZSTR_LEN(key
) + (hasArg
?strlen("%s"):0))*sizeof(char));
912 /* Generate string in */
913 ZEND_ASSERT(sprintf(buf
, "<%s%s>", ZSTR_VAL(key
), hasArg
?"%s":"") == len
);
916 ZVAL_STRINGL(&mval
, buf
, len
);
923 ZEND_ASSERT(zend_hash_add(Z_ARR_P(val
), mkey
, &mval
) != NULL
);
926 zend_string_release(mkey
);
932 /* Check if has close entry */
935 zend_string
*mkey
= zend_string_init("close", strlen("close"), 0);
940 /* Check if tag is empty */
941 if (ZSTR_LEN(key
) == 0) {
943 ZVAL_EMPTY_STRING(&mval
);
946 /* Init buffer and length */
947 buf
= (char *)malloc((len
= strlen("</>") + ZSTR_LEN(key
))*sizeof(char));
949 /* Generate string in */
950 ZEND_ASSERT(sprintf(buf
, "</%s>", ZSTR_VAL(key
)) == len
);
953 ZVAL_STRINGL(&mval
, buf
, len
);
960 ZEND_ASSERT(zend_hash_add(Z_ARR_P(val
), mkey
, &mval
) != NULL
);
963 zend_string_release(mkey
);
969 /* Check if has not default */
972 zend_string
*mkey
= zend_string_init("default", strlen("default"), 0);
978 ZVAL_STRING(&mval
, "%s");
981 ZEND_ASSERT(zend_hash_add(Z_ARR_P(val
), mkey
, &mval
) != NULL
);
984 zend_string_release(mkey
);
990 /* Compute parent here */
992 /* Parent key string */
993 zend_string
*pkey
= zend_string_init("parent", strlen("parent"), 0);
998 /* Init new parent val array */
1001 /* Init hash table */
1002 zend_hash_init(Z_ARR(pval
), 0, NULL
, ZVAL_PTR_DTOR
, 0);
1004 /* Compute and set parent here */
1005 /* XXX: we don't check or trigger error inside this function as these tests are done in current loop */
1006 /* TODO: change this assert to a if() and free pval on error, error will trigger in later loop (code coverage tests to do) */
1007 ZEND_ASSERT(bbcode_gen_parent(Z_ARR(pval
), tag
, key
) != NULL
);
1009 /* Add new parent key */
1010 ZEND_ASSERT(zend_hash_add(Z_ARR_P(val
), pkey
, &pval
) != NULL
);
1013 zend_string_release(pkey
);
1019 /* Compute child here */
1024 /* Init new child val array */
1025 ZVAL_NEW_ARR(&cval
);
1027 /* Init hash table */
1028 zend_hash_init(Z_ARR(cval
), 0, NULL
, ZVAL_PTR_DTOR
, 0);
1030 /* Compute and set child here */
1031 /* XXX: we don't check or trigger error inside this function as these tests are done in current loop */
1032 if (bbcode_gen_child(Z_ARR(cval
), tag
, key
) != NULL
) {
1033 /* Child key string */
1034 zend_string
*ckey
= zend_string_init("child", strlen("child"), 0);
1036 /* Add new child key */
1037 ZEND_ASSERT(zend_hash_add(Z_ARR_P(val
), ckey
, &cval
) != NULL
);
1040 zend_string_release(ckey
);
1044 /* Child generation reached an error case */
1045 /* XXX: it will trigger error later */
1047 /* Release hash table */
1048 zend_array_destroy(Z_ARR(cval
));
1051 /* Check for single type */
1052 } else if (type
== BBCODE_TYPE_SINGLE
) {
1053 /* Check if has open entry */
1055 /* Display error about open entry */
1056 php_error_docref(NULL TSRMLS_CC
, E_WARNING
, "Key %s[%d] from argument tag of type BBCODE::TYPE_SINGLE cannot have an open entry", ZSTR_VAL(key
), pos
);
1057 /* Return failure */
1061 /* Check if has close entry */
1063 /* Display error about close entry */
1064 php_error_docref(NULL TSRMLS_CC
, E_WARNING
, "Key %s[%d] from argument tag of type BBCODE::TYPE_SINGLE cannot have a close entry", ZSTR_VAL(key
), pos
);
1065 /* Return failure */
1069 /* Check if has not default */
1072 zend_string
*skey
= zend_string_init("default", strlen("default"), 0);
1077 /* Check if key is img */
1078 if (strcmp("img", ZSTR_VAL(key
)) == 0) {
1079 /* Init buffer and length */
1080 buf
= (char *)malloc((len
= strlen("<img src=\"%s\"/>") + (hasArg
?strlen("%s"):0))*sizeof(char));
1082 /* Generate string in */
1083 ZEND_ASSERT(sprintf(buf
, "<img src=\"%%s\"%s/>", hasArg
?"%s":"") == len
);
1085 /* Init buffer and length */
1086 buf
= (char *)malloc((len
= strlen("</>") + ZSTR_LEN(key
) + (hasArg
?strlen("%s"):0))*sizeof(char));
1088 /* Generate string in */
1089 ZEND_ASSERT(sprintf(buf
, "<%s%s/>", ZSTR_VAL(key
), hasArg
?"%s":"") == len
);
1092 /* Init new value */
1093 ZVAL_STRINGL(&sval
, buf
, len
);
1099 ZEND_ASSERT(zend_hash_add(Z_ARR_P(val
), skey
, &sval
) != NULL
);
1102 zend_string_release(skey
);
1104 /* Set hasDefault */
1108 /* Check if has child */
1110 /* Display error about child entry */
1111 php_error_docref(NULL TSRMLS_CC
, E_WARNING
, "Key %s[%d] from argument tag of type BBCODE::TYPE_SINGLE cannot have a child entry", ZSTR_VAL(key
), pos
);
1112 /* Return failure */
1116 /* Compute parent here */
1118 /* Parent key string */
1119 zend_string
*pkey
= zend_string_init("parent", strlen("parent"), 0);
1124 /* Init new parent val array */
1125 ZVAL_NEW_ARR(&pval
);
1127 /* Init hash table */
1128 zend_hash_init(Z_ARR(pval
), 0, NULL
, ZVAL_PTR_DTOR
, 0);
1130 /* Compute and set parent here */
1131 /* XXX: we don't check or trigger error inside this function as these tests are done in current loop */
1132 /* TODO: change this asser to a if() and free pval on error, error will trigger in later loop (code coverage tests to do) */
1133 ZEND_ASSERT(bbcode_gen_parent(Z_ARR(pval
), tag
, key
) != NULL
);
1135 /* Add new parent key */
1136 ZEND_ASSERT(zend_hash_add(Z_ARR_P(val
), pkey
, &pval
) != NULL
);
1139 zend_string_release(pkey
);
1146 /* Generate open and close pattern for tag with child */
1148 /* Child array and value */
1149 zval
*child
, *ccval
;
1151 /* Close pattern length */
1154 /* Close pattern buf */
1157 /* Retrieve child array */
1158 /* XXX: child should exists here and be an array */
1159 ZEND_ASSERT(((child
= zend_hash_str_find(Z_ARR_P(val
), "child", strlen("child"))) != NULL
) && Z_TYPE_P(child
) == IS_ARRAY
);
1161 /* Init open pattern buffer and length */
1162 /* XXX: lagest bbcode tag length seems to be 7, we guess that 10 is enough to contain every tag and pipe */
1163 buf
= (char *)malloc((len
= strlen("/\\[()(?:=([^\\]]*))?\\]/") + zend_array_count(Z_ARR_P(child
))*10)*sizeof(char));
1165 /* Init close pattern buffer and length */
1166 /* XXX: lagest bbcode tag length seems to be 7, we guess that 10 is enough to contain every tag and pipe */
1167 buf2
= (char *)malloc((len2
= strlen("/\\[\\/()\\]/") + zend_array_count(Z_ARR_P(child
))*10)*sizeof(char));
1169 /* Init cpos and cpos2 */
1170 cpos
= 4; cpos2
= 6;
1172 /* Assign open pattern begin */
1173 ZEND_ASSERT(memcpy(buf
, &"/\\[(", 4) != NULL
);
1175 /* Assign close pattern begin */
1176 ZEND_ASSERT(memcpy(buf2
, &"/\\[\\/(", 6) != NULL
);
1178 /* Iterate on each val until hash end is reached */
1179 ZEND_HASH_FOREACH_VAL(Z_ARR_P(child
), ccval
) {
1180 /* Check that child val is a string */
1181 /* XXX: ccval should be an array here */
1182 ZEND_ASSERT(Z_TYPE_P(ccval
) == IS_STRING
);
1184 /* Init zval node and type */
1185 zval
*znode
, *ztype
;
1187 /* Init zend_long type */
1190 /* Retrieve the node */
1191 ZEND_ASSERT((znode
= zend_hash_find(tag
, Z_STR_P(ccval
))) != NULL
&& Z_TYPE_P(znode
) == IS_ARRAY
);
1193 /* Try to retrieve the type */
1194 //ZEND_ASSERT((ztype = zend_hash_str_find(Z_ARR_P(znode), "type", strlen("type"))) != NULL && Z_TYPE_P(ztype) == IS_LONG);
1195 /* XXX: the type may not yet available here, it is fixed when checking the tag itself later */
1196 if ((ztype
= zend_hash_str_find(Z_ARR_P(znode
), "type", strlen("type"))) == NULL
|| Z_TYPE_P(ztype
) != IS_LONG
) {
1197 /* Check if empty tag */
1198 if (Z_STRLEN_P(ccval
) == 0) {
1200 ltype
= BBCODE_TYPE_ROOT
;
1201 } else if (strcmp("img", Z_STRVAL_P(ccval
)) == 0) {
1203 ltype
= BBCODE_TYPE_SINGLE
;
1206 ltype
= BBCODE_TYPE_MULTI
;
1208 /* Type zval available and usable */
1210 /* Set type from zval value */
1211 ltype
= Z_LVAL_P(ztype
);
1214 /* Check if not first string to add */
1216 /* Set pipe before next tag */
1220 /* Check if not single type */
1221 if (ltype
!= BBCODE_TYPE_SINGLE
) {
1222 /* Set pipe before next tag */
1223 *(buf2
+cpos2
) = '|';
1229 /* Check if not single type */
1230 if (ltype
!= BBCODE_TYPE_SINGLE
) {
1231 /* Grow buf if necessary */
1232 if (cpos
+ Z_STRLEN_P(ccval
) + 18 > len
) {
1233 /* We resize buf to current length(cpos), the string size plus eighteen char for trailing pattern */
1234 buf
= (char *)realloc(buf
, (len
= cpos
+ Z_STRLEN_P(ccval
) + 18)*sizeof(char));
1237 /* Copy string in buf */
1238 ZEND_ASSERT(memcpy(buf
+cpos
, Z_STRVAL_P(ccval
), Z_STRLEN_P(ccval
)) != NULL
);
1241 cpos
+= Z_STRLEN_P(ccval
);
1243 /* Grow buf2 if necessary */
1244 if (cpos2
+ Z_STRLEN_P(ccval
) + 4 > len2
) {
1245 /* We resize buf2 to current length(cpos2), the string size plus four char for trailing pattern */
1246 buf2
= (char *)realloc(buf2
, (len2
= cpos2
+ Z_STRLEN_P(ccval
) + 4)*sizeof(char));
1249 /* Copy string in buf2 */
1250 ZEND_ASSERT(memcpy(buf2
+cpos2
, Z_STRVAL_P(ccval
), Z_STRLEN_P(ccval
)) != NULL
);
1253 cpos2
+= Z_STRLEN_P(ccval
);
1254 /* Single type case */
1256 /* Grow buf if necessary */
1257 if (cpos
+ Z_STRLEN_P(ccval
) + strlen("(?:\\/)?") + 18 > len
) {
1258 /* We resize buf to current length(cpos), the string size plus three plus eighteen char for trailing pattern */
1259 buf
= (char *)realloc(buf
, (len
= cpos
+ Z_STRLEN_P(ccval
) + strlen("(?:\\/)?") + 18)*sizeof(char));
1262 /* Copy string in buf */
1263 ZEND_ASSERT(memcpy(buf
+cpos
, Z_STRVAL_P(ccval
), Z_STRLEN_P(ccval
)) != NULL
);
1265 /* Append pattern in buf */
1266 ZEND_ASSERT(memcpy(buf
+cpos
+Z_STRLEN_P(ccval
), &"(?:\\/)?", strlen("(?:\\/)?")) != NULL
);
1269 cpos
+= Z_STRLEN_P(ccval
) + strlen("(?:\\/)?");
1271 } ZEND_HASH_FOREACH_END();
1273 /* Assign open pattern begin */
1274 ZEND_ASSERT(memcpy(buf
+cpos
, &")(?:=([^\\]]*))?\\]/", 18) != NULL
);
1276 /* Assign open pattern begin */
1277 ZEND_ASSERT(memcpy(buf2
+cpos2
, &")\\]/", 4) != NULL
);
1279 /* Open key string */
1280 zend_string
*okey
= zend_string_init("opattern", strlen("opattern"), 0);
1282 /* Close key string */
1283 zend_string
*ckey
= zend_string_init("cpattern", strlen("cpattern"), 0);
1285 /* Open and close value */
1288 /* Set open value */
1289 ZVAL_STRINGL(&oval
, buf
, cpos
+ 18);
1291 /* Set close value */
1292 ZVAL_STRINGL(&cval
, buf2
, cpos2
+ 4);
1295 free(buf
); free(buf2
);
1298 ZEND_ASSERT(zend_hash_add(Z_ARR_P(val
), okey
, &oval
) != NULL
);
1301 ZEND_ASSERT(zend_hash_add(Z_ARR_P(val
), ckey
, &cval
) != NULL
);
1304 zend_string_release(okey
);
1306 /* Free close key */
1307 zend_string_release(ckey
);
1312 } ZEND_HASH_FOREACH_END();
1314 /* Check if not one unique root */
1316 /* Display error about multiple root */
1317 php_error_docref(NULL TSRMLS_CC
, E_WARNING
, "Tag array has %d BBCODE::TYPE_ROOT entry instead of expected unique one", hasRoot
);
1318 /* Return failure */
1322 /* Duplicate root zend string to avoid it beeing freed */
1323 return zend_string_copy(root
);
1327 /* Return success */
1333 /* {{{ static zend_bool *bbcode_check_smiley(HashTable *smiley TSRMLS_DC) {
1334 * Check that smiley hash is valid */
1335 static zend_bool
bbcode_check_smiley(HashTable
*smiley TSRMLS_DC
) {
1336 /* Key numeric value */
1346 /* Iterate on each key val until hash end is reached */
1347 ZEND_HASH_FOREACH_KEY_VAL(smiley
, idx
, key
, val
) {
1348 /* Check that key is a string */
1350 /* Display error about long key */
1351 php_error_docref(NULL TSRMLS_CC
, E_WARNING
, "Key %ld[%d] from argument smiley is not an expected string", idx
, pos
);
1352 /* Return failure */
1356 /* Check that value exists */
1358 /* Display error about long key */
1359 php_error_docref(NULL TSRMLS_CC
, E_WARNING
, "Value for key %s[%d] from argument smiley is NULL instead of expected array", ZSTR_VAL(key
), pos
);
1360 /* Return failure */
1362 /* Check that value is not an array */
1363 } else if (Z_TYPE_P(val
) == IS_ARRAY
) {
1364 /* Display error about long key */
1365 php_error_docref(NULL TSRMLS_CC
, E_WARNING
, "Value for key %s[%d] from argument smiley is an array instead of expected string", ZSTR_VAL(key
), pos
);
1366 /* Return failure */
1372 } ZEND_HASH_FOREACH_END();
1374 /* Return success */
1379 /* {{{ PHP_METHOD(BBCode, __construct) { */
1380 PHP_METHOD(BBCode
, __construct
) {
1382 HashTable
*tag
= NULL
;
1384 /* Init smiley hash */
1385 HashTable
*smiley
= NULL
;
1387 /* Init flag long */
1388 zend_long flag
= BBCODE_DEFAULT_FLAG
;
1390 /* Zend error handling */
1391 zend_error_handling error_handling
;
1393 /* TODO: set tag array to a default value like this in case of missing/empty tag array :
1398 /* TODO: init smiley with default list ? what about img storage ?
1399 * TODO: use prefix in an ini parameter ? ini_setable ?
1400 * TODO: make sure it's not really a global and per-app param ?
1404 zend_replace_error_handling(EH_THROW
, bbcode_exception_ce
, &error_handling
);
1405 /* Parse parameters */
1406 /*if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "h|hl", &tag, &smiley, &flag) == FAILURE) { return; }*/
1407 ZEND_PARSE_PARAMETERS_START(1, 3)
1408 /* Tag arg is required */
1409 Z_PARAM_ARRAY_HT(tag
)
1410 /* We switch to optional args */
1412 /* Smiley arg is optional and may be null */
1413 Z_PARAM_ARRAY_HT_EX(smiley
, 1, 0)
1414 /* Flag arg is optional and may be null */
1416 /* End parameters parse */
1417 ZEND_PARSE_PARAMETERS_END();
1419 /* Retrieve the pointer to this object */
1420 bbcode_object
*obj
= Z_BBCODE_P(getThis());
1422 /* Verify that the pointer is leading somewhere */
1423 assert(obj
!= NULL
);
1425 /* Set flag value */
1428 /* Duplicate hash to avoid modifications on original argument */
1429 obj
->tag
= zend_array_dup(tag
);
1431 /* Save tag argument hash in current object */
1432 if ((obj
->root
= bbcode_check_tag(obj
->tag
)) == NULL
) {
1433 /* Restore error handler */
1434 zend_restore_error_handling(&error_handling
);
1436 /* Release the tag hash */
1437 zend_array_destroy(obj
->tag
);
1439 /* Free the zend object */
1440 //XXX: seems useless, it don't seems possible to null the contructor return value...
1441 ZVAL_NULL(getThis() TSRMLS_CC
);
1443 /* Free the object */
1450 /* Check if smiley argument is available */
1452 /* Duplicate hash to avoid modifications on original argument */
1453 obj
->smiley
= zend_array_dup(smiley
);
1455 /* Save smiley argument hash in current object */
1456 if (!bbcode_check_smiley(obj
->smiley
)) {
1457 /* Restore error handler */
1458 zend_restore_error_handling(&error_handling
);
1460 /* Release the root string */
1461 zend_string_release(obj
->root
);
1463 /* Release the tag hash */
1464 zend_array_destroy(obj
->tag
);
1466 /* Release the smiley hash */
1467 zend_array_destroy(obj
->smiley
);
1469 /* Free the object */
1470 //XXX: seems useless, it don't seems possible to null the contructor return value...
1471 ZVAL_NULL(getThis() TSRMLS_CC
);
1473 /* Free the object */
1481 /* Restore error handler */
1482 zend_restore_error_handling(&error_handling
);
1486 /* {{{ PHP_METHOD(BBCode, __destruct) {
1487 * XXX: this function is called by bbcode_destroy function
1489 PHP_METHOD(BBCode
, __destruct
) {
1490 //XXX: disable this code, we don't need to retrieve obj as we do nothing with userland destructor for now
1492 /* Retrieve the pointer to this object */
1493 bbcode_object
*obj
= Z_BBCODE_P(getThis());
1495 /* Verify that the pointer is leading somewhere */
1496 assert(obj
!= NULL
);
1502 //[HashTable *ret][zend_string *current][char *str][int offset][int length]
1503 static char *bbcode_parse(HashTable
*tag
, zend_string
*cur
, char *rs
, int *ros
, int *rlen
, char *str
, int len TSRMLS_DC
) {
1504 /* Check if return string is not initialized or shorter than wanted length */
1505 if (rs
== NULL
|| *rlen
- *ros
<= 1024) {
1506 /* Compute new length */
1507 *rlen
= (rs
== NULL
) ? 2048 : *rlen
+ 1024;
1509 /* Grow the return string */
1510 ZEND_ASSERT((rs
= (char *)realloc(rs
, (*rlen
)*sizeof(char))) != NULL
);
1513 //DEBUG: for reminder
1514 //str=skip0[r]string0[a=arg0]string1[/a]string2[b]string3[/b][/r]skip1
1517 /* Init zval for current node and type */
1518 zval
*znode
, *ztype
;
1520 /* Retrieve current tag hash as zval */
1521 ZEND_ASSERT((znode
= zend_hash_find(tag
, cur
)) != NULL
&& Z_TYPE_P(znode
) == IS_ARRAY
);
1523 /* Retrieve current tag type */
1524 ZEND_ASSERT((ztype
= zend_hash_str_find(Z_ARR_P(znode
), "type", strlen("type"))) != NULL
&& Z_TYPE_P(ztype
) == IS_LONG
);
1526 /* Retrieve current tag open */
1527 /* XXX: may be NULL if not present */
1528 zval
*zopen
= zend_hash_str_find(Z_ARR_P(znode
), "open", strlen("open"));
1530 /* Retrieve current tag close */
1531 /* XXX: may be NULL if not present */
1532 zval
*zclose
= zend_hash_str_find(Z_ARR_P(znode
), "close", strlen("close"));
1534 /* Retrieve current tag default */
1535 /* XXX: may be NULL if not present */
1536 zval
*zdefault
= zend_hash_str_find(Z_ARR_P(znode
), "default", strlen("default"));
1538 /* Retrieve current tag arg */
1539 /* XXX: may be NULL if not present */
1540 zval
*zarg
= zend_hash_str_find(Z_ARR_P(znode
), "arg", strlen("arg"));
1542 /* The arg zend string */
1543 zend_string
*argstr
= NULL
;
1545 /* Check if we have a not root and not empty tag */
1546 if (Z_LVAL_P(ztype
) != BBCODE_TYPE_ROOT
&& strcmp(ZSTR_VAL(cur
), "") != 0) {
1547 /* Regular expression */
1550 /* Open and close pattern */
1551 char *openstr
, *closestr
, *found
;
1553 /* Open and close pattern length */
1554 int openlen
, closelen
;
1556 /* Alloc open pattern buffer and set length */
1557 openstr
= (char *)malloc(((openlen
= strlen("/\\[\\]/") + (zarg
== NULL
? strlen("(?:=[^\\]]*)?") : strlen("(?:=([^\\]]*))?")) + ZSTR_LEN(cur
)) + 1)*sizeof(char));
1559 /* Alloc close pattern buffer and set length */
1560 closestr
= (char *)malloc(((closelen
= strlen("[/]") + ZSTR_LEN(cur
)) + 1)*sizeof(char));
1562 /* Create open tag string */
1563 ZEND_ASSERT(snprintf(openstr
, openlen
+ 1, "/\\[%s%s\\]/", ZSTR_VAL(cur
), (zarg
== NULL
? "(?:=[^\\]]*)?" : "(?:=([^\\]]*))?")) == openlen
);
1565 /* Duplicate open tag in regex */
1566 regex
= zend_string_init(openstr
, openlen
, 0);
1568 /* Free the close string */
1571 /* Compiled regular expression */
1572 pcre_cache_entry
*pce
;
1574 /* Array for subpatterns and return value */
1577 /* Init subpats array */
1578 /* XXX: if we don't initialize the array first, some strange memory corruption happen inside php_pcre_match_impl call */
1579 array_init(&subpats
);
1581 /* Compile regex or get it from cache. */
1582 /* TODO: see if we handle the error here ? (maybe later)
1583 * XXX: it seems unlikely any error could be recoverable if pcre function throw something
1584 * XXX: special char in tag is already protected earlier
1585 * XXX: maybe just check that the pcre error trigger an exception or something uncatchable in all case
1586 * XXX: see function pcre_get_compiled_regex_cache in ext/pcre/php_pcre.c +530 */
1587 ZEND_ASSERT((pce
= pcre_get_compiled_regex_cache(regex
)) != NULL
);
1589 /* Increase pce refcount */
1590 /* XXX: we use this instead of pce->refcount++; to avoid dereferencing compile error */
1591 php_pcre_pce_incref(pce
);
1593 /* Call zend pcre implementation */
1594 php_pcre_match_impl(pce
, str
, len
, &rv
, &subpats
, 0, 0, 0, 0);
1596 /* Decrease pce refcount */
1597 /* XXX: we use this instead of pce->refcount--; to avoid dereferencing compile error */
1598 php_pcre_pce_decref(pce
);
1600 /* Check that we found an occurence */
1601 if (Z_TYPE(rv
) == IS_LONG
&& Z_LVAL(rv
) == 1) {
1602 /* Init match zval */
1605 /* Init the matched tag string */
1608 /* Test that subpats is an array */
1609 ZEND_ASSERT(Z_TYPE(subpats
) == IS_ARRAY
);
1611 /* Test that subpats members count is right */
1612 ZEND_ASSERT(zend_array_count(Z_ARR(subpats
)) == (zarg
== NULL
? 1 : 2));
1614 /* Retrieve the matched full tag string */
1615 ZEND_ASSERT((match
= zend_hash_index_find(Z_ARR(subpats
), 0)) != NULL
);
1617 /* Test that match is a string */
1618 ZEND_ASSERT(Z_TYPE_P(match
) == IS_STRING
);
1620 /* Find the tag in string */
1621 if ((found
= (char *)zend_memnstr(str
, Z_STRVAL_P(match
), Z_STRLEN_P(match
), str
+len
))) {
1622 /* Move str to found position + matched tag string */
1623 str
= found
+ Z_STRLEN_P(match
);
1626 /* Check if have and arg to retrieve */
1628 /* Retrieve the matched arg string */
1629 ZEND_ASSERT((match
= zend_hash_index_find(Z_ARR(subpats
), 1)) != NULL
);
1631 /* Test that match is a string */
1632 ZEND_ASSERT(Z_TYPE_P(match
) == IS_STRING
);
1634 /* Save arg string */
1635 argstr
= Z_STR_P(match
);
1638 //XXX: code unfinished, these todo are normal, work start here :)
1639 //TODO: set the new search position to the current offset plus found subpats[0] string length
1640 //TODO: save argument string for appending in return string if zarg is not NULL
1641 //TODO: see what happen if we find start tag and not end
1642 //TODO: see what happen if we find no start tag and no end
1643 //TODO: see what happen if we find no start tag and no end
1645 /* No occurence found */
1647 /* XXX: nothing to do ? skip end tag search ??? return to parent level ? */
1650 /* Destroy subpats array */
1651 zend_array_destroy(Z_ARR(subpats
));
1653 /* Create close tag string */
1654 //ZEND_ASSERT(snprintf(closestr, closelen + 1, "/\\[\\/%s\\]/", ZSTR_VAL(cur)) == closelen);
1655 ZEND_ASSERT(snprintf(closestr
, closelen
+ 1, "[/%s]", ZSTR_VAL(cur
)) == closelen
);
1657 /* Look for close tag */
1658 /* XXX: watch out, this is a maximum length to consider, the close tag may be present earlier for current leaf */
1659 if ((found
= (char *)zend_memnrstr(str
, closestr
, closelen
, str
+len
))) {
1660 /* Reduce len by the size of trailing string since close tag */
1661 len
-= len
+ str
- found
;
1664 /* Free openstr and closestr */
1668 printf("str=%.*s\n", len
, str
);
1675 printf("str=%s\n", str
);
1680 ZVAL_ARR(&ztag
, tag
);
1681 php_debug_zval_dump(&ztag
, 0);
1685 php_debug_zval_dump(zopen
, 0);
1686 php_debug_zval_dump(zclose
, 0);
1687 php_debug_zval_dump(zdefault
, 0);
1691 if (argstr
!= NULL
) {
1692 printf("arg=%s[%ld]\n", ZSTR_VAL(argstr
), ZSTR_LEN(argstr
));
1697 //XXX: same here unfinished, code start here :)
1698 //TODO: check what happen when we have something like that : [t]s[/t][t]p[/t]
1699 //1: compute open and close tag
1700 //2: extract end of open tag position and begin of close tag reverse searched in current space
1701 //(if we don't find end tag, consider positionning to end of string or rise error ? parse flag involved here)
1702 //3: append to return string open tag
1703 //4: loop for child tags in subspace and trigger recursion on space between current open and close tags
1704 //(loop involved here)
1705 //5: duplicate plain strings between each child in return string
1706 //6: append the close tag
1709 /* Check if non empty string (other tag that empty root) */
1710 if (strcmp(ZSTR_VAL(cur
), "") != 0) {
1711 //TODO: generate search patterns for opening tag and closing tag
1712 //TODO: search position of opening tag
1713 //TODO: search close tag
1714 //TODO: search for child tag
1717 //Nothing to do, search offset for child will be 0 and length of search to len
1718 //TODO: search current end tag here ?
1721 /* Search next child node */
1724 //bbcode_search_node(ret,
1727 /* Return string by default */
1731 /* { { { PHP_METHOD(BBCode, parse) {
1733 PHP_METHOD(BBCode
, parse
) {
1734 /* Init string to parse */
1735 zend_string
*str
= NULL
;
1738 //TODO: voir si on crée pas une struct pour éviter de trainer 3 arguments
1741 /* Return offset and length */
1742 int ros
= 0, rlen
= 0;
1744 /* Parse parameters */
1745 /*if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "S", &str) == FAILURE) { return; }*/
1746 ZEND_PARSE_PARAMETERS_START(1, 1)
1748 ZEND_PARSE_PARAMETERS_END();
1750 /* Retrieve the pointer to this object */
1751 bbcode_object
*obj
= Z_BBCODE_P(getThis());
1753 /* Init return string and set length to string * 2 plus last null char */
1754 //ZEND_ASSERT((rs = (char *)malloc((rlen = (2*Z_STRLEN(str)) + 1)*sizeof(char)) != NULL);
1756 /* Go compute the tree */
1757 bbcode_parse(obj
->tag
, obj
->root
, rs
, &ros
, &rlen
, ZSTR_VAL(str
), ZSTR_LEN(str
));
1759 /* Free return string */
1763 /* Retrieve root node */
1764 zval
*root
= zend_hash_find(obj
->tag
, obj
->root
);
1768 /* Retrieve child array */
1769 /* XXX: child should exists here and be an array */
1770 ZEND_ASSERT(((child
= zend_hash_str_find(Z_ARR_P(val
), "child", strlen("child"))) != NULL
) && Z_TYPE_P(child
) == IS_ARRAY
);
1777 /* Init new child val array */
1778 ZVAL_ARR(&tag
, obj
->tag
);
1780 php_debug_zval_dump(&tag
, 0);
1784 //printf("flag=%ld\n", obj->flag);
1785 //printf("die ici: %s +%d\n", __FILE__, __LINE__);
1787 //TODO: create a tree for storing result
1788 //TODO: apply recursively search of next possible tags (or a closing parent tag ?) regexp and store result inside
1790 //TODO: apply the smiley parsing on string
1792 /* TODO: the return string */
1797 /* {{{ bbcode_methods[]
1799 * Every BBCode visible function must have an entry in bbcode_methods[].
1801 const zend_function_entry bbcode_methods
[] = {
1802 PHP_ME(BBCode
, __construct
, bbcode_construct_arginfo
, ZEND_ACC_PUBLIC
|ZEND_ACC_CTOR
)
1803 PHP_ME(BBCode
, __destruct
, NULL
, ZEND_ACC_PUBLIC
|ZEND_ACC_DTOR
)
1804 PHP_ME(BBCode
, parse
, bbcode_parse_arginfo
, ZEND_ACC_PUBLIC
)
1809 /* {{{ PHP_MINIT_FUNCTION
1811 PHP_MINIT_FUNCTION(bbcode
) {
1812 /* If you have INI entries, uncomment these lines
1813 REGISTER_INI_ENTRIES();
1816 /* Tmp zend class entry*/
1817 zend_class_entry tmp_ce
;
1819 /* Init BBCode class entry */
1820 INIT_CLASS_ENTRY(tmp_ce
, "BBCode", bbcode_methods
);
1822 /* Register BBCode class entry */
1823 bbcode_ce
= zend_register_internal_class(&tmp_ce TSRMLS_CC
);
1824 bbcode_ce
->create_object
= bbcode_create
;
1826 /* Generate const */
1827 BBCODE_DEF(BBCODE_GEN_CONST
)
1829 /* Init BBCodeException class entry */
1830 INIT_CLASS_ENTRY(tmp_ce
, "BBCodeException", NULL
);
1832 /* Register BBCodeException class entry */
1833 bbcode_exception_ce
= zend_register_internal_class_ex(&tmp_ce
, zend_exception_get_default(TSRMLS_C
) TSRMLS_CC
);
1840 /* {{{ PHP_MSHUTDOWN_FUNCTION
1842 PHP_MSHUTDOWN_FUNCTION(bbcode
) {
1843 /* uncomment this line if you have INI entries
1844 UNREGISTER_INI_ENTRIES();
1850 /* {{{ PHP_MINFO_FUNCTION
1852 PHP_MINFO_FUNCTION(bbcode
) {
1853 php_info_print_table_start();
1854 php_info_print_table_header(2, PHP_BBCODE_NAME
" support", "enabled");
1855 php_info_print_table_row(2, "Extension version", PHP_BBCODE_VERSION
);
1856 php_info_print_table_end();
1858 /* Remove comments if you have entries in php.ini
1859 DISPLAY_INI_ENTRIES();
1864 /* {{{ bbcode_module_entry
1866 zend_module_entry bbcode_module_entry
= {
1867 STANDARD_MODULE_HEADER
,
1868 PHP_BBCODE_NAME
, /* extension name */
1869 NULL
, /* function list */
1870 PHP_MINIT(bbcode
), /* process startup */
1871 PHP_MSHUTDOWN(bbcode
), /* process shutdown */
1872 NULL
, /* request startup */
1873 NULL
, /* request shutdown */
1874 PHP_MINFO(bbcode
), /* extension info */
1875 PHP_BBCODE_VERSION
, /* extension version */
1876 STANDARD_MODULE_PROPERTIES
1880 #ifdef COMPILE_DL_BBCODE
1882 ZEND_TSRMLS_CACHE_DEFINE()
1884 ZEND_GET_MODULE(bbcode
)
1892 * vim600: noet sw=4 ts=4 fdm=marker
1893 * vim<600: noet sw=4 ts=4