CakePHP 3.0 build a permission menu using Cells - cakephp

I would like to create a menu in new CakePHP 3.0 and I found that using cells might be a good way. So let's say I created UserMenuCell
class UserMenuCell extends Cell {
protected $_validCellOptions = [];
public function display() {
$menu = [];
$menu[] = $this ->menu( __('Dashboard'), array( 'controller' => 'Users', 'action' => 'dashboard' ), 'fa-dashboard', [] );
if( $this -> Auth -> isAuthorized(null, ??? ))
$menu[] = $this ->menu( __('Barcodes'), array( 'controller' => 'Barcodes', 'action' => 'index' ), 'fa-table', [] );
$this -> set ( 'menu', $menu );
}
private function menu( $title, $url = [], $icon, $submenu = [] ) {
return ['title' => $title, 'url' => $url, 'icon' => $icon, 'submenu' => $submenu]; }
}
But I want to display Barcodes item only when current user is authorized to manage barcodes. How can I do it? I can't even access $this -> Auth to get current user.
In my cell's template is everything OK. I just need to create this nested array for menu.

According to Cookbook, the session is available from within Cells.
class UsermenuCell extends Cell
{
public function display()
{
var_dump($this->request->session()->read('Auth'));
}
}
Like this you could read the needed informations in your cell display function.

and if you pass the session variable?
<?= $this->cell('userMenu', $this->Session->read('Auth')); ?>

I think the problem is solved:
I can make controller to have static method for example static public function _isAuthorized($user, $request) which would handle the authorization logic (so every controller controls only its own permissions).
And then I can just call from anywhere for example PostsController::_isAuthorized($user, ['action' => 'add']). This should solve all problems I guess.
Also good point is to pass $this -> Auth -> user() into view, so it can be used in Cells (through parameter).
src/Controller/AppController.php
public function beforeFilter(Event $event) {
$this -> set('user', $this -> Auth -> user());
}
src/View/Cell/MenuCell.php
use App\Controller\PostsController; // Don't forget to use namespace of your Controller
class MenuCell extends Cell {
public function display($user) {
$menu = [];
if (PostsController::_isAuthorized($user, ['action' => 'add'])) // In that method you must handle authorization
$menu[] = ['title' => 'Add post', 'url' => array('controller' => 'Posts', 'action' => 'add')];
$this -> set ('menu', $menu); // Handle this in Template/Cell/Menu/display.ctp
}
}
src/Template/Cell/Menu/display.ctp - just to show how to render menu
<ul>
<?php foreach($menu as $item) {
echo '<li>' . $this -> Html -> link ($item['title'], $item['url']);
} ?>
</ul>
src/Template/Layout/default.ctp - render menu in main layout
<?= $this -> cell('Menu', array($user)) /* This is the user passed from beforeFilter */ ?>
Then you can play with isAuthorized methods. For example you can edit your AppController. Always when CakePHP calls isAuthorized function it will be redirected to YourNameController::_isAuthorized() static method (if exists).
src/Controller/AppController.php
public function isAuthorized( $user ) {
$childClass = get_called_class();
if(method_exists($childClass, '_isAuthorized'))
return $childClass::_isAuthorized($user, $this -> request);
return static::_isAuthorized($user, $request);
}
static public function _isAuthorized($user, $request)
{
if ($user['role'] == 'admin')
return true;
return false; // By default deny any unwanted access
}
This is an example of your controller. You can specify only static _isAuthorized($user, $request) method, because for purposes of CakePHP default behavior it will be called from AppController::isAuthorized (see code above).
src/Controller/PostController.php
static public function _isAuthorized($user, $request)
{
$action = ($request instanceof Cake\Network\Request) ? $request -> action : $request['action'];
if($action == 'add' && $user['role'] == 'CanAddPosts')
return true;
return parent::_isAuthorized($user, $request);
}
As you can see I made $request to accept an array or Cake\Network\Request object. That's because CakePHP call it with Request object but when I call it I don't need to create this object, since my parameters are easy (see code above MenuCell.php).
Of course you can now do more complex logic like user can have more roles separated by comma and you can explode this and check if user has permission by in_array.
Now it's really up to you what is your logic behind permissions. Every controller can handle it's own permission managing while you can always access these permissions with every user and every page request.

Related

How to populate select element in zend 1.12 from db

