regex not working with Wordpress REST API - php

The Wordpress REST API lets you validate a parameter against a regex if you provide one in the API route's schema:
register_rest_route( $this->namespace, "/posts", [
"methods" => "POST",
"permission_callback" => "__return_true",
"callback" => "create_post",
"args" => [
"title" => [
"type" => "string",
"description" => "The posts's title",
"required" => true,
"pattern" => '/^[A-Za-z0-9 ]+$/' // <-- Here's the pattern!
]
]
]);
The pattern I provided should accept all alphanumeric values and single spaces. Indeed, the pattern works if I test it in isolation:
preg_match( '/^[A-Za-z0-9 ]+$/', 'Title'); // Returns 1 as expected
preg_match( '/^[A-Za-z0-9 ]+$/', 'Test Title'); // Returns 1 as expected
preg_match( '/^[A-Za-z0-9 ]+$/', '123'); // Returns 1 as expected
preg_match( '/^[A-Za-z0-9 ]+$/', '&Title&'); // Returns 0 as expected
However, the pattern does not seem to work in the context of the Wordpress API! If I make a request to the /post route passing any of the above test values as the title, I always get the following error:
{
"code": "rest_invalid_param",
"message": "Invalid parameter(s): title",
"data": {
"status": 400,
"params": {
"title": "title does not match pattern /^[A-Za-z0-9 ]+$/."
},
"details": {
"title": {
"code": "rest_invalid_pattern",
"message": "title does not match pattern /^[A-Za-z0-9 ]+$/.",
"data": null
}
}
}
}
I've dug a bit deeper and discovered that internally Wordpress uses this function:
function rest_validate_json_schema_pattern( $pattern, $value ) {
$escaped_pattern = str_replace( '#', '\\#', $pattern );
return 1 === preg_match( '#' . $escaped_pattern . '#u', $value );
}
If I run my test values against this function, preg_match always returns false. Unfortunately I don't know the first thing about regexes so I don't understand what this function is doing. I've read the #u bit should be about unicode characters, but can't figure it out besides that. Can somebody please help?
(The Wordpress docs for using a pattern are here, but I can't work anything out of that either).

Got it. So apparently what's happening is that the rest_validate_json_schema_pattern function is adding the regex's delimiting characters, and since my regex already had delimiting characters, things were not working, so the solution is to provide the regex without any delimiting characters to begin with:
register_rest_route( $this->namespace, "/posts", [
"methods" => "POST",
"permission_callback" => "__return_true",
"callback" => "create_post",
"args" => [
"title" => [
"type" => "string",
"description" => "The posts's title",
"required" => true,
"pattern" => '^[A-Za-z0-9 ]+$' // <-- Regex without delimiting characters
]
]
]);
Confusing if you ask me, but it works!

Related

Malformed UTF-8 characters, possibly incorrectly encoded Laravel

Laravel 8
I have seen a few of these questions, but the answers are either msising, not for php, or some weird hack.
I have a table in the database, mariadb, with the field type of LONGTEXT - equates to JSON field.
in my model I do:
protected $casts = [
'event_data' => 'array',
];
public function setEventDataAttribute($value) {
$this->attributes['event_data'] = json_encode($value);
}
The data going into the field is:
array:12 [
"start" => "2022-08-23T00:00:00+00:00"
"end" => "2022-08-23T00:00:00+00:00"
"all_day" => false
"unassigned" => true
"draft" => true
"title" => "ggggg"
"notes" => "test"
"active" => true
"schedule_calendar_id" => null
"jobcode_id" => 122723308
"customfields" => array:2 [
1782352 => "Dirty"
1782354 => "Vacant"
]
"color" => "#888888"
]
When I run json_encode($value) where $value is the above array, I get:
{
"start": "2022-08-23T00:00:00+00:00",
"end": "2022-08-23T00:00:00+00:00",
"all_day": false,
"unassigned": true,
"draft": true,
"title": "ggggg",
"notes": "test",
"active": true,
"schedule_calendar_id": null,
"jobcode_id": 122723308,
"customfields": {
"1782352": "Dirty",
"1782354": "Vacant"
},
"color": "#888888"
}
which according to every validator out there, this is valid JSON. How ever attempting to set this as the attribute into the field throws:
Malformed UTF-8 characters, possibly incorrectly encoded
I can, above the $this->attributes['event_data'] do:
dump(json_encode($value), json_decode(json_encode($value)));
And get the json object listed above and get a stdClass class object of the decoded json.
So my question is:
If the online JSON formatters are saying this is valid JSON, php has no issue encoding and decoding it - why can't laravel insert it? Is it the dates? they must be in ISO8601 Format.
What is going on? I have done this, json encoding like this, a thousand times with no issue.

Boolean TRUE converted to string "1" when testing GraphQL request with Codeception

I have a Wordpress website with the WPGraphQL plugin installed and running some tests with Codeception to test the GraphQL queries. For a bit more context, I am using Codeception's REST module and loading Wordpress in the tests with wp-browser.
The following test to register a user is failing:
class RegisterCest {
public function seeCanRegister( ApiTester $I ) {
$I->send( "POST", "https://example.com/graphql", [
"query" => '
mutation registerUser( $input: RegisterUserInput! ) {
registerUser( input: $input ) {
user {
username
email
addressMain
addressSec
adminArea
country
city
firstName
lastName
postalCode
}
}
}
',
"variables" => [
"input" => [
"clientMutationId" => "registerUser",
"firstName" => "John",
"lastName" => "Smith",
"username" => "user#example.com",
"email" => "user#example.com",
"password" => "example",
"addressMain" => "Fake Street",
"addressSec" => "Apt #1",
"adminArea" => "New York",
"country" => "US",
"city" => "New York",
"postalCode" => 00000,
"planSlug" => null,
"subscribeToNewsletter" => true
]
]
]);
$I->seeResponseCodeIs( 200 );
$I->dontSeeResponseJsonMatchesJsonPath( "$.errors" );
}
}
The test fails because I get the following error in the response:
Expected type Boolean at value.subscribeToNewsletter; Boolean cannot
represent a non boolean value: 1"
Basically, what seems to happen is that the boolean true set as the value for subscribeToNewsletter is transformed into the string "1" before the query is executed, which causes the query to be invalid because in the GraphQL schema it is specified that subscribeToNewsletter is expected to be a boolean.
I do not get this error when running the query in the app; it's only coming up in this test. Can anyone think of a reason why?
As suggested by Arvin Jason Cabrera in the comments, the problem was I was not setting the Content-Type to application/json. In my case, I simply had to add the following line
$I->haveHttpHeader('Content-Type', 'application/json');
before sending the request.

How to convert below MongoDB query into PHP?

Below query worked fine in Studio 3T and Robomongo, But I want to convert it into PHP format,
Following is my query,
db.news.aggregate([
{
$match: { "_id" : "1" }
},
{
"$project": {
"comments": {
"$filter": {
"input": "$comments",
"as": "comment",
"cond": { "$eq": [ "$$comment.status", "active" ] }
}
}
}
}, {
"$project": {
"comments": {
"$slice": [
{
"$slice": [
"$comments",
{
"$subtract": [ { "$size": [ "$comments" ] }, 1 ]
}
]
}, -1
]
}
}
}])
I have tried below, But it giving error "Error: localhost:27017: FieldPath field names may not be empty strings."
PHP converted sample:
<?php
$commentsAggregate=array(
array('$match' => array("_id" => "1")),
array('$project' => array(
"comments" => array(
'$filter' => array(
"input" => "$comments",
"as" => "comment",
"cond" => array('$eq' => array( "$$comment.status", 'active'))
)))),
array('$project' => array(
"comments" => array(
'$slice' => array(array(
'$slice' => array("$comments",array('$subtract' => array( array( '$size' => array("$comments")),1)))
), -1)
)))
);
$cursor=$collectionNews->aggregate($commentsAggregate);
Please help me to convert above query.
The error message "FieldPath field names may not be empty strings" originates from the server. Looking at the example PHP code you've provided, I notice that you're inconsistently using single- and double-quoted strings. In particular, these two strings stand out:
"$$comment.status"
"$comment"
PHP is evaluating variable references inside double-quoted strings. Assuming the local scope does not actually have a $comment variable defined, those strings are going to resolve to "$.status" and "", respectively. As evidenced in this script and execution output on 3v4l.org, those examples should at least result in a PHP notice for an undefined variable (my local PHP configuration happens to report this at the "error" level). If you have no record of that error message, I would suggest the following:
Check your error_reporting configuration.
Ideally, you should report everything (E_ALL) in a development environment. Full reporting is also advisable for production, although there you would likely want to disable display_errors (for security) and instead ensure everything is logged properly.
If it turns out the error was logged, look into how it was missed while debugging this issue.
As for fixing the root cause, you should be mindful to use single-quoted strings when writing MongoDB queries/commands in PHP. There is a note about this in the MongoCollection::find() documentation, but it's not something we repeat on every page, as PHP's double-quoted string evaluation is outside the control of the driver.

Elasticsearch in php doesn't recognize dash

I'm working on a project and try to make a search with elasticsearch but my field can contain dash and when I search with it I can't find the result I'm looking for, so I tried to change the mapping but the index doesn't work at all. I don't have any error message but I can't find what I indexed even using a different field. So what I did was :
$params = [
'index' => 'arc',
'type' => 'purchase',
'id' => $purchase['id'],
'body' => $purchase
];
It worked great with that except for the field with the dash. My $purchase looks like that :
array:34 [
"id" => 163160
"distant" => "MOR-938BBM28147090"
[...]
]
so when I search for "MOR" I find the result but when I do "MOR-" nothing. I tried to change the mapping by doing that :
$params = [
'index' => 'arc',
'type' => 'purchase',
'id' => $purchase['id'],
'body' => [
'mappings' => [
'_default_' => [
'properties' => [
'distant' => [
'type' => 'string',
'index' => 'not_analyzed'
]
]
]
],
$purchase
]
];
But with that even if I try to search "163160" I can't find any result.
Whitespace analyzer could be the right solution in this case. It takes into account only whitespaces while breaking text into tokens, and characters like "-" or "_" are still treated as a part of a term.
But if you need to do a partial matching, for example with "MOR-" token, then it requires a bit more complicated mapping.
As I don't know php, I'll be using Elasticsearch syntax. First, create a proper mapping:
PUT http://127.0.0.1:9200/arc
{
"settings": {
"analysis": {
"analyzer": {
"edge_ngram_analyzer": {
"tokenizer": "my_tokenizer"
}
},
"tokenizer": {
"my_tokenizer": {
"type": "edge_ngram",
"min_gram": 3,
"max_gram": 18,
"token_chars": [
"letter",
"digit",
"punctuation"
]
}
}
}
},
"mappings": {
"purchase": {
"properties": {
"distant": {
"type": "string",
"analyzer": "edge_ngram_analyzer"
}
}
}
}
}
As you can see, I use EdgeNGram tokenizer here. When you index a document with MOR-938BBM28147090 in distant field, it will create following tokens:
[MOR, MOR-, MOR-9, MOR-93, MOR-938, MOR-938B, MOR-938BB, ...]
The core point here is punctuation character class in token_chars list, that tells elasticsearch, that dash character (and some others like ! or ") should be included in a token and not treated as a "split char".
Now when I index the document:
PUT http://127.0.0.1:9200/arc/purchase/163160
{
"distant": "MOR-938BBM28147090"
}
and run a term search query:
POST http://127.0.0.1:9200/arc/purchase/_search
{
"query": {
"bool" : {
"must" : {
"term" : {
"distant": "MOR-93"
}
}
}
}
}
I get in response:
"hits": {
"total": 1,
"max_score": 0.6337049,
"hits": [
{
"_index": "arc",
"_type": "purchase",
"_id": "163160",
"_score": 0.6337049,
"_source": {
"distant": "MOR-938BBM28147090"
}
}
]
}

Actions on Google Fulfillment Response

When you receive a request, your fulfillment must return a response
I have a HTTPS endpoint which receives commands sent thru the google assistant(My Fulfilment URL). But i want to return a text to the user for every request made
Eg:
USER REQUEST : "Tell, 'app name' to do blah blah"
ASSISTANT RESPONSE : "Okay, sure"
As documented in this article --> https://developers.google.com/actions/components/fulfillment (RESPONSE FORMAT) , I have coded the json file according to the format said in the above link
But it says your fulfillment must return a response
RESPONSE.JSON
"finalResponse": {
"richResponse": {
"items": [
{
"simpleResponse": {
"textToSpeech": "sure thing",
"displayText": "Sure, thing!"
}
]
}
}
In my Fulfilment end point i did the above.
fulfilment.php
$file = [
"expectUserResponse" => false,
"finalResponse" => [
"richResponse" => [
"items" => [
[
"simpleResponse" => [
"textToSpeech" => "Sure thing!",
"displayText" => "Sure, thing?"
]
]
]
]
]
];
echo json_encode($file);
header('Content-Type: application/json');
How do i return this file in php back to google assistant?
I am using PHP :)
For starters - that isn't valid JSON. Valid JSON would look something like this:
{
"finalResponse": {
"richResponse": {
"items": [{
"simpleResponse": {
"textToSpeech": "sure thing",
"displayText": "Sure, thing!"
}
}]
}
}
}
Notice the opening and closing brackets to designate that this is a JSON object. What you're sending is a string that has been encoded as a JSON string. Using PHP, the easiest way to create valid JSON is to use json_encode and pass it a nested associative array. So something like this would generate it.
<?php
header('Content-Type: application/json');
$a = [
"finalResponse" => [
"richResponse" => [
"items" => [
[
"simpleResponse" => [
"textToSpeech" => "Sure thing!",
"displayText" => "Sure, thing?"
]
]
]
]
]
];
echo json_encode($a);
Note that the header() must come before anything, even a blank line, has been sent.
However... that might not be your only problem.
This doesn't look like it has all the fields that you should reply. If you're only sending a finalResponse, then you probably also need to set expectUserResponse to false.
(And are you sure that you want to end the conversation every time?)

Categories