CakePHP RESTful API routing with custom querystring params - rest

I'm using CakePHP 2.x for a RESTful API, I want to be able to handle requests in the following form
api/activity/17?page=1&limit=10
Typically CakePHP I think likes each param to be separated by the forward slash char and then each of these is mapped into the variables defined in the 2nd array argument of router::connect above. For exampple:
api/activity/17/1/10
In my case though this won't work so I am trying to pass a custom query string which I will then decode in my controller. My router connect is as follows:
So I am using router::connect as follow:
Router::connect('/api/activity/:queryString', [
'controller' => 'users',
'action' => 'activity',
'[method]' => 'GET',
'ext' => 'json',
],
[
'queryString' => '[0-9]+[\?]...[not complete]'
]);
I can't get the regular expression to accept the '?' which I am exscaping in the regex above. How can I achieve this or otherwise is there a better or easier way of sending the URL in the format I require.

You can get the URL parameters (among other methods) via $this->request->query;
So, in your example, add the following in method view() of app/Model/Activity.php:
<?php
// file app/Model/Activity.php
public function view($id)
{
// URL is /activity/17?page=1&limit=10
echo $this->request->query['page']; // echo's: 1
echo $this->request->query['limit']; // echo's: 10
}
See 'Accessing Querystring parameters' in the CakePHP book

Related

CakePHP route redirect with parameters

I need to keep SEO links active so I'm trying to 301 redirect google trafic to new CakePHP route.
I go to:
http://localhost/jakne/someCategory/item-slug
And I want it to 301 redirect to:
http://localhost/product/item-slug
So I tried with route::redirect but I can't make it work. Doc on this is also non existent :(
$routes->redirect(
'/jakne/:subcategory/:item',
['controller' => 'Catalog', 'action' => 'product'],
['status' => 301, 'pass' => ['item']]
);
My Catalog::product looks like:
public function product($productId) {
}
I always get error that no parameter was passed to the action.
What am I missing? :(
The option for retaining parameters in redirect routes isn't pass (that's for regular routes and defines which parameters to pass as function arguments), it's persist, ie your route would need to be something like:
$routes->redirect(
'/jakne/:subcategory/:item',
['controller' => 'Catalog', 'action' => 'product'],
['status' => 301, 'persist' => ['item']]
);
This should work fine, assuming you have a proper target route connected that has a parameter named item, something like.
$routes->connect(
'/product/:item',
['controller' => 'Catalog', 'action' => 'product'],
['pass' => ['item']]
);
Generally you may want to consider doing such redirects on server level instead (for example via mod_rewrite on Apache), performance wise that's much better.
ps. Browsers do cache 301 redirects, so when making changes to such redirects, make sure that you clear the cache afterwards.
See also
Cookbook > Routing > Redirect Routing
So it turns out this is quite simple. I use this to dynamically generate a list of redirects based on what admins enter in the control panel. We use this to keep google traffic when the URL changes and is not rescanned by the google bot yet.
$builder->redirect('/from-url', '/to-url', ['status' => 301]);
Try this ways it is working for me:
Example request like: localhost:08080/get-username?id=%3Cid%3E
Routes :
$routes->connect('/get-username', ['controller' => 'Users', 'action' => 'getUserName']);
Controller :
class UsersController extends AppController {
public function initialize() {
parent::initialize();
$this->loadComponent('RequestHandler');
}
public function beforeFilter(Event $event) {
parent::beforeFilter($event);
$this->set('_serialize', false);
$this->Auth->allow([
'getUserName'
]);
}
public function getUserName() {
$id = $this->request->getQuery('id');
}
}

CSRF field is missing when I embed my form with a requestAction in CakePHP 3

