I have just upgraded to cakephp 2.4.1 as it now supports JsonP. I was previously getting an a missing callback error in my ajax cross domain code. However the documentation does not mention any additional steps need to implement this so I would have thought that it should wold but i get the same error as before.
Do I need an extra piece of code to send the callbck back?
My Controller
public function api($mem_id = null) {
$options = array(
'fields' => array('Member.total_points'),
'conditions' => array('Member.member_no' => $mem_id),
'recursive' => -1
);
$members = $this->Member->find('first', $options);
$this->set(array(
'member' => $members,
'_serialize' => array('member')
));
}
}
ajax code
$('document').ready(function() {
$.ajax({
url: 'http://mydomain.com/loyalty/members/api/5749.json',
dataType: 'jsonp',
success: function(response) {
console.log(resonse);
},
error: function(jqXHR, textStatus, errorThrown) {
console.log(errorThrown);
}
});
});
It should have worked fine with the older Cake version too, just as I've described in your other question ;)
Anyways, look at the code in /lib/Cake/View/JsonView.php or in the API documentation. You have to define a view var named _jsonp, which can be either a string specifying the name of the query variable that holds the callback function name, or true which means a default query variable with the name callback is being looked up.
So as jQuery uses a query variable name of callback by default, defining true for _jsonp should do it:
$this->set(array(
'member' => $members,
'_serialize' => array('member'),
'_jsonp' => true
));
In case no query variable named callback could be found in the request URL (ie ?callback=whatever), you'd receive a regular JSON response instead.
See also
Cookbook > Views > JSON and XML views > JSONP response
If that just don't work, try to change the value of $jsonpParam from "callback" to "jsoncallback" (in lib/Cake/View/JsonView.php). I had to do that for make it work, thats because the name of the variable in the jsonp request, is jsoncallback, this one contains the string of the callback.
Related
I am trying to mock/test an Http request in a feature test to mock the whatsapp APIs, using Http::fake() in the setUp as follow:
Http::fake([
'https://www.test-whatsapp.com/upload/image' => Http::response(UploadedFile::fake()->image('TestWhatsapp.jpg')->get()),
'https://www.test-whatsapp.com/*' => Http::response(),
]);
I'd like to mock that when my app calls https://www.test-whatsapp.com/upload/image i get the file content but it seems that it never get sent. This is my actual test:
public function test_ask_attachment_image()
{
$this->receivedMessage(['type' => 'TEXT', 'text' => 'ask-attachment']);
$this->receivedMessage(['type' => 'IMAGE', 'caption' => 'some text', 'url' => '"https://www.test-whatsapp.com/upload/image']);
Http::assertSent(function (Request $request){
Log::debug($request->url(), [$request->body()]);
return true;
});
}
But the log gives an empty body.
I tried to print_r the whole response but still no sign of the file.
In the end I tried with an existing file using file_get_content but i got the same result.
I have logged the faked file and it get the content right.
How can i fake an endpoint to return me a file content? Thank you for any suggestions!
Edit
After debugging more i found out that the problem is not when i'm trying to create an image but a generic file using the method UploadedFile::fake()->create('TestWhatsapp.mp4', 1000). I get the size and the mimetype right but the file is still empty.
I Have an issue with my code. My axios delete request doesn't delete data from database. I get response 200 but it's all.
Component
deleteComment(index){
axios.delete(this.uri + this.comments[index].id)
.catch(error=>{
console.log(error)
});
}
Controller
public function destroy(BlogComments $cid){
$comments->delete();
return response()->json([
'comments'=> $id,
'Message'=> "OK!"
]);
}
Console -> Network
Request URL: http://127.0.0.1:8000/api/blog-comments/4
Request Method: DELETE
Status Code: 200 OK
Remote Address: 127.0.0.1:8000
Referrer Policy: no-referrer-when-downgrade
When I make response form controller, I have only empty arrays
comments[]
UPDATE
I fixed it.
I replaced
Route::resources('/blog-comments', 'DashboardBlogCommentsController');
By
Route::delete('/blog-comments/{id}', 'DashboardBlogCommentsController#destroy');
Why resources doesn't work ? I used the same method in my other page and it's worked
Route::resource('/advantages', 'DashboardAdvantagesController');
public function destroy(HomepageAdvantages $advantage){
$advantage->delete();
return response()->json([
'advantage'=>$advantage,
'message'=> 'task created'
]);
}
Maybe a answer for:
Why resources doesn't work ?
Route
This is maybe a typo in your edit and not in your code, but is not resources:
Route::resources('/blog-comments', 'DashboardBlogCommentsController');
is resource:
Route::resource('/blog-comments', 'DashboardBlogCommentsController');
And based on the code before the UPDATE of your question:
Controller DashboardBlogCommentsController
public function destroy(BlogComments $comment){
// use delete() on the variable where you assigned the object
$comment->delete();
return response()->json([
// 'comments'=> $id, this no needed, the comment doesn't exist anymore
'message' => 'OK!' ], 202);
}
Hope it's helps
I am trying to pass JSON into an Add() action of my controller but the response I get is either a response from the Index() action or "null" depending on entries in my routes.php (shown below).
I'm using Firefox plugin RESTClient to test/pass json to my actions:
Method: POST
URL: http://MyApp/notes.json (my understanding is this should call Add action based on cakephp docs )
JSON:
{
"post_id":"123",
"note":"hello world"
}
When I have the following in my routes.php, the response is "null"
Router::scope('/', function ($routes) {
$routes->extensions(['json']);
$routes->resources('Notes');
Removing $routes->resources('Notes'); from this scope returns a response from the index action and all items in the Notes model are returned in the response.
Furthermore, when I implement this through Crud.Add in an api prefix, I get valid results. That being said trying to understand what I have incorrect in my routes.php or add action that could be causing the null or routing to index action.
Add Action:
public function add()
{
$note = $this->Notes->newEntity();
if ($this->request->is('post')) {
$note = $this->Notes->patchEntity($note, $this->request->data);
if ($this->Notes->save($note)) {
$message = 'Saved';
} else {
$message = 'Error';
}
}
$this->set('_serialize', ['note', 'message']);
}
Routes.php:
<?php
use Cake\Core\Plugin;
use Cake\Routing\Router;
Router::defaultRouteClass('DashedRoute');
Router::extensions(['json'], ['xml']);
Router::prefix('api',function($routes) {
$routes->extensions(['json','xml']);
$routes->resources('Notes');
});
Router::scope('/', function ($routes) {
$routes->extensions(['json']);
$routes->resources('Notes');
$routes->connect('/', ['controller' => 'Pages', 'action' => 'display', 'home']);
$routes->connect('/pages/*', ['controller' => 'Pages', 'action' => 'display']);
$routes->fallbacks('DashedRoute');
});
Plugin::routes();
Thanks in advance.
You didn't set any variables for the view, other than serialization options, so you get null because there's nothing to serialize, ie you are missing something like
$this->set(compact('note', 'message'));
in your action.
And without the resource routes a request to /notes maps to the index action of the Notes controller, because that's how fallback routes are connected, no action = index action.
See also
Cookbook > Views > JSON And XML Views > Using Data Views with the Serialize Key
Cookbook > Routing > Fallbacks Method
So, I have a table that is auto-generated using DataTables. An action in my CakePHP grabs the data for that table, and formats it into JSON for datatables to use, this is the formatted JSON:
<?php
$data = array();
if (!empty($results)) {
foreach ($results as $result) {
$data[] = [
'name' => $result->name,
'cad' => $this->Number->currency($result->CAD, 'USD'),
'usd' => $this->Number->currency($result->USD, 'USD'),
'edit' => '<a href="' .
$this->Url->build(['controller' => 'Portfolios', 'action' => 'edit', $result->id]) .
'"><i class="fa fa-pencil"></i></a>',
'delete' => '<input type="checkbox" class="delete" value="' . $result->id . '">'
];
}
}
echo json_encode(compact('data'));
As you can see, I have a 'delete' option in there that outputs a checkbox with the value of the id of the corresponding element. When that checkbox is checked, a delete button is showing which sends this ajax request:
$('a#delete').on('click', function(e) {
e.preventDefault();
var checkedValues = [];
$('input.delete:checked').each(function() {
checkedValues.push($(this).val());
});
$.ajax({
url: $(this).attr('href'),
type: 'POST',
data: checkedValues
});
})
This ajax post goes to my controller action delete(). The problem I'm having is that I'm getting an error that states "Invalid Csrf Token". I know why this is happening, I'm submitting a form with Csrf protection on, that has no Csrf token added to it.
I can't figure out how to manually create a Csrf token for this situation (where the input values are generated after the page has loaded). Nor can I figure out how to disable Csrf protection. I read this, but the code is placed in the beforeFilter function, and as far as I understand it, that means it's run on every action, not just this one, and that's not what I want. Plus, to be completely honest, I would prefer a solution where I don't deactivate security functions.
Is there anyway to disable Csrf for this specific action, or is there a better way to do this?
read all about the CSRF component here
http://book.cakephp.org/3.0/en/controllers/components/csrf.html
you can disable for a specific action here:
http://book.cakephp.org/3.0/en/controllers/components/csrf.html#disabling-the-csrf-component-for-specific-actions
public function beforeFilter(Event $event) {
if (in_array($this->request->action, ['actions_you want to disable'])) {
$this->eventManager()->off($this->Csrf);
}
}
Above answer does not work in Cakephp 3.6 or later.
Cakephp add object of CsrfProtectionMiddleware in src/Application.php.
If you have to remove CSRF protection for specific controller or action then you can use following work around:
public function middleware($middlewareQueue)
{
$middlewareQueue = $middlewareQueue
// Catch any exceptions in the lower layers,
// and make an error page/response
->add(ErrorHandlerMiddleware::class)
// Handle plugin/theme assets like CakePHP normally does.
->add(AssetMiddleware::class)
// Add routing middleware.
// Routes collection cache enabled by default, to disable route caching
// pass null as cacheConfig, example: `new RoutingMiddleware($this)`
// you might want to disable this cache in case your routing is extremely simple
->add(new RoutingMiddleware($this, '_cake_routes_'));
/*
// Add csrf middleware.
$middlewareQueue->add(new CsrfProtectionMiddleware([
'httpOnly' => true
]));
*/
//CSRF has been removed for AbcQutes controller
if(strpos($_SERVER['REQUEST_URI'], 'abc-quotes')===false){
$middlewareQueue->add(new CsrfProtectionMiddleware([
'httpOnly' => true
]));
}
return $middlewareQueue;
}
So i needed a fix for cakephp 3.7 and using $_SERVER['REQUEST_URI'] is realllly not the way to go here. So here is how you are supposed to do it after reading through some documentation.
In src/Application.php add this function
public function routes($routes)
{
$options = ['httpOnly' => true];
$routes->registerMiddleware('csrf', new CsrfProtectionMiddleware($options));
parent::routes($routes);
}
Comment out the existing CsrfProtectionMiddleware
public function middleware($middlewareQueue)
{
...
// $middlewareQueue->add(new CsrfProtectionMiddleware([
// 'httpOnly' => true
// ]));
}
Open your config/routes.php add $routes->applyMiddleware('csrf'); where you do want it
Router::prefix('api', function ($routes)
{
$routes->connect('/', ['controller' => 'Pages', 'action' => 'index']);
$routes->fallbacks(DashedRoute::class);
});
Router::scope('/', function (RouteBuilder $routes)
{
$routes->applyMiddleware('csrf');
$routes->connect('/', ['controller' => 'Pages', 'action' => 'dashboard']);
$routes->fallbacks(DashedRoute::class);
});
Note that my api user now has no csrf protection while the basic calls do have it.
If you have more prefixes don't forgot to add the function there aswell.
in Application.php this worked for me....
$csrf = new CsrfProtectionMiddleware();
// Token check will be skipped when callback returns `true`.
$csrf->whitelistCallback(function ($request) {
// Skip token check for API URLs.
if ($request->getParam('controller') === 'Api') {
return true;
}
});
I am working with CakePHP. What I need in my application is autosuggestion. I used following code to achieve my goal:
jQuery("#name").autocomplete( '<?php echo HTTP_PATH.'songs/sss'; ?>', {
multiple: true,
mustMatch: true,
matchContains: true,
autoFill: false,
});
Sending my request to the SongsController' sss function... My controller function is:
public function sss(){
$this->layout = '';
$condition = '';
$condition = array('Poet.status'=>'3');
$poet_name = $this->Poet->find('list', array('conditions' => $condition));
return $poet_name;
}
First issue is that I am returning my result in array. How would I separate my result again in a suggestion list.
Second thing is that when I tried to check the response using the Firebug panel, I noticed that CakePHP is expecting a view at this point. I don't want any sort of view as I am not updating anything...
You'll want to check out serializing you data as JSON: http://book.cakephp.org/2.0/en/views/json-and-xml-views.html#using-data-views-with-the-serialize-key. By setting a _serialize variable in the view you are telling Cake what data is important when it gets a request for a data view.
You will also need to add Router::parseExtensions('json'); and a .json to the end of your URI in the jQuery call so that Cake knows to respond with a JSON data view and use the data in the _serialize key you set like I mentioned above. There more info on file extensions here: http://book.cakephp.org/2.0/en/development/routing.html#file-extensions
Here's how I would write the method:
public function sss(){
$this->layout = '';
$condition = array('Poet.status'=>'3');
$poets = $this->Poet->find('list', array('conditions' => $condition));
$this->set(compact('poets'));
$this->set('_serialize', $poets);
}