Router::extensions(['json']) has no effect on the ErrorController - cakephp

My goal is to have exception messages converted to JSON when requested (via headers and/or filename extension). According to JSON exception in cakephp 3, this needs to happen in src/Controller/ErrorController.php. If I simply add $this->RequestHandler->renderAs($this, 'json') to beforeFilter() it works fine, but then again, I should be forcing JSON only when JSON is explicitly requested.
I started by adding Router::extensions(['json']) to the top of my routes.php following this advice.
Then I created a test action /foo/bar.json throwing a new BadRequestException('hello').
Lastly, debugging in src/Controller/ErrorController.php:
public function beforeRender(Event $event)
{
parent::beforeRender($event);
$this->viewBuilder()->setTemplatePath('Error');
debug($this->getRequest());exit;
}
When I request it, I can see that the extension isn't parsed at all:
object(Cake\Http\ServerRequest) {
[protected] params => [
'controller' => 'Foo',
'action' => 'bar.json',
// …
'_ext' => null,
]
Trying $this->getRequest()->is('json') also returns false.
When I add debug(Router::extensions());exit; to the top of config/routes.php, nothing happens. Could this mean the file isn't read at all? Or is that because the exception aborts the execution before it even gets there?
How do I force JSON error messages when explicitly requested?
Relevant code:
config/routes.php:
<?php
use Cake\Core\Plugin;
use Cake\Routing\RouteBuilder;
use Cake\Routing\Router;
use Cake\Routing\Route\DashedRoute;
use Cake\Http\Middleware\CsrfProtectionMiddleware;
Router::extensions(['json']);
debug(Router::extensions());exit;
Router::defaultRouteClass(DashedRoute::class);
Router::scope('/', function (RouteBuilder $routes) {
$routes->registerMiddleware('csrf', new CsrfProtectionMiddleware());
$routes->applyMiddleware('csrf');
$routes->connect('/', ['controller' => 'Pages', 'action' => 'display', 'home']);
$routes->connect('/pages/*', ['controller' => 'Pages', 'action' => 'display']);
$routes->fallbacks(DashedRoute::class);
});
Router::prefix('abc', function ($routes) {
$routes->fallbacks(DashedRoute::class);
});
Router::prefix('def', function ($routes) {
$routes->fallbacks(DashedRoute::class);
});
Router::prefix('ghi', function ($routes) {
$routes->fallbacks(DashedRoute::class);
});
Router::prefix('api', function ($routes) {
$routes->prefix('jkl', function ($routes) {
$routes->fallbacks(DashedRoute::class);
});
});
Router::prefix('mno', function ($routes) {
$routes->fallbacks(DashedRoute::class);
});
//Plugin::routes();

Use setExtensions() inside a corresponding scope instead of the global call
If using a prefix, configure that exact prefix.
Clear the cache: bin/cake cache clear_all
If done right, there will be no need to additionally configure src/Controller/ErrorController.php like in this answer.
Working code:
Router::prefix('myprefix', function ($routes) {
$routes->setExtensions(['json']);
$routes->fallbacks(DashedRoute::class);
});
That's it. Requests having the .json extension within /myprefix to an action that throws an exception will be returned as JSON.

Related

Cakephp 3 - limit () and contained model

I have two models, named Post and Comment, which are linked by Post hasMany Comment and Comment belongsTo Post.
I want to fetch all posts with the first five comments each. I would use this code:
$this->Posts->find('all')
->contain([
'Comments' => function($q) {
return $q
->order('created ASC')
->limit(5);
}
]);
This works incorrectly with the limit(). Please help to solve this.
I used this example:
How to limit contained associations per record/group?
I tried to like this (in Post model):
$this->hasOne('TopComments', [
'className' => 'Comments',
'strategy' => 'select',
'conditions' => function (\Cake\Database\Expression\QueryExpression $exp, \Cake\ORM\Query $query) {
$query->leftJoin(
['CommentsFilter' => 'comments'],
[
'TopComments.post_id = CommentsFilter.post_id',
'TopComments.created < CommentsFilter.created'
]);
return $exp->add(['CommentsFilter.id IS NULL']);
}
]);
In Post controller:
$this->Posts->find('all')
->contain([
'TopComments' => function($q) {
return $q
->order('TopComments.created ASC')
->limit(5);
}
]);
Unfortunately this does not work. I do not know where I'm wrong.
You should try this
In your model
$this->hasMany('Comments', [
'foreignKey' => 'post_id'
]);
In your controller
$this->Posts->find()
->contain([
'Comments' => function($q) {
return $q
->order(['created' =>'ASC'])
->limit(5);
}
]);

CakePHP 3 Rss error

i want do a rss, I followed this http://book.cakephp.org/3.0/en/views/helpers/rss.html. But the things not working correctly, because when access the router of the rss, it returns a controller error, saying that controller does not exists. My route is this:
/posts/index.rss
When do this request, it return a error of the controller not found.
The action index.rss is not defined in PostsController
I declared that "app" to accepts rss..My complete config/routes.php
use Cake\Core\Plugin;
use Cake\Routing\Router;
Router::defaultRouteClass('Route');
Router::scope('/', function ($routes) {
Router::extensions(['json', 'xml', 'rss']);
$routes->connect('/', ['controller' => 'Fronts', 'action' => 'index']);
$routes->connect('/contact', ['controller' => 'Fronts', 'action' => 'contact']);
$routes->connect(
'/:controller/:action/:id-:slug',
[],
[
'pass' => ['id', 'slug'],
'id' => '[0-9]+',
'routeClass' => 'DashedRoute'
]
);
$routes->fallbacks('InflectedRoute');
});
Plugin::routes();
I also made the LoadComponent in :: initialize () of the controller
public function initialize()
{
parent::initialize();
$this->loadComponent('RequestHandler');
}
And my controller
class PostsController extends AppController
{
...
public function index()
{
...
if($this->RequestHandler->isRss()) :
$_rss = $this->Posts->find()->limit(20);
$this->set(compact('_rss'));
return;
endif;
...
}
}
Whats is wrong?
Thankss..!!!
You're defining the extension the wrong way, Router::extensions() is ment to define global extensions for all routes that are being connected after Router::extensions() has been invoked.
So, inside a scope, the call to Router::extensions() is too late, as it's the Router::scope() method, that, when invoked, reads the global extensions and passes them into the scope.
Either invoke Router::extensions() outside of the scope
Router::extensions(['json', 'xml', 'rss']);
Router::scope('/', function ($routes) {
// ...
});
or use RouterBuilder::extensions() inside of the scope (note that this overrides the global extensions that the scope might have been inherited)
Router::scope('/', function (\Cake\Routing\RouteBuilder $routes) {
$routes->extensions(['json', 'xml', 'rss']);
// ...
});
See also Cookbook > Routing > Routing File Extensions

Is it possible in Cake 3.0 to add a prefix in routing that isn't actually in the url?

What I want to achieve is to keep my baked controllers in src/Controller/, and to extend these for both my admin controllers (in src/Controller/Admin/) and my customer controllers (in src/Controller/Customer/). I have already achieved this for admins with:
Router::prefix('admin', function ($routes) {
$routes->fallbacks('DashedRoute');
});
However, for customers I don't want /customer/ in the URL. Is this possible?
For example, http://example.org/users should use src/Controller/Customer/UsersController.php
I think I solved it:
Router::prefix('admin', function ($routes) {
$routes->fallbacks('DashedRoute');
});
Router::scope('/', function ($routes) {
$routes->connect(
'/:controller/:action/*',
['prefix' => 'customer'],
['routeClass' => 'DashedRoute']
);
$routes->connect(
'/:controller/*',
['prefix' => 'customer'],
['routeClass' => 'DashedRoute']
);
});

pass value from jquery to Cakephp controller

I am beginner to CakePHP and trying the send the textbox value during change function to my controller action using ajax.
Can someone help to how to pass the value form jquery to cakephp controller. If there is example code could great.
Let's say you want to send your data to a method called 'ajax_process' in the users controller. Here's how I do it:
in your view .ctp (anywhere)
<?php
echo $this->Form->textarea('text_box',array(
'id' => 'my_text',
));
?>
<div id="ajax_output"></div>
In the same view file - the jquery function to call on an event trigger:
function process_ajax(){
var post_url = '<?php echo $this->Html->url(array('controller' => 'users', 'action' => 'ajax_process')); ?>';
var text_box_value = $('#my_text').val();
$.ajax({
type : 'POST',
url : post_url,
data: {
text : text_box_value
},
dataType : 'html',
async: true,
beforeSend:function(){
$('#ajax_output').html('sending');
},
success : function(data){
$('#ajax_output').html(data);
},
error : function() {
$('#ajax_output').html('<p class="error">Ajax error</p>');
}
});
}
In the UsersController.php
public function ajax_process(){
$this->autoRender = false; //as not to render the layout and view - you dont have to do this
$data = $this->request->data; //the posted data will come as $data['text']
pr($data); //Debugging - print to see the data - this value will be sent back as html to <div id="ajax_output"></div>
}
Disable cake's security for the ajax_process method, in AppController.php:
public function beforeFilter() {
$this->Security->unlockedActions = array('ajax_process');
}
I haven't tested any of this code but it should give you what you need

WkHtmlToPdf Component, problem with Auth

I'm trying to use the WkHtmlToPdf Component, it seems like a nice tool when facing the problem of generating pdf files.
However - I can't get it to work with the Auth Component. The problem is that I always get the login page generated to pdf. I'm logged, the action is allowed in beforeFilter and it still somehow gets into the way of it.
EDIT:
AppController:
var $components = array('Auth', 'Session');
function beforeFilter()
{
$this->Auth->logoutRedirect = array('controller' => 'users', 'action' => 'login');
if (!$this->Auth->user())
{
$this->layout = 'login';
}
}
Controller:
var $components = array('WkHtmlToPdf');
function beforeFilter() // I am logged in, so this shouldn't even be needed
{
$this->Auth->allow('pdf');
}
function pdf()
{
$this->WkHtmlToPdf->createPdf();
}
// this function is required for wkhtmltopdf to retrieve
// the viewdump once it's rendered
function getViewDump($fileName)
{
$this->WkHtmlToPdf->getViewDump($fileName);
}
Any help would be greatly appreciated,
Paul
It turns out, you have to allow the getViewDump method. It doesn't seem to work with Auth, but there is no threat with allowing it for everyone, and it works.
controller:
function beforeFilter()
{
parent::beforeFilter();
$this->Auth->allow('getViewDump');
}

Resources