How to update/replace a field in an ElasticSearch document using PHP? - php

I want to update my Elasticsearch indexed document's field. In my case its the tags field.
This is the code I currently have:
// Index tags in the page document
$es_client->update([
'index' => 'myappname',
'type' => 'page',
'id' => $page_id,
'body' => [
'doc' => [
'tags' => $tagsArray
]
]
]);
So, this would update my document by adding the tags array to it, but it won't remove the old tags.
How can I make sure that the old tags get removed when I add the new tags?
I did look in the documentation, but as we all know, the Elasticsearch docs can be very confusing and all-over-the-place. Hence I am asking here after days of searching.
Any help or advice would be greatly appreciated.

Standard update behavior is to merge array/object fields as explained in the update API documentation .
...objects are merged together, existing scalar fields are overwritten
and new fields are added.
So instead you would use a script to modify the document source directly. You can make it generic and thus cacheable, and pass in params for better performance. Php API documentation
// Index tags in the page document
$es_client->update([
'index' => 'myappname',
'type' => 'page',
'id' => $page_id,
'body' => [
'script' => 'ctx._source.tags=tags',
'params' => ['tags' => $tagsArray]
]
]);

Related

categoryChoiceTree in prestashop module configuration page

I'm developing a prestashop module and I'm trying to show a category tree in my backoffice configuration page.
I'm trying to follow this instructions below but I don't know exactly where to add this code.
It should be inside main module's php? or inside a separate .php file and call it from the main one (don't know how to do it either).
As much time I'm spending trying to figure out, how to implement the code in the link above, the more I think I'm losing my time.
I see that "use" files, and this JS, " /admin-dev/themes/new-theme/js/components/form/choice-tree.js " are not in any prestashop folders.
Well, you should invest some time and learn Symfony since this is what you need to build backend modules for Prestashop 1.7.
As a pointer, you need to create a form class extending the CommonAbstractType, add a build form method. e.g. :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->context = Context::getContext();
$parents = [
['id_category' => 2, 'name' => 'Home', 'children' => $this->getSubCategories(1, true, 2)]
];
$builder->add('category', CategoryChoiceTreeType::class, [
'choices_tree' => $parents,
'choice_value' => 'id_category',
'choice_children' => 'children',
'choice_label' => 'name',
'disabled_values' => $disabledCategories,
'label' => 'Choose a category'
])
then add methods for retrieving the data to populate the form fields.
Then use this class in your controller and display the form:
$form = $this->createForm(YourFormForm::class);
Also add a processForm to process data.
As mentioned, this is not a copy/paste situation you need to understand the Symfony workflow.
The only way that I found to "paint" the categorytree in my configuration page is adding this code to the inputs form array:
Can anyone tell me how to retrieve users selection data to my database?
It does not work as any other form field.
array(
'type' => 'categories',
'label' => $this->l('Destination Category'),
'desc' => $this->l('Select ONE Category'),
'name' => 'CATEGORY_CATEGORY_TO',
'tree' => [
// 'selected_categories' => [],
'disabled_categories' => null,
'use_search' => false,
'use_checkbox' => false,
'id' => 'id_category_tree',
],
'required' => true
),
Well, it is SOLVED!!!! Finally it was very simple, but you must get the correct info for you particular case.
#Robertino's answer might be the best implementation, I don't know, but it became impossible to solve for me,
I uses this code below, and called $categoryTree from the form input. This input must be type=> categories_select
Thanks for your time, and for the help of another post from this forum.
$root = Category::getRootCategory();
//Generating the tree
$tree = new HelperTreeCategories('categories_1'); //The string in param is the ID used by the generated tree
$tree->setUseCheckBox(false)
->setAttribute('is_category_filter', $root->id)
->setRootCategory($root->id)
->setSelectedCategories(array((int)Configuration::get('CATEGORY_1'))) //if you wanted to be pre-carged
->setInputName('CATEGORY_1'); //Set the name of input. The option "name" of $fields_form doesn't seem to work with "categories_select" type
$categoryTree = $tree->render();
And the Form:
array(
'type' => 'categories_select',
'label' => $this->l('Category'),
'desc' => $this->l('Select Category '),
'name' => 'CATEGORY_1', //No ho podem treure si no, no passa la variable al configuration
'category_tree' => $categoryTree, //This is the category_tree called in form.tpl
'required' => true

Docusign PHP SDK - Fill Template pre-defined data label value

UPDATE:
As it turns out, I need to enable this setting for data to show up, and using tabs is the correct thing to do.
When an envelope is sent, write the initial value of the field for all recipients
=================================================================
Not sure why this one is not mentioned in API properly ... but how does one go about filling template's custom data label with template?
So, I create a template like this:
$envelope_definition = new EnvelopeDefinition([
'status' => 'sent',
'template_id' => $args['template_id'],
]);
then I create a signer:
$signer = new TemplateRole([
'email' => $args['signer_email'],
'name' => $args['signer_name'],
'role_name' => 'signer',
]);
Here is where the disconnect happened, where do I add a pre-defined template value? I tried two things so far:
1. Add tabs to $signer like so, but by doing so, it ereases all field value in the final document,
new Tabs([
"text_tabs" => [
new Text([
"tab_label" => "price",
"value" => "123456789",
]),
],
]),
Call $envelope_definition->setCustomFields() , like this :
$envelope_definition->setCustomFields(new CustomFields([
'text_custom_fields' => [
'price' => new Text([
'tab_label' => 'price',
'custom_tab_id' => 'price',
'value' => '123456789',
]),
],
]));
It will throw me a C# error, which I don't understand at all:
Error while requesting server, received a non successful HTTP code [400] with response Body: O:8:\"stdClass\":2:{s:9:\"errorCode\";s:20:\"INVALID_REQUEST_BODY\";s:7:\"message\";s:718:\"The request body is missing or improperly formatted. Cannot deserialize the current JSON object (e.g. {\"name\":\"value\"}) into type 'System.Collections.Generic.List`1[API_REST.Models.v2_1.textCustomField]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly.\r\nTo fix this error either change the JSON to a JSON array (e.g. [1,2,3]) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List<T>) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object.\r\nPath 'customFields.textCustomFields.price', line 1, position 45.\";}"
API docs seems to be focusing on creating template and values adhoc ... anyone have something that works? Thanks a lot!
You can find valid PHP example here which shows how to prepopulate template tab values while creating an envelope.
You didn't follow the example correctly.
You didn't create a \DocuSign\eSign\Model\TextCustomField object.
Here it is from Amit's link:
# Create an envelope custom field to save the our application's
# data about the envelope
$custom_field = new \DocuSign\eSign\Model\TextCustomField([
'name' => 'app metadata item',
'required' => 'false',
'show' => 'true', # Yes, include in the CoC
'value' => '1234567']);
$custom_fields = new \DocuSign\eSign\Model\CustomFields([
'text_custom_fields' => [$custom_field]]);
$envelope_definition->setCustomFields($custom_fields);

Elasticsearch exact match field

I have a field called url that is set to not_analyzed when I index it:
'url' => [
'type' => 'string',
'index' => 'not_analyzed'
]
Here is my method to determine if a URL already exists in the index:
public function urlExists($index, $type, $url) {
$params = [
'index' => $index,
'type' => $type,
'body' => [
'query' => [
'match' => [
'url' => $url
]
]
]
];
$results = $this->client->count($params);
return ($results['count'] > 0);
}
This seems to work fine however I can't be 100% sure this is the correct way to find an exact match, as reading the docs another way to do the search is with the params like:
$params = [
'index' => $index,
'type' => $type,
'body' => [
'query' => [
'filtered' => [
'filter' => [
'term' => [
'url' => $url
]
]
]
]
]
];
My question is would either params work the same way for a not_analyzed field?
The second query is the right approach. term level queries/filters should be used for exact match. Biggest advantage is caching. Elasticsearch uses bitset for this and you will get quicker response time with subsequent calls.
From the Docs
Exclude as many document as you can with a filter, then query just the
documents that remain.
Also if you observe your output, you will find that _score of every document is 1 as scoring is not applied to filters, same goes for highlighting but with match query you will see different _score. Again From the Docs
Keep in mind that once you wrap a query as a filter, it loses query
features like highlighting and scoring because these are not features
supported by filters.
Your first query uses match which is basically used for analyzed fields e.g when you want both Google and google to match all your documents containing google(case insensitive) match queries are used.
Hope this helps!!

Using elasticsearch, how to create an index for a document that contains an array, and append to that array in the future

In my example code I am using the php client library, but it should be understood by anyone familiar with elasticsearch.
I'm using elasticsearch to create an index where each document contains an array of nGram indexed authors. Initially, the document will have a single author, but as time progresses, more authors will be appended to the array. Ideally, a search could be executed by an author's name, and if any of the authors in the array get matched, the document will be found.
I have been trying to use the documentation here for appending to the array and here for using the array type - but I have not had success getting this working.
First, I want to create an index for documents, with a title, array of authors, and an array of comments.
$client = new Client();
$params = [
'index' => 'document',
'body' => [
'settings' => [
// Simple settings for now, single shard
'number_of_shards' => 1,
'number_of_replicas' => 0,
'analysis' => [
'filter' => [
'shingle' => [
'type' => 'shingle'
]
],
'analyzer' => [
'my_ngram_analyzer' => [
'tokenizer' => 'my_ngram_tokenizer',
'filter' => 'lowercase',
]
],
// Allow searching for partial names with nGram
'tokenizer' => [
'my_ngram_tokenizer' => [
'type' => 'nGram',
'min_gram' => 1,
'max_gram' => 15,
'token_chars' => ['letter', 'digit']
]
]
]
],
'mappings' => [
'_default_' => [
'properties' => [
'document_id' => [
'type' => 'string',
'index' => 'not_analyzed',
],
// The name, email, or other info related to the person
'title' => [
'type' => 'string',
'analyzer' => 'my_ngram_analyzer',
'term_vector' => 'yes',
'copy_to' => 'combined'
],
'authors' => [
'type' => 'list',
'analyzer' => 'my_ngram_analyzer',
'term_vector' => 'yes',
'copy_to' => 'combined'
],
'comments' => [
'type' => 'list',
'analyzer' => 'my_ngram_analyzer',
'term_vector' => 'yes',
'copy_to' => 'combined'
],
]
],
]
]
];
// Create index `person` with ngram indexing
$client->indices()->create($params);
Off the get go, I can't even create the index due to this error:
{"error":"MapperParsingException[mapping [_default_]]; nested: MapperParsingException[No handler for type [list] declared on field [authors]]; ","status":400}
HAD this gone successfully though, I would plan to create an index, starting with empty arrays for authors and title, something like this:
$client = new Client();
$params = array();
$params['body'] = array('document_id' => 'id_here', 'title' => 'my_title', 'authors' => [], 'comments' => []);
$params['index'] = 'document';
$params['type'] = 'example_type';
$params['id'] = 'id_here';
$ret = $client->index($params);
return $ret;
This seems like it should work if I had the desired index to add this structure of information to, but what concerns me would be appending something to the array using update. For example,
$client = new Client();
$params = array();
//$params['body'] = array('person_id' => $person_id, 'emails' => [$email]);
$params['index'] = 'document';
$params['type'] = 'example_type';
$params['id'] = 'id_here';
$params['script'] = 'NO IDEA WHAT THIS SCRIPT SHOULD BE TO APPEND TO THE ARRAY';
$ret = $client->update($params);
return $ret;
}
I am not sure how I would go about actually appending a thing to the array and making sure it's indexed.
Finally, another thing that confuses me is how I could search based on any author in the array. Ideally I could do something like this:
But I'm not 100% whether it will work. Maybe there is something fundemental about elasticsearch that I am not understanding. I am completely new to so any resources that will get me to a point where these little details don't hang me up would be appreciated.
Also, any direct advice on how to use elasticsearch to solve these problems would be appreciated.
Sorry for the big wall of text, to recap, I am looking for advice on how to
Create an index that supports nGram analysis on all elements of an array
Updating that index to append to the array
Searching for the now-updated index.
Thanks for any help
EDIT: thanks to #astax, I am now able to create the index and append to the value as a string. HOWEVER, there are two problems with this:
the array is stored as a string value, so a script like
$params['script'] = 'ctx._source.authors += [\'hello\']';
actually appends a STRING with [] rather than an array containing a value.
the value inputted does not appear to be ngram analyzed, so a search like this:
$client = new Client();
$searchParams['index'] = 'document';
$searchParams['type'] = 'example_type';
$searchParams['body']['query']['match']['_all'] = 'hello';
$queryResponse = $client->search($searchParams);
print_r($queryResponse); // SUCCESS
will find the new value but a search like this:
$client = new Client();
$searchParams['index'] = 'document';
$searchParams['type'] = 'example_type';
$searchParams['body']['query']['match']['_all'] = 'hel';
$queryResponse = $client->search($searchParams);
print_r($queryResponse); // NO RESULTS
does not
There is no type "list" in elasticsearch. But you can use "string" field type and store array of values.
....
'comments' => [
'type' => 'string',
'analyzer' => 'my_ngram_analyzer',
'term_vector' => 'yes',
'copy_to' => 'combined'
],
....
And index a document this way:
....
$params['body'] = array(
'document_id' => 'id_here',
'title' => 'my_title',
'authors' => [],
'comments' => ['comment1', 'comment2']);
....
As for the script for apending an element to array, this answer may help you - Elasticsearch upserting and appending to array
However, do you really need to update the document? It might be easier to just reindex it as this is exactly what Elasticsearch does internally. It reads the "_source" property, does the required modification and reindexes it. BTW, this means that "_source" must be enabled and all properties of the document should be included into it.
You also may consider storing comments and authors (as I understand these are authors of comments, not the document authors) as child document in ES and using "has_child" filter.
I can't really give you specific solution, but strongly recommend installing Marvel plugin for ElasticSearch and use its "sense" tool to check how your overall process works step by step.
So check if your tokenizer is properly configured by running tests as described at http://www.elastic.co/guide/en/elasticsearch/reference/1.4/indices-analyze.html.
Then check if your update script is doing what you expect by retrieving the document by running GET /document/example_type/some_existing_id
The authors and comments should be arrays, but not strings.
Finally perform the search:
GET /document/_search
{
'query' : {
'match': { '_all': 'hel' }
}
}
If you're building the query yourself rather than getting it from the user, you may use query_string with placeholders:
GET /document/_search
{
'query' : {
'query_string': {
'fields': '_all',
'query': 'hel*'
}
}
}

Iterating through an array and replacing data within a template

I have an XML document with "tags" that are replaced based on data within an array. There are two types of tags, one is a parent tag to define a set, another is simply a tag that is replaced by a value. Here's an example of the data used to build and fill in the template:
$array = array(
'name' => 'name',
'city' => 'city',
'addresses' => array(
array(
'street' => '123',
'city' => 'main'
),
array(
'street' => '123',
'city' => 'main'
'phone' => array(
array(
'home' => '123456', 'work' => '1234567'
)
Here is an example template:
<name>%name%</name>
<city>%city%</city>
%%addresses%%
<street>%street%</street>
<city>%city%</city>
%%phone%%
<home>%%home%%</home>
<work>%%work%%</work>
%%/phone%%
%%/addresses%%
The key values of the array, match the tags within the template. If the key is an array itself, then it loops through the data contained within that key's tag (%%).
I've tried doing a recursive function but it only seems to work one level deep.
Does anyone have any suggestions? Thank you!
I suggest you use an existing simple template language that "just works"tm, like Mustache (there are plenty much others). I know that Mustache supports looping over arrays, used it, does the job, easy to integrate. Available for many languages.

Categories