The Setup
Using the Reviews extension for WP Job Manager, I have comments with the meta field review_average which is an average of `review_stars. I'm customizing the WP REST API so I can read and write comments remotely via the mobile app I'm building.
I've exposed these fields with
register_meta( 'comment', 'review_average', array( 'show_in_rest' => true,
'type' => 'string',
'description' => 'review_average',
'single'=> false));
register_meta( 'comment', 'review_stars', array( 'show_in_rest' => true,
'type' => 'string',
'description' => 'review_stars',
'single'=> false));
which results in this field in the REST API response for comments:
meta: {
review_average: [
"4"
],
review_stars: [
"Array"
]
},
(I can't seem to break down that array, but there's only one stars category so the average is fine)
I've written a create_review function that uses add_comment_meta to write to review_average and review_stars which successfully gives the comment the right stars. Both those meta values are required for it to work.
function create_review($param) {
$id = wp_insert_comment(array('comment_post_ID' => $param['post_id'],
'comment_author' => $param['username'],
'comment_author_email' => $param['email'],
'user_id' => $param['user_id'],
'comment_content' => $param['content']));
if ($id) add_comment_meta($id, 'review_average', $param['rating']);
if ($id) add_comment_meta($id, 'review_stars', array('Your Rating'=>$param['rating']));
return get_comment($id);
}
The Problem
I can't seem to get the ratings meta info into a response for the comments. On my way to writing the "index" function get_comments, I've written the "show" function, get_commment:
function get_review($param) {
$id = $param['id'];
$info = get_comment($id);
$res = array(
'id' => $id,
'author_name' => $info->comment_author,
'author_email' => $info->comment_author_email,
'author_id' => $info->user_id,
'date' => $info->comment_date,
'rating' => $info->review_average
);
return $res;
}
The response has rating: null. Same result with 'rating' => $info->meta->review_average, as well as using _review_average in both those scenarios.
I have another function for my custom posts, which are job_listings that in my app are customers.job_listing has a meta field that shows up in the default REST API response under meta as _job_location, but inside my get_customer function, $res['address'] = $info->_job_location; works just fine!
How do I get the damn rating_average meta!?
Well, 'rating' => get_comment_meta($id) inside my get_review method gives me this:
"rating": {
"review_average": [
"4"
],
"review_stars": [
"a:1:{s:11:\"Your Rating\";s:1:\"4\";}"
]
}
And then
'rating' => get_comment_meta($id)['review_average'][0],
'rating_info' => get_comment_meta($id),
Gives me a nice full
"rating": "4",
"rating_info": {
"review_average": [
"4"
],
"review_stars": [
"a:1:{s:11:\"Your Rating\";s:1:\"4\";}"
]
}
I'm learning php as I go, so I'd love if someone could post a comment about why
get_comment_meta($id)->review_average
returns null but
get_comment_meta($id)['review_average']
works.
Related
I am working on an ecommerce project, a generic book shop.
I started out with a Test Driven approach, and I adhered to it fully till now.
Different endpoints on this Lumen Microservice project have been successfully tested earlier to make sure they do CRUD operations. However, as I have to protect the Create, Update and Delete method with token authorisation, I am quite confused how to introduce tests for authorisation.
As of now this is my testing structure:
tests/app/Exceptions/HandlerTest.php
tests/app/Http/Controllers/BooksControllerTest.php
The tests are for index, show, store, update, delete. This is one of the tests:
public function testStoreBookByPost()
{
$book = factory('App\Book')->make();
$this->post(
'/books',
[
'isbn' => $book->isbn,
'title' => $book->title,
'description' => $book->description,
'author' => $book->author,
'image' => $book->image,
'price' => $book->price,
'slug' => $book->slug
]
);
$this
->seeJson(
[
'created' => true
]
)
->seeInDatabase(
'books',
[
'title' => $book->title
]
);
}
I had earlier separated Exception Handler tests, similarly I would prefer to separate the AuthControllerTest to AuthControllerTest.php.
What is the best way to do this?
Do I need to write the authorisation tests by refactoring all the BooksControllerTest?
Or should I just test for issuing of token and inability to manipulate database? Would that be fine?
Short answer: I needed to write the authorisation tests by refactoring all the BooksControllerTest
Long answer: I found out a fantastic way of logging in dummy users during testing.
With that I have created this method.
public function loginWithUserGetJWT()
{
$user = factory('App\User')->create(
[
'password' => bcrypt('366643') // random password
]
);
$content = $this
->post(
'/auth/login',
[
'email' => $user->email,
'password' => '366643'
]
)
->seeStatusCode(200)
->response->getContent();
$token = json_decode($content)->token;
return $token;
}
And I am reusing this method in all the test cases, like so:
public function testStoreBookByPost()
{
$token = $this->loginWithUserGetJWT();
$book = factory('App\Book')->make();
$this->post(
'/books',
[
'isbn' => $book->isbn,
'title' => $book->title,
'description' => $book->description,
'author' => $book->author,
'image' => $book->image,
'price' => $book->price,
'slug' => $book->slug,
'token' => $token
]
);
$this
->seeJson(
[
'created' => true
]
)
->seeInDatabase(
'books',
[
'title' => $book->title
]
);
}
I am using unclead / yii2-multiple-input widget.
I want to generate different number of rows with values from my database.
How can i do this?
I can design my columns in view and edit data manualy after page generated. But miss how to program the number of rows and its values in the view.
My code in view:
<?= $form->field($User, 'User')->widget(MultipleInput::className(), [
'min' => 0,
'max' => 4,
'columns' => [
[
'name' => 'name',
'title' => 'Name',
'type' => 'textInput',
'options' => [
'onchange' => $onchange,
],
],
[
'name' => 'birth',
'type' => \kartik\date\DatePicker::className(),
'title' => 'Birth',
'value' => function($data) {
return $data['day'];
},
'options' => [
'pluginOptions' => [
'format' => 'dd.mm.yyyy',
'todayHighlight' => true
]
]
],
]
])->label(false);
How can I make (for example) 8 rows with different values, and also have the ability to edit/remove/update some of them?
You need to look into the documentation as it says that you need to assign a separate field into the model which will store all the schedule in form of JSON and then provide it back to the field when editing/updating the model.
You have not added the appropriate model to verify how are you creating the field User in your given case above. so, i will try to create a simple example which will help you implement it in your scenario.
For Example.
You have to store a user in the database along with his favorite books.
User
id, name, email
Books
id, name
Create a field/column in you User table with the name schedule of type text, you can write a migration or add manually.
Add it to the rules in the User model as safe.
like below
public function rules() {
return [
....//other rules
[ [ 'schedule'] , 'safe' ]
];
}
Add the widget to the newly created column in ActiveForm
see below code
echo $form->field($model,'schedule')->widget(MultipleInput::class,[
'max' => 4,
'columns' => [
[
'name' => 'book_id',
'type' => 'dropDownList',
'title' => 'Book',
'items' => ArrayHelper::map( Books::find()->asArray()->all (),'id','name'),
],
]
]);
When saving the User model convert the array to JSON string.
like below
if( Yii::$app->request->isPost && $model->load(Yii::$app->request->post()) ){
$model->schedule = \yii\helpers\Json::encode($model->schedule);
$model->save();
}
Override the afterFind() of the User model to covert the json back to the array before loading the form.
like below
public function afterFind() {
parent::afterFind();
$this->schedule = \yii\helpers\Json::decode($this->schedule);
}
Now when saved the schedule field against the current user will have the JSON for the selected rows for the books, as many selected, for example, if I saved three books having ids(1,2,3) then it will have json like below
{
"0": {
"book_id": "1"
},
"2": {
"book_id": "2"
},
"3": {
"book_id": "3"
}
}
The above JSON will be converted to an array in the afterFind() so that the widget loads the saved schedule when you EDIT the record.
Now go to your update page or edit the newly saved model you will see the books loaded automatically.
i have white listed my domain and i get a message showing it was successful
{"result": "Successfuly updated whitelisted domains"}
but when i try getting the user id I get the error message
An error occuredMessenger Extensions are not enabled - could be "messenger_extensions" was not set on a url, the domain was not whitelisted or this is an outdated version of Messenger client
i am using A PC so an outdated version might not be it, and i have the messenger extension set this way
$get_started_display = "{
'recipient':{
'id': $sender_id
},
'message':{
'attachment':{
'type':'template',
'payload':{
'template_type':'button',
'text':'Click a button below to continue',
'buttons':[
{
'type':'web_url',
'title':'Add Leader Profile',
'url':'https://aadb-3120.herokuapp.com/login.html',
'webview_height_ratio' : 'full',
'messenger_extensions': true
},
{
'type':'postback',
'title':'Review Added Profile',
'payload':'review'
},
{
'type':'postback',
'title':'Help',
'payload':'help'
},
]
}
}
}
}";
please what are my doing wrong?
one of the Admins at the messenger platform community just confirmed that webviews extension don't work on PC, so the only way i can get the User ID is by adding it to the URL on the URL button or through session variables.
I don't think that is a valid json format. It should be in double quotes not single quotes. Why don't you write in php array instead and convert to json to reduce your chances of making mistakes.
eg.
$data = [
'recipient' => [
'id' => $sender_id
],
'message' => [
'attachment' => [
'type' => 'template',
'payload' => [
'template_type' => 'button',
'text' => 'Click a button below to continue',
'buttons' => [
[
'type' => 'web_url',
'url' => 'https://google.com',
'title' => 'Visit Google',
"webview_height_ratio" => "compact"
]
]
]
]
]];
$json = json_encode($data);
I want to make search in my project. I use typeahead but it's not working. This is my code:
<?php
echo '<label class="control-label">Select Repository</label>';
$template = '<div><p class="repo-language">{{no_telepon}}</p>' .
'<p class="repo-name">{{nama}}</p>' .
'<p class="repo-description">{{email}}</p></div>';
echo Typeahead::widget([
'name' => 'twitter_oss',
'options' => ['placeholder' => 'Filter as you type ...'],
'dataset' => [
[
'prefetch' => Penerima::find()->all(),
'datumTokenizer' => "Bloodhound.tokenizers.obj.whitespace('value')",
'display' => 'value',
'templates' => [
'notFound' => '<div class="text-danger" style="padding:0 8px">Unable to find repositories for selected query.</div>',
'suggestion' => new JsExpression("Handlebars.compile('{$template}')")
]
]
]
]);
?>
This question was asked long time a go.
I also faced the same problem, but i could figure-out this.
for future reference i add this post.
in your controller
$result = SampleModel::find()
->select('Attribute_name')
->where('name LIKE "%' . $searchParameter .'%"')
->asArray()
->all();
return Json::encode($result);
here you need to get the database value as "associative array", you can get that from using "asArray()".
then as you see return value as Json encode.
in your "View"
<?php
echo Typeahead::widget([
'name' => 'sampleName',
'options' => ['placeholder' => 'Filtering data ...'],
'scrollable' => true,
'pluginOptions' => ['highlight'=>true],
'dataset' => [
[
'remote' => [
'url' => Yii::$app->urlManager->createUrl(['sample/action']) .
'?searchParameter=%QUERY',
'wildcard' => '%QUERY'
],
'datumTokenizer' => "Bloodhound.tokenizers.obj.whitespace('Atribute_name')",
'display' => 'Atribute_name',
'limit' => 10,
],
],
'pluginEvents' => [
'typeahead:select' => 'function(e, s) {
EnableUserDetailsTypeAhead(s);
}',
]
]);
?>
here few things to be consider.
calling to the controller action. you can do that.
Yii::$app->urlManager->createUrl(['sample/action']) .
'?searchParameter=%QUERY',
'wildcard' => '%QUERY'
],
the below lines inside data set must be provide.
'datumTokenizer' => "Bloodhound.tokenizers.obj.whitespace('Atribute_name')",
'display' => 'Atribute_name',
you will get the expected data.
this sample code i have tested and this is working
From the docs:
prefetch: array, configuration for the prefetch options object. Refer documentation for the options you can set for this parameter. The return data must be Json encoded and converted to an associative array of the format [['value' => 'data1'], ['value' => 'data2'],...], where value is the fixed key set in display
You are passing an array of objects instead of an array of key value pairs. You can use asArray to create a list of objects. You will need to change display to the name of the field containing the data:
'prefetch' => Penerima::find()->select('title')->asArray()->all(),
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*'
}
}
}