1 /*
2    +----------------------------------------------------------------------+
3    | Copyright (c) The PHP Group                                          |
4    +----------------------------------------------------------------------+
5    | This source file is subject to version 3.01 of the PHP license,      |
6    | that is bundled with this package in the file LICENSE, and is        |
7    | available through the world-wide-web at the following url:           |
8    | http://www.php.net/license/3_01.txt                                  |
9    | If you did not receive a copy of the PHP license and are unable to   |
10    | obtain it through the world-wide-web, please send a note to          |
11    | license@php.net so we can mail you a copy immediately.               |
12    +----------------------------------------------------------------------+
13    | Author: Israel Ekpo <iekpo@php.net>                                  |
14    +----------------------------------------------------------------------+
15 */
16 
17 #include "php_solr.h"
18 
19 #if PHP_VERSION_ID < 80000
solr_document_object_handler_clone(zval * zobject)20 PHP_SOLR_API zend_object *solr_document_object_handler_clone(zval *zobject)
21 {
22     zend_object *old_object = Z_OBJ_P(zobject);
23 #else
24 PHP_SOLR_API zend_object *solr_document_object_handler_clone(zend_object *old_object)
25 {
26 #endif
27     zend_object *new_object;
28     solr_document_t *doc_entry, *old_doc_entry;
29     long document_index = SOLR_UNIQUE_DOCUMENT_INDEX();
30 
31     new_object = zend_objects_new(old_object->ce);
32     object_properties_init(new_object, old_object->ce);
33     zend_objects_clone_members(new_object, old_object);
34 
35 #if PHP_VERSION_ID < 80000
36     if (solr_fetch_document_entry(zobject, &old_doc_entry) == FAILURE) {
37 #else
38     if (solr_fetch_document_entry(old_object, &old_doc_entry) == FAILURE) {
39 #endif
40         php_error_docref(NULL, E_ERROR, "Clone Failed: Unable to fetch document entry of the source document");
41     }
42 
43     doc_entry = solr_init_document(document_index);
44     ZVAL_LONG(&(new_object->properties_table[0]), document_index);
45     /* Add the document entry to the directory of documents */
46     doc_entry->field_count = old_doc_entry->field_count;
47     doc_entry->document_boost = old_doc_entry->document_boost;
48 
49     zend_hash_copy(doc_entry->fields, old_doc_entry->fields, (copy_ctor_func_t) field_copy_constructor);
50     zend_hash_copy(doc_entry->children, old_doc_entry->children, (copy_ctor_func_t) zval_add_ref);
51 
52     return new_object;
53 }
54 
55 PHP_SOLR_API void field_copy_constructor_ex(solr_field_list_t **original_field_queue_ptr)
56 {
57     solr_field_list_t *original_field_queue = *original_field_queue_ptr;
58     solr_field_value_t *ptr = original_field_queue->head;
59     if (ptr == NULL)
60     {
61         return;
62     }
63 
64     solr_field_list_t *new_field_queue = (solr_field_list_t *) pemalloc(sizeof(solr_field_list_t), SOLR_DOCUMENT_FIELD_PERSISTENT);
65 
66     new_field_queue->count       = 0L;
67     new_field_queue->field_name  = (solr_char_t *) pestrdup((char *) (original_field_queue)->field_name, SOLR_DOCUMENT_FIELD_PERSISTENT);
68     new_field_queue->head        = NULL;
69     new_field_queue->last        = NULL;
70     new_field_queue->field_boost = original_field_queue->field_boost;
71 
72     while(ptr != NULL)
73     {
74         if (solr_document_insert_field_value(new_field_queue, ptr->field_value, 0.0) == FAILURE) {
75             php_error_docref(NULL, E_ERROR, "Unable to insert field value");
76         }
77 
78         ptr = ptr->next;
79     }
80 
81     *original_field_queue_ptr = new_field_queue;
82 }
83 /* {{{ void field_copy_constructor_zv(zval *field_queue_zv) */
84 PHP_SOLR_API void field_copy_constructor_zv(zval *field_queue_zv)
85 {
86     solr_field_list_t *original_field_queue = NULL;
87 	original_field_queue = Z_PTR_P(field_queue_zv);
88 	field_copy_constructor_ex(&original_field_queue);
89 	ZVAL_PTR(field_queue_zv, original_field_queue);
90 }
91 /* }}} */
92 
93 
94 /* {{{ PHP_SOLR_API int solr_document_insert_field_value(solr_field_list_t *queue, const solr_char_t *field_value, double field_boost, int modifier) */
95 PHP_SOLR_API int solr_document_insert_field_value_ex(solr_field_list_t *queue, const solr_char_t *field_value, double field_boost, int modifier)
96 {
97 	solr_field_value_t *new_entry = (solr_field_value_t *) pemalloc(sizeof(solr_field_value_t), SOLR_DOCUMENT_FIELD_PERSISTENT);
98 
99 	if (new_entry == NULL) {
100 
101 		return FAILURE;
102 	}
103 
104 	new_entry->field_value = (solr_char_t *) pestrdup((char *) field_value, SOLR_DOCUMENT_FIELD_PERSISTENT);
105 
106 	if (new_entry->field_value == NULL) {
107 
108 		return FAILURE;
109 	}
110 
111 	new_entry->next = NULL;
112 	new_entry->modifier = modifier;
113 
114 	if (queue->head == NULL) {
115 
116 		/* This is the first and only item in the field list */
117 		queue->head = new_entry;
118 		queue->last = new_entry;
119 
120 		/* Update the field boost value */
121 		if (field_boost > 0.0) {
122 
123 			queue->field_boost = field_boost;
124 		}
125 
126 	} else { /* There are already entries in the list. */
127 
128 		/* Append to the end of the queue */
129 		queue->last->next = new_entry;
130 
131 		/* Set the last item in the queue to the latest entry */
132 		queue->last       = new_entry;
133 
134 		/* Update the field boost value */
135 		if (field_boost > 0.0) {
136 
137 			if (queue->field_boost > 0.0) {
138 
139 				queue->field_boost *= field_boost;
140 
141 			} else {
142 
143 				queue->field_boost = field_boost;
144 			}
145 		}
146 	}
147 
148 	queue->count++;
149 
150 	return SUCCESS;
151 }
152 /* }}} */
153 
154 /* {{{ PHP_SOLR_API solr_document_t *solr_init_document(long int document_index)
155  * create and allocate a solr_document_t with the specified index
156  */
157 PHP_SOLR_API solr_document_t *solr_init_document(long int document_index)
158 {
159     uint32_t nSize = SOLR_INITIAL_HASH_TABLE_SIZE;
160     solr_document_t *doc_ptr = NULL;
161     solr_document_t *doc_entry;
162 
163 #ifdef PHP_7
164     doc_entry = pemalloc(sizeof(solr_document_t), SOLR_DOCUMENT_PERSISTENT);
165 #endif
166 
167     doc_entry->document_index  = document_index;
168     doc_entry->field_count     = 0L;
169     doc_entry->document_boost  = 0.0f;
170 
171     /* Allocated memory for the fields HashTable using fast cache for HashTables */
172     ALLOC_HASHTABLE(doc_entry->fields);
173     ALLOC_HASHTABLE(doc_entry->children);
174 
175     /* Initializing the hash table used for storing fields in this SolrDocument */
176     zend_hash_init(doc_entry->fields, nSize, NULL, (dtor_func_t) solr_destroy_field_list_ht_dtor, SOLR_DOCUMENT_FIELD_PERSISTENT);
177     zend_hash_init(doc_entry->children, nSize, NULL, ZVAL_PTR_DTOR, SOLR_DOCUMENT_FIELD_PERSISTENT);
178 
179     /* Let's check one more time before insert into the HashTable */
180     if (zend_hash_index_exists(SOLR_GLOBAL(documents), document_index)) {
181         /* todo call dtor ? */
182         pefree(doc_entry->fields, SOLR_DOCUMENT_FIELD_PERSISTENT);
183 
184         pefree(doc_entry->children, SOLR_DOCUMENT_FIELD_PERSISTENT);
185         return NULL;
186     }
187 
188     /* Add the document entry to the directory of documents */
189     doc_ptr = zend_hash_index_update_ptr(SOLR_GLOBAL(documents), document_index, (void *) doc_entry);
190 
191     /* Keep track of how many SolrDocument instances we currently have */
192     SOLR_GLOBAL(document_count)++;
193     return doc_ptr;
194 }
195 /* }}} */
196 
197 /* {{{ PHP_SOLR_API solr_document_t *solr_input_doc_ctor(zval *objptr)
198  * constructor populate/allocate the new document after object instantiation
199  * and allocates hashtables for fields and children
200  */
201 PHP_SOLR_API solr_document_t *solr_input_doc_ctor(zval *objptr)
202 {
203     zend_ulong document_index = SOLR_UNIQUE_DOCUMENT_INDEX();
204     solr_document_t *solr_doc = NULL;
205 
206     if ((solr_doc = solr_init_document(document_index)) == NULL)
207     {
208         return NULL;
209     }
210 
211     /* Set the value of the internal id property */
212     zend_update_property_long(solr_ce_SolrInputDocument, OBJ_FOR_PROP(objptr), SOLR_INDEX_PROPERTY_NAME, sizeof(SOLR_INDEX_PROPERTY_NAME) - 1, document_index);
213 
214     /* Overriding the default object handlers */
215     Z_OBJ_HT_P(objptr) = &solr_input_document_object_handlers;
216     return solr_doc;
217 }
218 /* }}} */
219 
220 /* {{{ PHP_SOLR_API void solr_destroy_document_zv(zval *document)
221  * destory a solr_document within a zval (ZE 3)
222  */
223 PHP_SOLR_API void solr_destroy_document_zv(zval *document)
224 {
225     solr_document_t *doc_entry = (solr_document_t *) Z_PTR_P(document);
226 
227     solr_destroy_document_ex(doc_entry);
228 
229     pefree(doc_entry, SOLR_DOCUMENT_PERSISTENT);
230 }
231 /* }}} */
232 
233 /* {{{  void solr_destroy_document(void *document)
234  * destory a solr_document_t
235  */
236 PHP_SOLR_API void solr_destroy_document_ex(solr_document_t *doc_entry)
237 {
238     /* Release all the field_lists one at a time with solr_destroy_field_list */
239     zend_hash_destroy(doc_entry->fields);
240 
241     /* Deallocate memory for the fields HashTable */
242     pefree(doc_entry->fields, SOLR_DOCUMENT_FIELD_PERSISTENT);
243     if (doc_entry->children) {
244         zend_hash_destroy(doc_entry->children);
245         pefree(doc_entry->children, SOLR_DOCUMENT_FIELD_PERSISTENT);
246     }
247 }
248 /* }}} */
249 
250 PHP_SOLR_API void solr_destroy_field_list_ht_dtor(zval *zv_field_entry)
251 {
252     solr_field_list_t *field_entry = Z_PTR_P(zv_field_entry);
253     solr_destroy_field_list(field_entry);
254 }
255 
256 /* {{{ void solr_destroy_field_list(solr_field_list_t **field_entry_ptr) */
257 PHP_SOLR_API void solr_destroy_field_list(solr_field_list_t *field_entry)
258 {
259 	solr_field_value_t *tmp = NULL;
260 	solr_field_value_t *current_field_value = field_entry->head;
261 
262 	/* Go through the list and free all the values */
263 	while(current_field_value != NULL) {
264 
265 		tmp = current_field_value->next;
266 
267 		pefree(current_field_value->field_value, SOLR_DOCUMENT_FIELD_PERSISTENT);
268 
269 		pefree(current_field_value, SOLR_DOCUMENT_FIELD_PERSISTENT);
270 
271 		current_field_value = tmp;
272 	}
273 
274 	field_entry->head = NULL;
275 
276 	field_entry->last = NULL;
277 
278 	pefree(field_entry->field_name, SOLR_DOCUMENT_FIELD_PERSISTENT);
279 
280 	pefree(field_entry, SOLR_DOCUMENT_FIELD_PERSISTENT);
281 }
282 /* }}} */
283 
284 /* Comparison functions for field entries */
285 
286 /* {{{ int solr_compare_field_name(const void *a, const void *b) */
287 #if PHP_VERSION_ID < 80000
288 PHP_SOLR_API int solr_compare_field_name(const void *a, const void *b)
289 {
290     const Bucket *x = (Bucket *) a;
291     const Bucket *y = (Bucket *) b;
292 #else
293 PHP_SOLR_API int solr_compare_field_name(Bucket *x, Bucket *y)
294 {
295 #endif
296 	const solr_field_list_t *first  = (solr_field_list_t *) Z_PTR(x->val);
297 	const solr_field_list_t *second = (solr_field_list_t *) Z_PTR(y->val);
298 
299 	const int diff = strcmp((char *) first->field_name, (char *) second->field_name);
300 
301 	const int result = ((diff > 0) ? 1 : ((diff < 0) ? -1 : 0));
302 
303 	return result;
304 }
305 /* }}} */
306 
307 /* {{{ 	int solr_rcompare_field_name(const void *a, const void *b) */
308 #if PHP_VERSION_ID < 80000
309 PHP_SOLR_API int solr_rcompare_field_name(const void *a, const void *b)
310 #else
311 PHP_SOLR_API int solr_rcompare_field_name(Bucket *a, Bucket *b)
312 #endif
313 {
314 	return (solr_compare_field_name(a, b) * -1);
315 }
316 /* }}} */
317 
318 /* {{{	int solr_compare_field_value_count(const void *a, const void *b) */
319 #if PHP_VERSION_ID < 80000
320 PHP_SOLR_API int solr_compare_field_value_count(const void *a, const void *b)
321 {
322     const Bucket *x = (Bucket *) a;
323     const Bucket *y = (Bucket *) b;
324 #else
325 PHP_SOLR_API int solr_compare_field_value_count(Bucket *x, Bucket *y)
326 {
327 #endif
328     const solr_field_list_t *first  = (solr_field_list_t *) Z_PTR(x->val);
329     const solr_field_list_t *second = (solr_field_list_t *) Z_PTR(y->val);
330 
331 	const int diff = first->count - second->count;
332 
333 	const int result = ((diff > 0) ? 1 : ((diff < 0) ? -1 : 0));
334 
335 	return result;
336 }
337 /* }}} */
338 
339 /* {{{	int solr_rcompare_field_value_count(const void *a, const void *b)	*/
340 #if PHP_VERSION_ID < 80000
341 PHP_SOLR_API int solr_rcompare_field_value_count(const void *a, const void *b)
342 #else
343 PHP_SOLR_API int solr_rcompare_field_value_count(Bucket *a, Bucket *b)
344 #endif
345 {
346 	return (solr_compare_field_value_count(a, b) * -1);
347 }
348 /* }}} */
349 
350 /* {{{ 	int solr_compare_field_boost_value(const void *a, const void *b) */
351 #if PHP_VERSION_ID < 80000
352 PHP_SOLR_API int solr_compare_field_boost_value(const void *a, const void *b)
353 {
354     const Bucket *x = (Bucket *) a;
355     const Bucket *y = (Bucket *) b;
356 #else
357 PHP_SOLR_API int solr_compare_field_boost_value(Bucket *x, Bucket *y)
358 {
359 #endif
360 
361     const solr_field_list_t *first  = (solr_field_list_t *) Z_PTR(x->val);
362     const solr_field_list_t *second = (solr_field_list_t *) Z_PTR(y->val);
363 
364 	const double diff = first->field_boost - second->field_boost;
365 
366 	const int result = ((diff > 0.0) ? 1 : ((diff < 0.0) ? -1 : 0));
367 
368 	return result;
369 }
370 /* }}} */
371 
372 /* {{{ 	int solr_rcompare_field_boost_value(const void *a, const void *b) */
373 #if PHP_VERSION_ID < 80000
374 PHP_SOLR_API int solr_rcompare_field_boost_value(const void *a, const void *b)
375 #else
376 PHP_SOLR_API int solr_rcompare_field_boost_value(Bucket *a, Bucket *b)
377 #endif
378 {
379 	return (solr_compare_field_boost_value(a, b) * -1);
380 }
381 /* }}} */
382 
383 /* {{{ PHP_SOLR_API void solr_create_document_field_object(solr_field_list_t *field_values, zval **field_obj) */
384 PHP_SOLR_API void solr_create_document_field_object(solr_field_list_t *field_values, zval **field_obj)
385 {
386 	zval *doc_field = *field_obj;
387 
388 	solr_field_value_t *curr_ptr = NULL;
389 	zval field_values_array_tmp;
390 	zval *field_values_array = &field_values_array_tmp;
391 
392 	array_init(field_values_array);
393 
394 	curr_ptr = field_values->head;
395 
396 	while(curr_ptr != NULL) {
397 
398 		solr_char_t *current_value = curr_ptr->field_value;
399 
400 		add_next_index_string(field_values_array, current_value);
401 
402 		curr_ptr = curr_ptr->next;
403 	}
404 
405 	object_init_ex(doc_field, solr_ce_SolrDocumentField);
406 
407 	zend_update_property_string(solr_ce_SolrDocumentField, OBJ_FOR_PROP(doc_field), SOLR_FIELD_NAME_PROPERTY_NAME, sizeof(SOLR_FIELD_NAME_PROPERTY_NAME)-1, field_values->field_name);
408 	zend_update_property_double(solr_ce_SolrDocumentField, OBJ_FOR_PROP(doc_field), SOLR_FIELD_BOOST_PROPERTY_NAME, sizeof(SOLR_FIELD_BOOST_PROPERTY_NAME)-1, field_values->field_boost);
409 	zend_update_property(solr_ce_SolrDocumentField, OBJ_FOR_PROP(doc_field), SOLR_FIELD_VALUES_PROPERTY_NAME, sizeof(SOLR_FIELD_VALUES_PROPERTY_NAME)-1, field_values_array);
410 
411 	zval_ptr_dtor(field_values_array);
412 
413 	Z_OBJ_HT_P(doc_field) = &solr_document_field_handlers;
414 }
415 /* }}} */
416 
417 /* {{{ PHP_SOLR_API void solr_create_document_field_object(solr_field_list_t *field_values, zval **field_obj)
418     constructs the <doc> element when adding a document to the index */
419 PHP_SOLR_API void solr_add_doc_node(xmlNode *root_node, solr_document_t *doc_entry)
420 {
421     HashTable *document_fields = NULL;
422     xmlNode *solr_doc_node = NULL;
423     document_fields = doc_entry->fields;
424 
425     solr_doc_node = xmlNewChild(root_node, NULL, (xmlChar *) "doc", NULL);
426 
427    if (doc_entry->document_boost > 0.0f)
428    {
429        auto char tmp_buffer[256]; /* Scratch pad for converting numeric values to strings */
430        memset(tmp_buffer, 0, sizeof(tmp_buffer));
431        php_gcvt(doc_entry->document_boost, EG(precision), '.', 'e' , tmp_buffer);
432        xmlNewProp(solr_doc_node, (xmlChar *) "boost", (xmlChar *) tmp_buffer);
433    }
434 
435    solr_generate_document_xml_from_fields(solr_doc_node, document_fields);
436    if (zend_hash_num_elements(doc_entry->children) > 0) {
437 
438        SOLR_HASHTABLE_FOR_LOOP(doc_entry->children)
439        {
440            zval *doc_obj = NULL;
441            solr_document_t *child_doc_entry = NULL;
442            doc_obj = zend_hash_get_current_data(doc_entry->children);
443            if (solr_fetch_document_entry(OBJ_FOR_PROP(doc_obj), &child_doc_entry) == SUCCESS)
444            {
445                solr_add_doc_node(solr_doc_node, child_doc_entry);
446            }
447        }
448    }
449 }
450 /* }}} */
451 
452 
453 /* {{{ static void solr_generate_document_xml_from_fields(xmlNode *solr_doc_node, HashTable *document_fields) */
454 PHP_SOLR_API void solr_generate_document_xml_from_fields(xmlNode *solr_doc_node, HashTable *document_fields)
455 {
456     xmlDoc *doc_ptr = solr_doc_node->doc;
457 
458     solr_char_t *doc_field_name;
459     zend_string *field_str = NULL;
460     solr_field_value_t *doc_field_value;
461     solr_field_list_t *field = NULL;
462     zend_ulong num_idx = 0L;
463     ZEND_HASH_FOREACH_KEY_PTR(document_fields, num_idx, field_str, field)
464     {
465         (void)num_idx;
466         zend_bool is_first_value = 1; /* Turn on first value flag */
467         xmlChar *modifier_string = NULL;
468 
469         doc_field_name = field_str->val;
470         doc_field_value = field->head;
471 
472         /* Loop through all the values for this field */
473         while(doc_field_value != NULL)
474         {
475             xmlChar *escaped_field_value = xmlEncodeEntitiesReentrant(doc_ptr, (xmlChar *) doc_field_value->field_value);
476 
477             xmlNode *solr_field_node = xmlNewChild(solr_doc_node, NULL, (xmlChar *) "field", escaped_field_value);
478 
479             xmlNewProp(solr_field_node, (xmlChar *) "name", (xmlChar *) doc_field_name);
480 
481             if (field->modified) {
482                 switch (doc_field_value->modifier) {
483                 case SOLR_FIELD_VALUE_MOD_ADD:
484                     modifier_string = (xmlChar *)"add";
485                     break;
486                 case SOLR_FIELD_VALUE_MOD_REMOVE:
487                     modifier_string = (xmlChar *)"remove";
488                     break;
489                 case SOLR_FIELD_VALUE_MOD_REMOVEREGEX:
490                     modifier_string = (xmlChar *)"removeregex";
491                     break;
492                 case SOLR_FIELD_VALUE_MOD_SET:
493                     modifier_string = (xmlChar *)"set";
494                     break;
495 
496                 case SOLR_FIELD_VALUE_MOD_INC:
497                     modifier_string = (xmlChar *)"inc";
498                     break;
499                 case SOLR_FIELD_VALUE_MOD_NONE:default:
500                     break;
501                 }
502                 if (modifier_string) {
503                     xmlNewProp(solr_field_node, (xmlChar *) "update", modifier_string);
504                 }
505             }
506 
507             /* Set the boost attribute if this is the first value */
508             if (is_first_value && field->field_boost > 0.0f)
509             {
510                 auto char tmp_boost_value_buffer[256];
511 
512                 memset(tmp_boost_value_buffer, 0, sizeof(tmp_boost_value_buffer));
513 
514                 php_gcvt(field->field_boost, EG(precision), '.', 'e' , tmp_boost_value_buffer);
515 
516                 xmlNewProp(solr_field_node, (xmlChar *) "boost", (xmlChar *) tmp_boost_value_buffer);
517 
518                 is_first_value = 0; /* Turn off the flag */
519             }
520 
521             /* Release the memory allocated by xmlEncodeEntitiesReentrant */
522             xmlFree(escaped_field_value);
523 
524             /* Grab the next value for this field if any */
525             doc_field_value = doc_field_value->next;
526 
527         } /* while(doc_field_value != NULL) */
528     } ZEND_HASH_FOREACH_END(); /* end fields loop */
529 }
530 /* }}} */
531 
532 
533 PHP_SOLR_API void solr_document_get_field_names(INTERNAL_FUNCTION_PARAMETERS)
534 {
535     solr_document_t *doc_entry = NULL;
536 
537     /* Retrieve the document entry for the SolrDocument instance */
538     if (solr_fetch_document_entry(OBJ_FOR_PROP(getThis()), &doc_entry) == SUCCESS)
539     {
540         HashTable *fields_ht = doc_entry->fields;
541 
542         array_init(return_value);
543 
544         SOLR_HASHTABLE_FOR_LOOP(fields_ht)
545         {
546             solr_field_list_t *field      = NULL;
547             field = zend_hash_get_current_data_ptr(fields_ht);
548             add_next_index_string(return_value, (char *) field->field_name);
549         }
550         return;
551     }
552 
553     RETURN_FALSE;
554 }
555 
556 /*
557  * Local variables:
558  * tab-width: 4
559  * c-basic-offset: 4
560  * End:
561  * vim600: fdm=marker
562  * vim: noet sw=4 ts=4
563  */
564