I have been using a fos user Bundle where each of user is assigned to a group and each group is provided with a specific role as per fos user bundle everything works fine.
Now to make a menu system I am trying to use Knp menu bundle.Now to make menu structure I want to pass roles of each group to menu system(dynamically).So that changing role of specific group can allow the menu system to change dynamically.
I have already configured menu bundle as per documentation
knp menu bundle documentation
here I have added a class named menu builder inside namespace Admin\Bundle\HomeBundle\Menu; Now I need to call group roles of current logged in user and add them to menu dynamically also i need to make some of these roles to sub menu within same main menu.
Please improve me if I am on wrong way (if any) and process how I can dynamically include roles of group to menu using Knp menu bundle as a sevice.
Thanks in advance.
I find out another solution in Symfony 3. You can get security.authorization_checker from the container and use it in menu
namespace AppBundle\Menu;
use Knp\Menu\FactoryInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
class Builder implements ContainerAwareInterface
{
use ContainerAwareTrait;
public function topHeaderMenu(FactoryInterface $factory, array $options)
{
$checker = $this->container->get('security.authorization_checker');
$menu = $factory->createItem('root');
$menu->setChildrenAttribute('class','links-top list-inline');
if ($checker->isGranted('ROLE_ADMIN')) {
$menu->addChild('admin panel', array('route' => 'admin'));
}
if ($checker->isGranted('ROLE_USER')) {
$menu->addChild('account', array('route' => 'login'));
$menu->addChild('logout', array('route' => 'logout'));
} else {
$menu->addChild('registration', array('route' => 'registration'));
$menu->addChild('login', array('route' => 'login'));
}
return $menu;
}
}
In Symfony3 the best and easiest solution for me was in MenuBuilder.php :
use Knp\Menu\FactoryInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
class MenuBuilder implements ContainerAwareInterface
{
use ContainerAwareTrait;
public function __construct(FactoryInterface $factory, AuthorizationCheckerInterface $authorizationChecker)
{
$this->factory = $factory;
$this->checker = $authorizationChecker;
}
public function mainMenu(array $options)
{
$menu = $this->factory->createItem('root');
$menu->addChild('Home', array('route' => 'homepage'));
$menu->addChild('Blog', array('route' => 'blog_homepage'));
$menu['Blog']->addChild('About', array('route' => 'blog_about'));
$menu['Blog']->addChild('Contact Us', array('route' => 'blog_contact'));
if($this->checker->isGranted('ROLE_ADMIN')) {
$menu->addChild('Admin', array('route' => 'sonata_admin_dashboard'));
}
$menu->addChild('User', array('route' => 'fos_user_profile_show'));
$menu['User']->addChild('Register', array('route' => 'fos_user_registration_register'));
$menu['User']->addChild('Change password', array('route' => 'fos_user_change_password'));
$menu['User']->addChild('Log Out', array('route' => 'fos_user_security_logout'));
// ... add more children
return $menu;
}
}
Donf forget to add "#security.authorization_checker" to arguments in your service
Related
Using cakephp 2.3.5.
I use beforeFilter in several controllers to allow certain actions without the need to log in. I've used it successfully for quite some time. However, I've noticed that I can't get beforeFilter to fire if the controller also has the implementedEvents() method.
public function beforeFilter() {
parent::beforeFilter();
$this->Auth->allow('matchWwiProducts');
}
public function implementedEvents() {
return array(
'Controller.Product.delete' => 'deleteSku',
'Controller.Product.price' => 'notifySubscribers',
'Controller.Product.stock' => 'notifySubscribers'
);
}
For the code as displayed above I will be forced to do a login if I call the method www.example.com/products/matchWwiProducts.
When I comment out the implementedEvents() everything works as intended. I've searched around and can't find any references to implementedEvents() creating issues with beforeFilter.
The action matchWwiProducts() is as follows. It works perfectly when I log in. However, I don't want to force a log in for this action to take place.
public function matchWwiProducts() {
// this is an audit function that matches Products sourced by wwi
//
$this->autoRender = false; // no view to be rendered
// retrieve products sourced from wwi from Table::Product
$this->Product->contain();
$wwiProducts = $this->Product->getWwiSkus();
$wwiProductCount = count($wwiProducts);
// retrieve products sourced from wwi from Table:Wwiproduct
$this->loadModel('WwiProduct');
$this->Wwiproduct->contain();
$wwiSource = $this->Wwiproduct->getSkuList();
$wwiSourceCount = count($wwiSource);
// identify SKUs in $wwiProducts that are not in $wwiSource
$invalidSkus = array_diff($wwiProducts, $wwiSource);
// identify SKUs in $wwiSource that are not in $wwiProducts
$missingSkus = array_diff($wwiSource, $wwiProducts);
$missingSkuDetails = array();
foreach ($missingSkus as $missingSku) {
$skuStockStatus = $this->Wwiproduct->getStockStatus($missingSku);
$missingSkuDetails[$missingSku] = $skuStockStatus;
}
$email = new CakeEmail();
$email->config('sourcewwi');
$email->template('sourcewwiaudit', 'sourcewwi');
if (count($invalidSkus) > 0 || count($missingSkus) > 0) {
$email->subject('WWI Source Audit: Invalid or Missing SKUs');
$email->viewVars(array('invalidSkus' => $invalidSkus,
'missingSkuDetails' => $missingSkuDetails,
'wwiProductCount' => $wwiProductCount,
'wwiSourceCount' => $wwiSourceCount));
} else {
$email->subject('WWI Source Audit: No Exceptions');
$email->viewVars(array('wwiProductCount' => $wwiProductCount,
'wwiSourceCount' => $wwiSourceCount));
}
$email->send();
}
It doesn't fire because you're overloading the implementendEvents() method without making sure you keep the existing events there.
public function implementedEvents() {
return array_merge(parent::implementedEvents(), array(
'Controller.Product.delete' => 'deleteSku',
'Controller.Product.price' => 'notifySubscribers',
'Controller.Product.stock' => 'notifySubscribers'
));
}
Overloading in php.
Check most of the base classes, Controller, Table, Behavior, Component, they all fire or listen to events. So be careful when extending certain methods there. Most simple way might to do a search for "EventListenerInterface" in all classes. A class that implements this interface is likely to implement event callbacks.
I am developing a Drupal module. Part of it opens a pop-up window, displays some elements in it, and uses JavaScript to transfer input back to the main page.
As this is a small window, I don't want it to display the full theme borders from the site's theme.
In Drupal 6 I was able to achieve this with the following:
function MyPopupPageHandler() {
#content = "Content...";
//Add additional content to #content;
print theme("page", #content);
}
However, Drupal 7 expects the second parameter of the theme() function to be an array, and none of the examples I've seen show how I set the main content of the page.
What I'd like is a custom page template in the module, that can be overridden by the site theme if it provides one.
I would like to know:
What elements do I need to put in the array I pass to the theme() function?
What should I call my template file?
How do I tell Drupal where to find my template, as it needs to be in the module by default?
Appreciate any help you can offer.
James
Try this
First of all create a menu in .module file
function MYMODULE_menu()
{
$items['Demo'] =array(
'title' => 'Demo Page',
'page callback' => 'demo_page', // call a function
'access arguments' => array('access content'),
);
return $items;
}
After you have create a function
function demo_page()
{
$select = db_select('node', 'n');
$select = $select->fields('n', array('id'))
->extend('PagerDefault');
$queried_nodes = $select->execute()
->fetchAllAssoc('id');
$pager = theme('pager');
return theme('demo_template', array('nodes' => $queried_nodes , 'pager' => $pager)); // call a theme or you have no pass any argument in theme to change a 'nodes'=> NULL or 'pager'=>NULL
}
after i have create a theme function
function MYMODULE_theme()
{
return array(
'demo_template' => array(
'template' => 'demo-page',//this is file name of template file
'variables' => array('nodes' => NULL,'pager' => NULL), //this is pass avarible of templates file
'path' => drupal_get_path('module', 'MYMODULE_NAME').'/template' // set a path of file
),
);
}
after you have create file name like demo-page.tpl.php in sites/all/modules/MYMODULENAME/template/
and clear caches
1) The second parameter of theme() function must be an associative array. Your function should look like :
function MYMODULE_custom_function() {
$content = "Some stuff";
return theme('MYMODULE_custom_output', array('content' => $content));
}
2) I guess you meant "Where should I call my template file?" In the hook_theme() function in your same .module file :
function MYMODULE_theme() {
return array(
'MYMODULE_custom_output' => array(
'variables' => array('content' => array()),
// You can also use template file here : 'template' => 'MYMODULE-template'
// OR use the following theme_function() if you don't want to create a new file
),
);
}
// If you don't use template file
function theme_MYMODULE_custom_output($variables) {
$output = // Arrange your html output here
return $output;
}
3) To tell where to find your custom template file if you decide to use one, you can read this : https://drupal.org/node/715160 and I hope it will help.
Please stay indulgent because I'm still new with Drupal and I did try to do my best here :o)
My site has a public section for employees and back end for admin. It uses 2 different models, Employee and Admin.
I want to use Auth component for employee login and admin login. I know how to setup Auth component to use a Model other than default User model. But can i have auth component use 2 models, one for Employee authentication and other for Admin authentication? I am using admin_ prefix routing.
Is this possible? I searched but all i could found was tutorials on howto make Auth component use models other than User model.
Please advise!
EDIT
I use separate login forms for admin login and employee login. Both use the employee controller, but separate actions.
http://api.cakephp.org/class/auth-component
check the property authenticate, your answer is there!
and more :
http://book.cakephp.org/2.0/en/core-libraries/components/authentication.html.
Look at authentication handlers!
Here is an example directly from cake page
<?php
// Basic setup
$this->Auth->authenticate = array('Form');
// Pass settings in
$this->Auth->authenticate = array(
'Form' => array('userModel' => 'Member'),
'Basic' => array('userModel' => 'Member')
);
Just put something else instead of Form and Basic and associate the good Model
Considering you are using two radio buttons for Employee and Admin. Then you can use the following code into the login method.
function login()
{
if ($this->request->is('post'))
{
$logged_in = false;
$login_type = $this->request->data['User']['login_type']
if ($login_type == 'Admin')
{
$this->Auth->authenticate = array('Form' => array('userModel' => 'Admin' ));
}
else //if ($login_type == 'Employee')
{
$this->Auth->authenticate = array('Form' => array('userModel' => 'Employee' ));
}
$this->Auth->constructAuthenticate();
if ($this->Auth->login())
{
$logged_in = true;
/*.... Do what you want............*/
}
}
}
I'm trying to implement something like Mark Story's "Down for Maintenance" page using CakePHP 2.1.0. I'm pretty close to achieving this, but I'm running into two issues that I could use some help with. First of all, here is all of the relevant code (six files):
1) app/Config/bootstrap.php:
Configure::write('App.maintenance', true);
2) app/Config/core.php:
Configure::write('debug', 1);
...
Configure::write('Exception', array(
'handler' => 'ErrorHandler::handleException',
'renderer' => 'AppExceptionRenderer',
'log' => true
));
3) app/Controller/AppController.php:
if (Configure::read('App.maintenance') == true) {
App::uses('DownForMaintenanceException', 'Error/Exception');
throw new DownForMaintenanceException(null);
}
4) app/Lib/Error/Exception/DownForMaintenanceException.php:
<?php
class DownForMaintenanceException extends CakeException {}
5) app/Lib/Error/AppExceptionRenderer.php:
<?php
App::uses('ExceptionRenderer', 'Error');
class AppExceptionRenderer extends ExceptionRenderer {
function _outputMessage($template) {
// Call the "beforeFilter" method so that the "Page Not Found" page will
// know if the user is logged in or not and, therefore, show the links that
// it is supposed to show.
if (Configure::read('App.maintenance') == false)
{
$this->controller->beforeFilter();
}
parent::_outputMessage($template);
}
public function downForMaintenance() {
$url = $this->controller->request->here();
$code = 403;
$this->controller->response->statusCode($code);
$this->controller->set(array(
'code' => $code,
'url' => h($url),
'isMobile' => $this->controller->RequestHandler->isMobile(),
'logged_in' => false,
'title_for_layout' => 'Down for Maintenance'
));
$this->_outputMessage($this->template);
}
}
6) app/View/Errors/down_for_maintenance.ctp:
<p>Down for Maintenance</p>
Now, for the two issues I'm experiencing. First, this code only works when debug is set higher than 1. Is there anything I can do about that? Does that indicate that I'm going about this the wrong way? The second issue is that, although I'm setting the "isMobile" and "logged_in" view variables to boolean values in the "downForMaintenance" method, the "app/View/Layouts/default.ctp" file is seeing them as strings. What can I do about that?
Thanks!
here is a quick and dirty maintenance page for cakephp
in public index.php
define('MAINTENANCE', 0);
if(MAINTENANCE > 0 && $_SERVER['REMOTE_ADDR'] !='188.YOUR.IP.HERE')
{
require('maintenance.php'); die();
}
Then just change MAINTENANCE = 1 when you want to take your site down and it will still be viewable from your home/office.
BONUS: Works with all versions of cake!
A more elegant way would be to add a route overriding any other one at the very top of routes.php:
//Uncomment to set the site to "under construction"
Router::connect('/*', array('controller' => 'pages', 'action' => 'underConstruction'));
//any other route should be underneath
If you want to add any condition you can also do it here:
define('MAINTENANCE', 0);
if(MAINTENANCE > 0 && $_SERVER['REMOTE_ADDR'] !='188.YOUR.IP.HERE')
Router::connect('/*', array('controller' => 'pages', 'action' => 'underConstruction'));
}
We'll need to create a custom Dispatch Filter,CakePHP has you covered.
check below link
http://josediazgonzalez.com/2013/12/13/simple-application-maintenance-mode/
Background: Building a web app (as an introduction to CakePHP) which allows users to manage a lounge. A lounge is composed of a blog, contacts, calendar, etc. Each lounge is associated with a subdomain (so jcotton.lounger.local would take you to my lounge). The root of the site, used for creating new lounges, registering users, etc is hosted on lounger.local. I am using Cake 2.0.
Questions:
I wanted to be able to separate actions and views associated with the root site (lounger.local) from individual lounges (subdomains of lounger.local). After a good deal of research I settled on the following soln. I setup a prefix route "lounge" and added the the following code in routes.php. Actions (and views) associated with a lounge all contain the prefix lounge (ex: lounge_index()). How would you handle this?
if(preg_match('/^([^.]+)\.lounger\.local$/',env("HTTP_HOST"),$matches)){
$prefix = "lounge";
Router::connect('/', array('controller' => 'loungememberships','action' => 'index', 'prefix' => $prefix, $prefix => true));
/* Not currently using plugins
Router::connect("/:plugin/:controller", array('action' => 'index', 'prefix' => $prefix, $prefix => true));
Router::connect("/:plugin/:controller/:action/*", array('prefix' => $prefix, $prefix => true));
*/
Router::connect("/:controller", array('action' => 'index', 'prefix' => $prefix, $prefix => true));
Router::connect("/:controller/:action/*", array('prefix' => $prefix, $prefix => true));
unset($prefix);
}
Each time a user performs an action within a lounge such as posting a comment within the blog, adding a contact, etc, it is necessary to lookup the lounge_id (based on the subdomain); this is necessary to verify the user is authorized to perform that action and to associate the corresponding data with the correct lounge. I have implemented this via the beforeFilter function in AppController. Each time a request is received with a subdomain a search is performed and the lounge_id is written to a session variable. Each controller then loads CakeSession and reads the corresponding lounge_id. Is this better than calling ClassRegistry::Init('Lounge') and doing the lookup in each controller? Is there a better soln?
Thanks in advance for the help
The way I approached this was with a custom route, and some trickery with route configuration similar to your example.
First, I have a "Master domain" that is redirected to and used as the main domain for the multi-tenancy site. I also store a default action i want them to take. I store these in configuration variables:
Configure::write('Domain.Master', 'mastersite.local');
Configure::write('Domain.DefaultRoute', array('controller' => 'sites', 'action' => 'add'));
Next, I created a DomainRoute route class in /Lib/Route/DomainRoute.php:
<?php
App::uses('CakeRoute', 'Routing/Route');
App::uses('CakeResponse', 'Network');
App::uses('Cause', 'Model');
/**
* Domain Route class will ensure a domain has been setup before allowing
* users to continue on routes for that domain. Instead, it redirects them
* to a default route if the domain name is not in the system, allowing
* creation of accounts, or whatever.
*
* #package default
* #author Graham Weldon (http://grahamweldon.com)
*/
class DomainRoute extends CakeRoute {
/**
* A CakeResponse object
*
* #var CakeResponse
*/
public $response = null;
/**
* Flag for disabling exit() when this route parses a url.
*
* #var boolean
*/
public $stop = true;
/**
* Parses a string url into an array. Parsed urls will result in an automatic
* redirection
*
* #param string $url The url to parse
* #return boolean False on failure
*/
public function parse($url) {
$params = parent::parse($url);
if ($params === false) {
return false;
}
$domain = env('HTTP_HOST');
$masterDomain = Configure::read('Domain.Master');
if ($domain !== $masterDomain) {
$defaultRoute = Configure::read('Domain.DefaultRoute');
$Cause = new Cause();
if (!($Cause->domainExists($domain)) && $params != $defaultRoute) {
if (!$this->response) {
$this->response = new CakeResponse();
}
$status = 307;
$redirect = $defaultRoute;
$this->response->header(array('Location' => Router::url($redirect, true)));
$this->response->statusCode($status);
$this->response->send();
$this->_stop();
}
$params['domain'] = $domain;
}
return $params;
}
/**
* Stop execution of the current script. Wraps exit() making
* testing easier.
*
* #param integer|string $status see http://php.net/exit for values
* #return void
*/
protected function _stop($code = 0) {
if ($this->stop) {
exit($code);
}
}
}
This custom route class is used within the /Config/routes.php file to setup multi-tenancy.
if (env('HTTP_HOST') === Configure::read('Domain.Master')) {
// Master domain shows the home page.
$rootRoute = array('controller' => 'pages', 'action' => 'display', 'home');
} else {
// Subdomains show the cause view page.
$rootRoute = array('controller' => 'causes', 'action' => 'view', env('HTTP_HOST'));
}
Router::connect('/', $rootRoute, array('routeClass' => 'DomainRoute'));
On inspection of the custom router, you will see that I am pulling the current domain being accessed and adding that to the $params array.
While this does not directly achieve what you are after, minor modifications will get you on the right track with your requirements. There is not a great deal of information about Custom Routes, but here is the CakePHP documentation link for custom route classes.
I hope that helps!