XF 2.2 Bad Request on Ajax call

FoxSecrets

Active member
I am searching for part of a username and copying the returned list to a select field just as example and am getting error 400 Bad Request on ajax call. Did not find any clear solution on old threads. Can someone help me to solve that?

AJAX:
Code:
function search() {
        var user = $("#user").val();
        $.ajax({
            url: "{{ link('my-page/search') }}",
            method: "POST",
            data: { username: user },
            success: function(data) {
                $("#result").val(data);
            },
            error: function(res){
              alert("Error: " + res.status + " " + res.statusText);
            }
          });
    }

PHP:
Code:
public function actionSearch()
    {
        if (isset($_POST['username'])) {
            $username = $_POST['username'];
     
            $finder = \XF::finder('XF:User');
            $result = $finder->where('user_name', $username)->fetch();

            if ($result->count() > 0) {
                echo $result;
            } else {
                echo "No users found.";
            }
        }
    }
 
Last edited:
It's likely because the CSRF token is not included. It is required for POST requests. Using XF.ajax instead will handle this (among other things) automatically:
JavaScript:
function search()
{
    const user = $("#user").val();
    XF.ajax(
       "POST",
       XF.canonicalizeUrl("index.php?my-page/search"),
       { username: user },
       (data) => {
           $("#result").val(data);
       }
   );
}

And you'll want to return a reply object from the action instead of using echo (the JS handling probably won't succeed otherwise). You should also prefer using the input filterer instead of globals ($_GET, $_POST, etc.), and assert the request is a POST request:
PHP:
// under your namespace declaration:
use XF\Mvc\Reply\AbstractReply;

// ...

public function actionSearch(): AbstractReply
{
    $this->assertPostOnly();

    $user = null;

    $username = $this->filter('username', 'str');
    if ($username)
    {
        $user = $this->em()->findOne('XF:User', ['username' => $username]);
    }

    $view = $this->view();
    $view->setJsonParams([
        'user' => $user ? $user->toArray() : null,
    ]);
    return $view;
}
 
Last edited:
My mistake, it should be filter. I have also revised the code above to output the entity as an array, but you may want to further customize the output values and what your JS does with it.
 
@Jeremy P , how would be to search for the string and return an array like [{ id: 1, user: "name1"}, { id: 2, user: "name2"}] ?

Code:
$finder = \XF::finder('XF:User');
$user = $finder->where('username', 'LIKE', "'%'.$username.'%'")->fetch();

Is there anyway to write raw SQL? I still didn't get to use the way you did.
 
You don't need the inner quotes, but you should use \XF\Mvc\Entity\Finder::escapeLike:

PHP:
$finder = \XF::finder('XF:User');
$users = $finder->where('username', 'like', $finder->escapeLike($username, '%?%')->fetch();

You can write raw SQL, but you should almost always use entities if you're inserting/updating core records as that allows all the expected lifecycle hooks to run.
 
Last edited:
Great! Another question: Where does this object come from? PHP or XF? (sorry i'm newbie)
Also, why user is returning like XF:User[x] if the query is already done?

1690558936661.png
 
Last edited:
Great! Another question: Where does this object come from? PHP or XF? (sorry i'm newbie)
That's how the framework renders JSON responses. It comes from PHP and facilitates things like updating conversation/alert counts and inserting rendered templates (and auto-loading any newly-required CSS or JS).

Also, why user is returning like XF:User[x] if the query is already done?
They're being cast to strings, probably implicitly. If you want to return an array with select values, you could process the output a bit:

PHP:
$output = array_map(
    function (\XF\Entity\User $user)
    {
        return [
            'id' => $user->user_id,
            'user' => $user->username,
        ];
    },
    $users->toArray()
);

$view = $this->view();
$view->setJsonParams([
    'users' => $output,
]);
return $view
 
Top Bottom