I want to embed a contact form in multiple places on my website.
I developed a contact form in a contact() function within my MessagesController.php:
// MessagesController.php
public function contact()
{
$this->set('title', 'Contact');
$message = $this->Messages->newEntity();
... // shortened for brevity
$this->set(compact('message'));
$this->set('_serialize', ['message']);
}
I loaded the CSRF component in the initialize() function of the AppController.php:
// AppController.php
public function initialize()
{
parent::initialize();
$this->loadComponent('Csrf');
... // shortened for brevity
}
The form is rendered with a contact.ctp and it works fine.
I followed CakePHP's cookbook which suggests using requestAction() within an element, then echoing the element where I want it:
// contact_form.ctp
<?php
echo $this->requestAction(
['controller' => 'Messages', 'action' => 'contact']
);
?>
And:
// home.ctp
<?= $this->element('contact_form'); ?>
The problem is that the form is rendered fine, but the CSRF hidden field is missing. It should be automatically added to the form since the CSRF component is called in the AppController.php.
I guess either using an element with a requestAction() isn't the solution for this particular case, or I am doing something wrong.
Any ideas? Thanks in advance for the input!
Request parameters need to be passed manually
requestAction() uses a new \Cake\Network\Request instance, and it doesn't pass the _Token and _csrf parameters to it, so that's why things break.
While you could pass them yourself via the $extra argument, like
$this->requestAction(
['controller' => 'Messages', 'action' => 'contact'],
[
'_Token' => $this->request->param('_Token'),
'_csrf' => $this->request->param('_csrf')
]
);
Use a cell instead
I would suggest using a cell instead, which is way more lightweight than requesting an action, also it operates in the current request and thus will work with the CSRF component out of the box.
You'd pretty much just need to copy your controller action code (as far as the code is concerned that you are showing), and add a loadModel() call to load the Messages table, something like
src/View/Cell/ContactFormCell.php
namespace App\View\Cell;
use Cake\View\Cell;
class ContactFormCell extends Cell
{
public function display()
{
$this->loadModel('Messages');
$this->set('title', 'Contact');
$message = $this->Messages->newEntity();
// ... shortened for brevity
$this->set(compact('message'));
$this->set('_serialize', ['message']);
}
}
Create the form in the corresponding cell template
src/Template/Cell/ContactForm/display.ctp
<?php
echo $this->Form->create(
/* ... */,
// The URL needs to be set explicitly, as the form is being
// created in the context of the current request
['url' => ['controller' => 'Messages', 'action' => 'contact']]
);
// ...
And then wherever you want to place the form, just use <?= $this->cell('ContactForm') ?>.
See also
API > \Cake\Routing\RequestActionTrait::requestAction()
Cookbook > Views > Cells

Zend Framework 2 - Including a variable in a form action