i'm creating a application in which i need to populate data in select element from a db table.
i need to populate user roles from db
my form code is
$this->pass2->addValidator('Identical', false, array('token' => 'pass1'));
$this->addElement('select', 'userrole', array(
'class' => 'form-control',
'required' => true,
'multiOptions' =>
));
what should i do with multi options ?,
is there any way to load data from db in element using controller ,please helpme
thanks
What I have done in the past is to pass the db-adapter (or a model that knows how to do the required db query) to the form as a constructor parameter.
Something like this:
class Application_Form_MyForm extends Zend_Form
{
protected $db;
public function __construct($db)
{
$this->db = $db;
// Don't forget to call the parent __construct. Ultimately
// it is the parent __construct() that calls your init()
// method that adds your elements
parent::__construct();
}
public function init()
{
// Create your form elements
// $this->addElement('text', 'my_text_field'); // etc
// Now your select field...
$this->addElement('select', 'my_select', array(
'multiOptions' => $this->buildMultiOptions(),
'validators' => array(
// blah, blah
),
);
}
protected function buildMultiOptions()
{
$select = $this->db->select()
->from('my_table', array(
'my_value_column',
'my_display_column'
))
->order(array(
'my_display_column ASC',
));
$results = $this->db->query($select)->fetchAll();
$return = array();
foreach ($results as $row) {
$return[$row['my_value_column']] = $row['my_display_column'];
}
return $return;
}
}
Then in the controller action, when you instantiate your form, you grab the db-adapter and pass it in as a constructor parameter:
$db = $this->getInvokeArg('bootstrap')->getResource('db');
$form = new Application_Form_MyForm($db);
// Then process your form as usual
on case with is necessary populate options outside form class.
$form->getElement( 'ele_name' )
->setConfig(new Zend_Config( array(
'multiOptions' => array('option1','option2') )
)));

JSON view not working after implementing authorization in CakePHP

Since I implemented authorization in my cakephp project, it seens I can't access my views with JSON anymore. Do I have to set a specific setting anywhere to make this work again?
I was hoping adding autoComplete to the allowedActions and isAuthorized would do the trick
app\Controller\BrandsController.php (stripped from unnecessary code)
<?php
App::uses('Sanitize', 'Utility');
class BrandsController extends AppController {
public $helpers = array('Html', 'Form', 'Session');
public $components = array('Session', 'RequestHandler');
public function beforeFilter() {
parent::beforeFilter();
$this->Auth->allowedActions = array('autoComplete');
}
public function isAuthorized($user) {
if ($this->action === 'view' || $this->action === 'index' || $this->action === 'autoComplete') {
return true;
}
return parent::isAuthorized($user);
}
public function autoComplete($name = null)
{
if(!$name)
{
$name = #$this->params->query['name'];
}
$name = Sanitize::paranoid($name, array(' '));
if (!$name || empty($name))
{
new NotFoundException(__('Invalid searchquery'));
}
else
{
$objects = $this->Brand->find('list',
array(
'conditions' => array("Brand.name LIKE" => "%" . $name . "%")
)
);
$this->layout = 'ajax';
$this->RequestHandler->setContent('json', 'application/json' );
$this->set(compact('objects'));
$this->render('json/output_json');
}
}
}
?>
app\View\Brands\json\output_json.ctp
<?php
Configure::write('debug', 0);
echo json_encode($objects);
?>
Call
<?php
$brandsAutoCompleteUrl = Router::url(array('controller' => 'brands', 'action' => 'autoComplete'));
?>
<script type="text/javascript">
$(document).ready(function(){
$.getJSON('<?php echo $brandsAutoCompleteUrl; ?>', { name: 'test' }, function(data) {
// never called
});
});
</script>
In my Chrome debug window it sais
GET http://localhost/cakephp/brands/autoComplete?name=tes 500 (Internal Server Error)
When I call this url from my browser directly, I get the expected results
I fixed this problem by replacing
$this->RequestHandler->setContent('json', 'application/json' );
by
$this->response->type(array('json' => 'application/json'));
correct allowing is
$this->Auth->allow('view', 'index');
This can be done only in a controller that is owner of allowed actions -
Link to documentation
Also - in firefox firebug or in chrome you can view response for your 500 HTTP error that is usually an html code with an exact CakePHP error - investigate this first.
Why do you add components and helpers in this controller instead of AppController? This components are common for any controller - it is reasonable to put them to AppController.
And if you haven't noticed - all helpers and components of AppController are merged with helpers and components of child controllers. You can't switch off Auth component by replacing components in child controller.
Recommendation:
Avoid using of '#' for code like #$this->params->query['name'];. Use !empty($this->params->query['name']) in 'if' condition
If you don't know: empty($name) == !$name, also !$name can trigger error in variable $name is not initiated - http://www.php.net/manual/ru/types.comparisons.php (1st table)

CakePHP Audit Log Plugin user authentication

I'm trying to implement the Audit Trail plugin - https://github.com/robwilkerson/CakePHP-Audit-Log-Plugin
It all works great, however i can't get the user authentication working by following the instructions i get the following error -
Fatal error: Call to undefined method CakeErrorController::currentUser()
I have followed the instructions by adding
protected function currentUser() {
$user = $this->Auth->user();
return $user[$this->Auth->userModel]; # Return the complete user array
}
and adding
public function beforeFilter() {
...
if( !empty( $this->data ) && empty( $this->data[$this->Auth->userModel] ) ) {
$this->data[$this->Auth->userModel] = $this->currentUser();
}
}
to my appController, has anyone implemented this before or recognise the error?
For Cakephp 2.4 you have to do some changes in order to work with the Auth component:
In the AppModel:
public function currentUser() {
$userId = AuthComponent::user('id');
//Get the information of the user
$currentUser = $this->importModel('User')->find('first', array(
'conditions'=>array('User.id'=>$userId),
));
//Return all the User
return $currentUser['User'];
}
And now in your AppController:
The true is you don't need to do anything else in your controller, it's just to prevent some problem. So, OPTIONAL:
if( !empty( $this->request->data ) && empty( $this->request->data[$this->Auth->userModel] ) ) {
$user['User']['id'] = $this->Auth->user('id');
$this->request->data[$this->Auth->userModel] = $user;
}
It works for me.
Don't add the currentUser() function to your AppController, it has to be in your AppModel. Here's what my currentUser() function looks like using CakePHP 2.3:
public function currentUser() {
return array('id' => AuthComponent::user('id'));
}

