I'm using ZfcUser with ht-user-registration and scn-social-auth and there are 2 fairly minor enhancements I want to achieve to extend my implementation, but I'm not having much success so far:
ht-user-registration sends new users an email after registering and denies them access until they've activated their account, this is working fine. What I'd like to do to extend this is redirect the user after registration so that they are sent to a page telling them to check their e-mail rather than to the login page, which is the default behaviour of ZfcUser. I have tried adding an event listener to the module bootstrap that looks like this:
$response = $e->getResponse();
// indicate that we intend to redirect after register action
// set the redirection location to the home page
$response->getHeaders()->addHeaderLine('Location', 'home');
$response->setStatusCode(302);
$response->sendHeaders();
$em = \Zend\EventManager\StaticEventManager::getInstance();
$em->attach('ZfcUser\Service\User', 'register', function($event) use ($response) {
// don't allow anything else to process this event
$event->stopPropagation();
// return the redirect response
return $response;
});
This is called correctly but the redirect never happens and the user still ends up at the login page. Is there something else I need to do to execute the redirect? Or maybe there's a better way entirely to achieve this?
I'd like to add layout variables so that I can modify page titles and navigation in my layout template for the ZfcUser pages. In order to do this I made an override of the UserController from ZfcUser like this :
class UserController extends \ZfcUser\Controller\UserController
{
public function loginAction()
{
$this->layout()->setVariables(array(
'view_title' => 'Reports Login',
));
return parent::loginAction();
}
}
And then overrode the invokable for zfcuser in the config like this:
'controllers' => array(
'invokables' => array(
'Application\Controller\Index' => 'Application\Controller\IndexController',
'zfcuser' => 'Application\Controller\UserController',
),
),
The framework tries to instantiate my UserController at the right point but fails with an InvalidArgumentException: 'You must supply a callable redirectCallback' which I can see is required to construct the base controller but doesn't get passed to my overridden version - any clues why not? I can't find a working example of this to help.
Maybe there's a much easier way to inject layout variables into another module's controller actions?
[EDIT] I have found a simple but not very elegant way of doing this. Since my module overrides the views for both login and registration then it's possible to set layout variables within the view, thus I was able to add a one liner to the top of each view to set the layout variable, e.g.:
<?php $this->layout()->setVariable('view_title', 'Register for an account'); ?>
This doesn't feel correct, but works. If there's a better solution I'd like to know.
Related
I am doing an app that requires authentication. In the index page of the app, I specified access rules like this
public function accessRules() {
return array(
array('deny',
'actions'=>array('index','register','login','password'),
'users'=>array('#'),
),
array('allow',
'users'=>array('*')
),
);
}
At the first rule, the actions 'index','register','login' and 'password' are made unaccessible to authenticated users. However, I do not want to show this message
Unauthorized
You are not authorized to perform this action.
You do not have the proper credential to access this page.
If you think this is a server error, please contact the webmaster.
...to the authenticated users when they try to access those actions. Instead, I want to redirect them to another page. It would be useful if i could do something like this at the first rule
array('redirect',
'actions'=>array('index','register','login','password'),
'users'=>array('#'),
'url'=>array('home/index'),
),
Since Yii v1.1.11 you can do the same thing with a callback and a closure, and just the default classes:
array('deny',
'actions'=>array('index','register','login','password'),
'users'=>array('#'),
'deniedCallback' => function() { Yii::app()->controller->redirect(array ('/home/index')); }
),
They 'll make you an offer you can't refuse
Starting with Yii v1.1.11 CAccessRule defines the deniedCallback property that easily allows you to define a redirect when access is denied. I don't want to steal Iain Gray's thunder, so go upvote his answer (thanks to the commenter who alerted me to this as well).
The original answer follows.
Option 1: extend Yii to enable this functionality (correct)
To do this we will need to write our own classes to be used instead of CAccessRule and CAccessControlFilter. For CAccessRule we just need to add one additional property:
class MyAccessRule extends CAccessRule {
public $redirect; // just add this property
}
For CAccessControlFilter we want to make it recognize the value of this property and act upon it. To do this, we need to override the preFilter method. Starting from the stock implementation, make a few changes:
class MyAccessControlFilter extends CAccessControlFilter {
protected function preFilter($filterChain)
{
$app=Yii::app();
$request=$app->getRequest();
$user=$app->getUser();
$verb=$request->getRequestType();
$ip=$request->getUserHostAddress();
foreach($this->getRules() as $rule)
{
if(($allow=$rule->isUserAllowed($user,
$filterChain->controller,
$filterChain->action,
$ip,
$verb))>0) // allowed
break;
else if($allow<0) // denied
{
// CODE CHANGED HERE
$request->redirect($app->createUrl($rule->redirect));
return false;
}
}
return true;
}
}
Then we also need to override the setRules method, to instruct the filter to use the MyAccessRule class instead of the standard CAccessRule. Again, we modify the stock implementation by changing the line
$r=new CAccessRule;
to read
$r=new MyAccessRule;
After creating these classes, we have to also inject them into Yii's pipeline. To do this, override filterAccessControl on the base controller class; again, taking the stock implementation as reference and making a small change:
public function filterAccessControl($filterChain)
{
$filter=new MyAccessControlFilter; // CHANGED THIS
$filter->setRules($this->accessRules());
$filter->filter($filterChain);
}
That's it! You can now take advantage of the extra functionality in any controller by supplying the new redirect parameter to the access control filters like this:
public function accessRules() {
return array(
array('deny',
'actions'=>array('index','register','login','password'),
'users'=>array('#'),
'redirect'=>array('home/index'),
),
);
}
Option 2: implement access control inside each action (to be avoided)
If you are not comfortable with subclassing Yii's core components, another option that I do not recommend is to embed both access control and redirection logic inside each controller action that you want to protect, or overriding the beforeAction method on your controllers to cover multiple actions from one location.
this one worked for me since yii 1.1.11:
array('deny', // deny all users
'users'=>array('*'),
'deniedCallback' => $this->redirect('/')
),
or use a static method in a Class:
'deniedCallback' => array('ClassName', 'staticMethodName'),
$request->redirect($app->createUrl($rule->redirect));
Should be:
if(is_array($rule->redirect) && isset ($rule->redirect[0])){
$request->redirect($app->createUrl($rule->redirect[0]));
}
I have started to learn Zend Framework. I have done the quickstart tutorial and the akrabat tutorial.
Now I am trying to work on some of my own project with Zend Framework and it is a frustrating experience to say the least.
I made a layout that creates my standard header that shows up on every page. Now I want to display the user's name in the header which is retrieved from a mysql database. I don't know how to go about it. Do I interact with a controller and model from the layout??
Also, are there any other good guides for this kind of information? The zend documentation seems to be very detailed for each component but not very good at explaining how things work together.
Within your layout you will need this
<?= $this->layout()->content ?>
This will output anything from your view script.
In your project structure you need a controller and a view script for that controller. You should know how to access this if you have done the tutorial.
Your controller should get the username from the DB and assign it to the view like so
$this->view->username = "John";
Then in your view
echo $this->username;
EDIT
In your Bootstrap class register a new plugin
$plugin = new Default_Controller_Plugin_Username();
Zend_Controller_Front::getInstance()->registerPlugin($plugin);
This plugin might look like this
class Default_Controller_Plugin_Username
extends Zend_Controller_Plugin_Abstract
implements Lib_Observer_Observable
{
public function preDispatch(Zend_Controller_Request_Abstract $request)
{
//Do things in here to get username
//Then you can set it in the registry
Zend_Registry::set("username", $username);
}
}
If you have the $user from some kind of auth/login sequence, then you might be using Zend_Auth. The default container for the identity data is session-based, so depending upon your session-handling settings, you might not need to hit the db each time to get that user info.
The bigger question to me is whether you need a front controller plugin to place this $user info from Zend_Auth into the view (to be rendered in your layout) or whether the layout can pull from Zend_Auth directly.
I've seen (and used) both.
Your problem is the problem of the block composition on the page. As you may know Zend Framework is only a Framework, so there's really more than one way to deal with this problem.
To fill the layout part you could for example use :
some variable containing the block and given to the view for each controller (with a controller plugin? a main Controller that all controllers inherits?
the Action Helper in the view to chain an internal call to another MVC call in this part of the layout
the Action stack
certainly others things
I think the nicest one is the Action stack. The way it work is:
You make a main query on the MVC-thing (where you get yout fooAction called in a controller)
You call the Action stack in this Action to tell ZF that some others internal calls on some other Action should be done. Here you can list all blocks that should be filled in the Layout
Others bloacks are called by the internal MVC loop. Some other barAction userAction entry points are called, in these actions you tell the Layout which key of the layout is filled by the resulting view rendering with setResponseSegment.
Here's how it looks in code:
For the block action (here a /modulefoo/titi/pilili internal request), that should fill the 'foobar' block of the Layout :
public function pilipiliAction() {
(...) // do things
$this->_helper->viewRenderer->setResponseSegment('foobar');
}
On the layout you echo this block with:
<?= $this->foobar ?>
Now this action must be called by your main action (with the Action Stack helper). But the nicest way will be to use a new custom helper that will stack all the blocks actions.
Here 's a fooAction on a controller, your classical entry point. At the end of the action an helper is called that should build the blocks of the layouts
public function fooAction() {
(...) // do things
// Build Main layout
$this->_helper->LayoutBuilder();
}
Here's an example of what this helper would do (should work, it's a simplified version of an existing one which makes a lot of others things, like managing cache for blocks):
class My_Action_Helper_LayoutBuilder extends Zend_Controller_Action_Helper_Abstract
{
/**
* #var $actionStack
* local ActionStack builtin helper used to Stack several MVC actions,
* for action for each Layout block
*/
protected static $actionStack;
/**
* #var _layout
* reference to the general layout collecting view
*/
protected $_layout;
public function __construct() {
self::$actionStack = Zend_Controller_Action_HelperBroker::getStaticHelper('actionStack');
$this->_layout = Zend_Layout::getMvcInstance();
}
public function direct($blocklist = null) {
$layoutblocks = array(
array('module' => 'default',
'controller' => 'index',
'action' => 'nav')
,
array('module' => 'modulefoo',
'controller' => 'titi',
'action' => 'pilipili') // action given upper
);
$auth = Zend_Auth::getInstance();
if ($auth->hasIdentity()) {
$request = Zend_Registry::get('request');
$module = $request->getModuleName();
$layoutblocks[] = array('module' => $module,
'controller' => 'index',
'action' => 'modulenav');
}
if (isset($blocklist))
{ //user had his own blocklist to add
$layoutblocks = array_merge($layoutblocks,$blocklist);
}
$this->buildCompositeLayout($layoutblocks);
}
public function buildCompositeLayout($blocklist = null) {
foreach($blocklist as $MVCBlock) {
$block = $MVCBlock['action'];
if (!isset($MVCBlock['module'])) {
$module = $this->getRequest()->getModuleName();
$front = $this->getFrontController();
$module = $front->getDispatcher()->getDefaultModule();
} else {
$module = $MVCBlock['module'];
}
if (!isset($MVCBlock['controller'])) {
if (!isset($front)) {
$front = $this->getFrontController();
}
$controller = $front->getDispatcher()->getDefaultController();
} else {
$controller = $MVCBlock['controller'];
}
// Here we stack actions
self::$actionStack->actionToStack($block,
$controller,
$module,
);
}
}
But as I said before, there's more than one way to do it. I like this way as I can interactively add new blocks for some requests, add some caching in the block rendering, and I can as well 'forget' to call the LayoutBuilder helper for some actions, like the ajax ones. The secret is that the MVC loop of Zend Framework is really a loop; it can run several actions for one request.
Zend_Auth rocks.. you need to use it.
There's a good tutorial on Zend_Auth here:
http://akrabat.com/zend-auth-tutorial/
This includes the process of creating a View Helper to do the repetitive task of displaying the Username, role and a logout link.
Take the time to come to grips with View Helpers.. they really explode your productivity and code reuse when coding views.
Duncan.
#Mike, this answer from #jakenoble is exactly correct for your scenario.
If you want to perform some global actions in your controllers then i suggest to create your own base controller class inherited from Zend_Controller say MyBaseController.
Then you can inherit all your controllers from MyBaseController, this way all the actions performed in init() method of MyBaseController will global to your complete project.
i am new to zend and i could not find a way to implement Ajax in zend.
In general php, its pretty easy to make an ajax request and show the response in the desired part of our page. But coming to zend, i have no clue how to do it.
Suppose in my index controller's index.phtml file, i have a button, and when i click on it, i have to make an ajax request to a particular controller and action, and load the related controller action's view in my page.
But what i could not understand is how to specify the urls for the ajax request and how the routing works.
Currently, i made ajax request to load the view statically like this:
xmlhttp.open("GET","../application/views/scripts/register/register.phtml",true);
FYI, i am using regex routing in my application, so would it be better to use curl to route the requests?
First things first, you don't request the view directly. You need to request the specific controller action, eg
/register/register
Zend comes with a great action helper called AjaxContext. This helper lets you respond with a different view based on the type of request (XmlHttpRequest) and a format parameter, disabling any layouts normally present.
To set it up, place something like this in your controller's init() method
public function init()
{
$this->_helper->ajaxContext->addActionContext('register', 'html')
->initContext();
}
Then, add a view script with the ajax suffix, eg register/register.ajax.phtml.
Construct your AJAX GET request to include the format=html parameter, eg
xmlhttp.open('GET', '/register/register/format/html', true);
or
xmlhttp.open('GET', '/register/register?format=html', true);
What will be returned is the rendered contents of register.ajax.phtml, without any layouts.
Apart from what the other answers stated, there's also a URL view helper function that can be useful to call a specific action on a controller. So you could just use $this->url(array('controller' => 'your_controller', 'action' => 'your_action'), 'default', true); to get the link to "your_action" action on the "your_controller" controller (using the default route). You could also specify a specific route instead of 'default' if you have one defined.
So for your example you would use something like the following in your view phtml file (if you're using the default routing) :
xmlhttp.open("GET","<?php echo $this->url(array('controller' => 'register', 'action' => 'register'), 'default', true);?>");
For more information refer to Zend Framework - View Helpers.
You should never be requesting the view directly. That's just wrong. Request URI like this:
xmlhttp.open("GET","/register/register");
Which means "I am looking for default module, register controller and register action", or in other words RegisterController::registerAction().
It's the same as:
xmlhttp.open("GET","/default/register/register");
Which is the same, the default module can be omitted.
Zend Framework knows where to look for the view script (unless you are using some unusual directory structure).
You can just create a blank layout and use it for your ajax controller actions (or what Phil suggested, AjaxContent is probably better for this).
I'll write here an almost complete "how to" guide to implement AJAX calls in Zendframework 3.
We need:
A route declaration
A controller
Some javaScript (is out of skope, perhaps I'll show it in other place)
The route declaration:
<?php
// editing a module.config.php in your project
// typical lines:
namespace Ajax2l; // my ajax module name
use Zend\Router\Http\Segment;
// ...
'router' => [
// ... other routes
'ajax2lsajaxcall' => [
'type' => Segment::class,
'options' => [
'route' => '/ajax2l/ajaxrequest[/:action][/:garbage]',
// the :action wildcard is a place to have extra parameters
// the :garbage wildcard is a place to send random strings
// to ensure you break cache to get real process for
// all your calls, since every call will be different
'defaults' => [
'controller' => Controller\AjaxController::class,
'action' => 'ajaxrequest'
]
],
'may_terminate' => true,
'child_routes' => [
// other ajax routes if needed
]
],
// I only use one ajax route and one controller for all my sites' ajax
// calls because they fire small db actions and reflect them on small
// parts of my pages. So I think I do not need a route and a controller
// for each ajax call. Instead, I use a Library and some services to get
// sets of elementary components and to perform tasks.
The Controller
My Ajax2l module is located under "myzend_project/module" directory.
<?php
// Filename: /module/Ajax2l/src/Controller/AjaxController.php
namespace Ajax2l\Controller
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
// No more components are needed
class AjaxController extends AbstractActionController {
public function ajaxrequestAction(){
$request = $this->getRequest();
$content = $request->getContent();
$isHttpRequest = $this->ifIsValidHttpRequest();
// if you have a real ajax call you must disable the normal output
$viewmodel = new ViewModel();
$viewmodel->setTerminal($is_xmlhttprequest);
// If not an Ajax call (perhaps from untrusted eyes) serve a forgiven
// page message
if(!$isHttpRequest){
return $this->forgivenPage();
}
// Now prepare a response and responds
$requestParams = $this->preProcessRequest($content);
// call the logics to process your params
$output = $this->processRequestParams($requestParams);
// and send a response
$response = $this->getResponse();
$response->setContent($output);
return $response;
}
private function ifIsValidHttpRequest($content){
// I use a small token string to verify if posted data matches
// perhaps not the proper way but is fast and works
$token = 'my_secure_visitor_session_token_identifier';
return (strpos($content, $token) > 2) ? 1 : 0;
// the validation is simplified here it has some more
// complex logics. To ensure validation of requesting source
}
private function preProcessRequest($content){
// The logics to know what to do
// ...
// here you can identify and set properly the post params
}
function processRequestParams($requestParams){
// some logics to process your data
// ...
// some logics to create a Json output
// ...
return $jsonObject;
}
protected function forgivenPage(){
$view = new ViewModel([]);
// set a forgiven message page as output
$view->setTemplate('ajax2l/messages/forgiven.phtml');
// note: the views directory uses lowercase for module names
return $view;
}
}
Hope it helps. I lost a lot of time reading on zend ajax without results. So I hope I was the last losing his time on this topic.
Luis
I implemented security according to this tutorial:
http://book.cakephp.org/view/1543/Simple-Acl-controlled-Application
What I want it to do is if a user issues a request and isn't logged in, they are presented with the login page and then redirected back to their original request.
I think I need to add code in app_controller.php (the top level controller) to save the initial request as maybe a session variable, and then add a line at the end of this function in the users controller to redirect to the saved value:
function login() {
if ($this->Session->read('Auth.User')) {
$this->Session->setFlash('You are logged in!');
// redirect to referrer here
}
}
Am I on the right track here?
you could do a quick search... Take user back to previous page after logging in?
So from dogmatic's linked thread, it looks like all I needed to do is replace this line from the tutorial:
$this->Auth->loginRedirect = array('controller' => 'alerts', 'action' => 'index');
with this:
$this->Auth->loginRedirect = array('controller' => 'alerts', 'action' => 'home');
I presume you've spent enough time with CakePHP to do steps below. Here is the solution;
Add the Auth and Session components to AppController's components (if you
haven't done). From now on all of your controllers able to use of
Auth and Session functions.
Override the beforeFilter() function of the UsersController (or similar controller
to manage user actions) with that one-line-of-code;
$this->Auth->loginRedirect = $this->Session->read("Auth.loginRedirect");This
code should be placed into function since PHP doesn't support function calls into
variable assingment.
After that, to prevent mistaken redirection to already redirected pages, also add that line to the
UsersController's beforeFilter() function;
$this->Session->write('Auth.loginRedirect', "/");
The above code is not required if you sure that done step 4 for every controller.
Override the beforeFilter() function of
the controller that you wanted to get back there after login with
that one-line-of-code;
$this->Session->write('Auth.loginRedirect', Router::url(null,
true));.What this code does is simply writing the fullbase of
controller/action[/param1...] URL (be careful with the parameters
btw) to the Session with Auth.loginRedirect name.
PS: Read jocull's comment to find out why I didn't use the $this->here.
i'm trying to work out how i can stop zend or redirect zend to go to a different zend controller and action if a check within the boot strap fails.
for example a get variable does not exist or more likely a session does not exist meaning the user must log in.
so the user had originally requested index/someaction
but i want them to go to login/index
within my boot strap i would place the condition and then change the controller action to view.
if i'm doing this in a way that is not standard can anyone direct me to documentation on the best practice ?
zend novice
From Zend documentation (Dispatcher)
// forward to an action in another controller:
// FooController::bazAction(),
// in the current module:
$this->_forward('baz', 'foo', null, array('baz' => 'bogus'));
I'd sugest you to do with plugins for access check on each page and for login create an authentication controller.
Here you'll find out how to do this http://alex-tech-adventures.com/development/zend-framework/61-zendauth-and-zendform.html
An example:
class Plugin_AccessCheck extends Zend_Controller_Plugin_Abstract
{
public function preDispatch(Zend_Controller_Request_Abstract $request)
{
// ...
if(!$auth->hasIdentity())
{
$request->setControllerName('authentication')
->setActionName('login');
}
}
}
I don't usually put authentication in the bootstrap, that should have it's own controller.
Create an AuthController() to set up your auth adapter and and set up your instance.
Then in a common view (for secure pages), just check your instance with something like:
$auth = Zend_Auth::getInstance();
if(!$auth->hasIdentity())
{
#re-direct to login page
}