My login form may be called with a re-direct query and I am wondering if there is a simple way to include this in the subsequent post action.
The use case is for SSO login.
My normal login route is:
/customer/login
and when called from a third party client becomes:
/customer/login?redirectTo=http://www.example.com
My login action:
public function loginAction()
{
$prg = $this->prg();
if ($prg instanceof Response) {
return $prg;
} elseif ($prg === false) {
return new ViewModel(['form' => $this->loginForm]);
}
This loads my view and I currently define my action as so:
$form = $this->form;
$form->setAttribute('action', $this->url());
Now when the action is called, I am losing the redirectTo parameter.
So my question is this, is it possible to update the action to include the re-direct url so that when a user clicks to login, it is posted back to my form?
thanks!
EDIT -
Obviously I can create a redirectTo route in the configs and test on the initial call to the page for the existence of such a route and include this in the form. My question however is whether or not this can be done automagically simply from the viewscript.
To generate query string arguments from the view helper, you need to assign them as the third argument using the query key. Please refer to the ZF2 docs http://framework.zend.com/manual/current/en/modules/zend.view.helpers.url.html
$form->setAttribute('action', $this->url('application', array('action' => 'login'), array('query' => array('redirectTo' => 'http://www.example.com,))));
$form->setAttribute('action', $this->url('login', [
'query' => [
'redirectTo' => $this->params()->fromQuery('redirectTo')
]
]);
Where 'login' is the name of the login route.
See Url View Helper
Well my solution is not as elegant as I hoped it would be. I wanted to avoid using the controller for the query params. As #Stanimir pointed out, the view helpers are in fact, to help with view so my original idea was unfounded.
This is an end to end of what I have put together:
Controller:
$redirect_url = $this->params()->fromQuery('redirectTo',null);
Returns this to view on initial load:
return new ViewModel( ['form' => $this->loginForm , 'redirect_url' => $redirect_url] );
View
$form->setAttribute(
'action',
$this->url(
'customer/login', [] ,
[ 'query'=>
[ 'redirectTo' => $this->redirect_url ]
]
)
);

Get tweet by ID with Twitter::Net

I have a most simple task: I try to read programmatically a tweet given its ID. For the access to the Twitter API, I use Perl's Twitter::Net API .
In lack of a clear documentation of which methods Twitter::Net provides (the docu is very verbose on the search method, as if that would be the only method of interest, but it doesn't even provide a list of all supported methods), I had to work with trial and error.
Twitter's REST API doc says:
GET statuses/show/:id - returns a single Tweet, specified by the id
parameter. The Tweet's author will also be embedded within the tweet.
I create a Twitter::Net instance, using my credentials and the REST 1.1 trait,
my $nt = Net::Twitter->new(
traits => [ qw/API::RESTv1_1/ ],
consumer_key => '...',
consumer_secret => '...',
access_token => '...',
access_token_secret => '...',
ssl => 1
);
Now I tried
my $t = $nt->show( <tweet_id> );
with no success:
Tweets11.pm: Can't locate object method "show" via package "Net::Twitter_v4_01002_with__API_RESTv1_1__OAuth" at Tweets11.pm line 25.
Similar message with statuses instead of show.
How to afford this very simple task with Perl's Twitter::Net?
Per the docs for Twitter::Net, the method you want is actually show_status:
show_status
show_status(id)
Parameters: id, trim_user, include_entities, include_my_retweet
Required: id
Returns a single status, specified by the id parameter. The status's author will be returned inline.
Returns: Status

Zend Framework paginator force $_GET params usage

Is it possible to use paginator with $_GET parameters?
For example i have a route like this:
$router->addRoute('ajax_gallery',
new Routes_Categories(
'/:lang/:category/age/:dep/:cat/:towns',
array(
"page" => 1,
"dep" => 0,
"cat" => 0,
"towns" => 0
),
array(
"dep" => "[0-9]+",
"cat" => "[0-9]+"
)
));
And i'm making request like this via ajax:
http://localhost/en/gallery?dep=9&cat=27&towns=1
But links that returned from results are without ?dep=9&cat=27&towns=1
How to force zend paginator to use passed $_GET params inside pagination link generation?
So that returned links were:
http://localhost/en/gallery/2?dep=9&cat=27&towns=1
http://localhost/en/gallery/3?dep=9&cat=27&towns=1
http://localhost/en/gallery/4?dep=9&cat=27&towns=1
etc...
or even
http://localhost/en/gallery/2/9/27/1
http://localhost/en/gallery/3/9/27/1
http://localhost/en/gallery/4/9/27/1
like they are defined inside route
etc...
Thanks
The view URL helper will always output params as part of the URL (separated by forward slashes) and doesn't, to my knowledge, support the GET parameter format.
I don't know what Routes_Categories class does, but working from the default ZF route classes try this:
$route = new Zend_Controller_Router_Route(
'/:lang/:category/:age/:dep/:cat/:towns/*',
array(
"dep" => 0,
"cat" => 0,
"towns" => 0
),
array(
"dep" => "[0-9]+",
"cat" => "[0-9]+"
)
);
$router->addRoute('ajax_gallery', $route);
The * supports any additional named params after your route. The above assumes lang, category and age are required, and dep, cat and towns are optional. Bear in mind if you want to set cat you have to set dep otherwise the route will get confused which variable is what.
In your controller access the page param via the following, which sets a default of 1.
$page = $this->_getParam('page', 1);
Access the URL via AJAX as: http://localhost/en/gallery/2/9/27/1
If you want the page param, use a named parameter: http://localhost/en/gallery/2/9/27/1/page/2
To get this route to work in your pagination you need to update your paginator view controls to use the right route. See: http://framework.zend.com/manual/en/zend.paginator.usage.html#zend.paginator.usage.rendering.example-controls
Look for the code where the URL is outputted and add the route name to the URL view helper. So replace code like this:
<?php echo $this->url(array('page' => $this->previous)); ?>
With:
<?php echo $this->url(array('page' => $this->previous), 'ajax_gallery'); ?>