Check for existing controller

I've written static pages component for my application, where admins can dynamically add/edit/remove static content pages. these are saved in the database.
(e.g. you can create a page called "about" and can visit it at myapplication/about)
This is my routing for these pages:
$page = new StaticPage();
$slugs = $page->find('list', array(
'fields' => array('slug'),
'recursive' => -1,
'order' => 'StaticPage.slug DESC',
));
Router::connect('/:slug',
array('controller' => 'static_pages', 'action' => 'display'),
array(
'pass' => array('slug'),
'slug' => implode($slugs, '|')
)
);
Now i have the problem, that when you create a page which slug matches an existing controller (e.g. users), it overwrites the Route to the UsersController.
so i need something like a blacklist or similar: i began to write a validation rule, where i want to check if that controller exists. for cake 1.3 there was a function "loadController" which return false, if the controller did not exist, but for cake 2.x there is no such an function. am i missing this somehow? does it have a new name or is in a utility library now?
Or are there better ways to solve this?
you should try this : http://www.cleverweb.nl/cakephp/list-all-controllers-in-cakephp-2/
and by getting the list of all controllers you can easily exclude the name of controllers
This is my validation method for now:
$route = Router::parse($check['slug']);
$controllerName = Inflector::camelize($route['controller'] . 'Controller');
$aCtrlClasses = App::objects('controller');
foreach ($aCtrlClasses as $controller) {
if ($controller != 'AppController') {
// Load the controller
App::import('Controller', str_replace('Controller', '', $controller));
// Load the ApplicationController (if there is one)
App::import('Controller', 'AppController');
$controllers[] = $controller;
}
}
if (in_array($controllerName, $controllers)) {
return false;
} else {
return true;
}

Symfony 2 : Access database inside FormBuilder

I'm building a form which contains a category field. I need a choice list to do that, but I don't find out how to fill this choice list with the several categories stored in the database.
public function buildForm(FormBuilder $builder, array $options) {
$builder->add('item', 'text', array('label' => 'Item'));
$builder->add('category', 'choice', array(
'choices' => ???,
'label' => 'Category'
));
}
How can I get the categories from the database?
(I can't seem to access $this->getDoctrine() inside this class.)
Use type entity instead of choice
$builder
->add('entity_property', 'entity', array(
'class' => 'Namespace\\To\\Entity',
'query_builder' => function(EntityRepository $repository) {
return $repository->createQueryBuilder('q')
->where('q.a_field = yourvalue');
}
));
Edit:
Two ways for using custom parameters in your query. In both situations, the parameters are injected from outside, so your FormType don't need any references to the session or request objects or whatever.
1- Pass required parameters to your constructor
class TaskType extends AbstractType
{
private $custom_value;
public function __construct($custom_value) {
$this->custom_value = $custom_value;
}
// ...
}
in your buildForm() you must copy the value to local variable and make it available for the query_builder callback:
public function buildForm(/*...*/) {
$my_custom_value = $this->custom_value;
// ...
'query_builder' => function(EntityRepository $repository) use ($my_custom_value) {
return $repository->createQueryBuilder('q')
->where('q.a_field = :my_custom_value')
->setParameter('my_custom_value', $my_custom_value);
}
// ...
}
2- use the $options parameter of the buildForm method.
First you have to define a default value by overriding getDefaultOptions:
public function getDefaultOptions(array $options)
{
return array(
'my_custom_value' => 'defaultvalue'
);
}
Then you can pass it from your controller in the third argument of the createForm method.
$this->createForm(new YourFormType(), $entity, array('my_custom_value' => 'custom_value'));
Now the value should be available through the $options parameter of youru buildForm method. Pass it to the callback as described above.
In Symfony 2.1
You now have to use the OptionsResolverInterface within the setDefaultOptions method. Here is the code you would have to use if you wanted to retrieve the options (using the same example as the accepted answer)
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
public function buildForm(FormBuilderInterface $builder, array $options){
parent::buildForm($builder, $options);
$my_custom_value = $options[custom_value];
// ...
'query_builder' => function(EntityRepository $repository) use ($my_custom_value) {
return $repository->createQueryBuilder('q')
->where('q.a_field = :my_custom_value')
->setParameter('my_custom_value', $my_custom_value);
}
// ...
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'my_custom_value' => 'defaultvalue'
));
}
You still pass the options in the same way:
$this->createForm(new YourFormType(), $entity, array('my_custom_value' => 'custom_value'));

Resources