Execute my code before any action of any controller - yii2

I would like to check if my user have filled certain fields in his profile before he can access any action of any controller.
For example
if(empty(field1) && empty(field2))
{
header("Location:/site/error")
}
In yii1 I could do it in protected\components\Controller.php in init() function
But in yii2 I'm not sure where to put my code. I cannot modify core files, but not sure what to do in backend of my advanced application to make it work.
I know I can user beforeAction() but I have too many controllers to do that and to keep track of every controller

In case you need to execute a code before every controller and action, you can do like below:
1 - Add a component into your components directory, for example(MyGlobalClass):
namespace app\components;
class MyGlobalClass extends \yii\base\Component{
public function init() {
echo "Hi";
parent::init();
}
}
2 - Add MyGlobalClass component into your components array in config file:
'components' => [
'MyGlobalClass'=>[
'class'=>'app\components\MyGlobalClass'
],
//other components
3 - Add MyGlobalClass into bootstarp array in config file:
'bootstrap' => ['log','MyGlobalClass'],
Now, you can see Hi before every action.
Please note that, if you do not need to use Events and Behaviors you can use \yii\base\Object instead of \yii\base\Component

Just add in config file into $config array:
'on beforeAction' => function ($event) {
echo "Hello";
},

Create a new controller
namespace backend\components;
class Controller extends \yii\web\Controller {
public function beforeAction($event)
{
..............
return parent::beforeAction($event);
}
}
All your controllers should now extend backend\components\Controller and not \yii\web\Controller. with this, you should modify every controller. I would go for this solution.
I believe you might also replace 1 class with another (so no change to any controller necessary), something like
\Yii::$classMap = array_merge(\Yii::$classMap,[
'\yii\web\Controller'=>'backend\components\Controller',
]);
See more details here: http://www.yiiframework.com/doc-2.0/guide-tutorial-yii-integration.html and I took the code from here: https://github.com/mithun12000/adminUI/blob/master/src/AdminUiBootstrap.php
you can put this in your index.php file. However, make sure you document this change very well as somebody that will come and try to debug your code will be totally confused by this.

Just i think this code on config file can help you:
'on beforeAction' => function ($event) {
// To log all request information
},
'components' => [
'response' => [
'on beforeSend' => function($event) {
// To log all response information
},
],
];

Or, https://github.com/yiisoft/yii2/blob/master/docs/guide/security-authorization.md use RBAC, to restrict access to controllers actions one at a time based on rules. Why would you want to restrict access to controller actions based on user fields is beyond me. You will not be able to access anything (including the login form) if you put a restriction there.

Related

accessing an array in a component from the layout in yii2

I need to check the records in the notifications table at every page load of every controller.
So I wrote it in a component and the component is executed in the bootstraping process.
I need the notifications to be available in the layout so that i can show them in the notification menu.
below is what I have tried so far:
component:
namespace admin\components;
use Yii;
use yii\base\Component;
use admin\models\Notification;
class NotificationManager extends \yii\base\Component{
public function init() {
$notifications = Notification::find()->orderBy('id DESC')->asArray()->all();
//echo "<pre>"; print_r($notifications);exit;
if(count($notifications)>0){
foreach ($notifications as $notif) {
if($notif['type'] == 'courier')
$courier_notifications[] = $notif;
elseif($notif['type'] == 'order')
$order_notifications[] = $notif;
}
Yii::$app->view->params['courier_notifications'] = $courier_notifications;
Yii::$app->view->params['order_notifications'] = $order_notifications;
}
}
}
Layout:
$courier_notifications = $this->params['courier_notifications'];
I am not sure which part am I going wrong: in component or in the layout?
I appreciate your help.
Im not sure why your component execution during bootstrap fails to add the value to params.But believe it to be an overkill.
You can rather move the logic to component method and access in layout whenever necessary
Component.
namespace admin\components;
use Yii;
use yii\base\Component;
use admin\models\Notification;
class NotificationManager extends Component{
public function notifications($type = 'courier') {
$notifications = Notification::find()
->where(['type' => $type])
->orderBy('id DESC')
->asArray()->all();
return $notifications;
}
}
Add the component class under Components section in your config file
'notificationManager ' => [
'class' => 'admin\components\NotificationManager'
]
Layout
$courier_notifications = yii::$app->notificationManager->notifications('courier');
If you really want to go bootstrap mode, you need to implement yii\base\BootstrapInterface and put your logic in the bootstrap($app) method in order for the param to be available site-wide by setting the value of Yii::$app->params['notifications'] to the result of your logic.
Another common approach is to add a new method public function displayNotifications or whatever you want to name it, to your component, move all the logic in it and then in your layout/view etc., call it with Yii::$app->notificationManager->displayNotifications(). You can also pass additional parameters to it and enhance your logic.
notificationManager has to be replaced with the name you registered your custom component in the Yii app config (web.php for basic app, main.php for advanced app).
LE - If you only registered your component for bootstrap, you should also register it in the components array.
'notificationManager' => [
'class' => '\admin\components\NotificationManager'
]

Yii2 - module login in basic application template

Using the "basic" application template, what is the correct way of setting up a module login that is separate from the main site login?
For example I have an "admin" module which requires a login. I also need a user login for the main site.
I have done the following:
Created admin module using gii tool
Created models folder within the admin module folder
Placed LoginForm.php and User.php within this folder (also updated the namespace declarations in these files)
Added AccessControl behaviour and login/logout actions to modules\admin\controllers\DefaultController.php
Updated config\web.php as follows:
'modules' => [
'admin' => [
'class' => 'app\modules\admin\Module',
],
],
Updated app\modules\admin\Module.php as follows:
public function init()
{
parent::init();
Yii::$app->set('user', [
'class' => 'yii\web\User',
'identityClass' => 'app\modules\admin\models\User',
'enableAutoLogin' => true,
'loginUrl' => ['admin/default/login'],
]);
Yii::$app->set('session', [
'class' => 'yii\web\Session',
'name' => '_adminSessionId',
]);
}
The problem I am having is that if I try to access an admin page when I am not logged in, it shows the login form (this is correct). However upon logging in, it is just redirects me back to the main site. It should redirect me to the admin page I was trying to access.
In DefaultController.php, it has the following (default code):
if ($model->load(Yii::$app->request->post()) && $model->login())
return $this->goBack();
What is the correct way of doing this so I can have independent logins for the admin module and for the main site? I don't want to use the "advanced application template" as that adds some unnecessary complexity.
The User component allows you to set a returnUrl, the getter explained: This method reads the return URL from the session. It is usually used by the login action which may call this method to redirect the browser to where it goes after successful authentication.
Recommended: Before processing the data, set the returnUrl by calling Yii::$app->user->setReturnUrl(Url::current()); (this will store the page the user was on in the session, moreover, you can manipulate GET parameters using Url::current() before passing it to the session), and let the framework do its magic.
Not recommended: After processing the data, you can rely on referrer (which won't work in some cases) as following
return Yii::$app->request->referrer ? $this->redirect(Yii::$app->request->referrer) : $this->goHome(); or instead of goHome which basically redirects to app->homeUrl that can be set during Module init, you could say $this->redirect('your/admin/index');
I recommend setting the ['homeUrl' => 'your/admin/index'] during the initialization of the Module, as you might need it for other features as well and Yii2 uses it as a "fallback" to some redirects as well.
The best way is to create new controller in the admin module, which should have login, logout actions. Because, in future you may add there some extra logic.
In that login action you can use same LoginForm.
You can specify redirect url in that login action.
Admin module class can looks like this:
namespace app\modules\admin;
class Module extends \yii\base\Module
{
public $layout = 'main';
public $defaultRoute = 'main/index';
public function init()
{
parent::init();
Yii::$app->errorHandler->errorAction = '/admin/main/error';
Yii::$app->user->loginUrl = '/admin/main/login';
.....
}
}
goBack() defaults to the homeUrl if the returnUrl for the user hasn't been set.
Why not just redirect?
if ($model->load(Yii::$app->request->post()) && $model->login())
return $this->redirect(['myadminmodule']);
go to config/web.php and add it to the components
'backendUrlManager'=>[
'class' => 'yii\web\urlManager',
'enablePrettyUrl'=>true,
'showScriptName'=>false,
'baseUrl'=>'/admin',
],
In DefaultController.php, it has the following (default code):
if ($model->load(Yii::$app->request->post()) && $model->login())
return Yii::$app->getResponse()->redirect(Yii::$app->backendUrlManager->baseUrl);

Meaning of Yii::$app->user->identity->role->role_name;

In a book called Yii2 for Beginners, which is mainly about the advanced template, I have encountered the following unexplained code, which seems relevant to RBAC:
$userHasRoleName = Yii::$app->user->identity->role->role_name;
What exactly does this mean? For example, I guess that this:
Yii::$app->user
refers to this file:
vendor\yiisoft\yii2\web\User.php
Is this correct?
In any case, what does the rest of the code refer to? Specifically:
->identity->role->role_name
In the above User.php file, I have not been able to find anything like "function identity()", so it can't be that. I have found numerous $identity variables, but I don't know which one the code might be referring to. And there is no $role variable at all.
What is this code referring to:
Yii::$app->user->identity->role->role_name;
Yii described magic methods like __get, __set and so on, to get access for inaccessible properties. Oftenly such methods begins from get or set (in Yii implementation it is). To get access to ->identity, \yii\web\User has method getIdentity. This method return identity wich you described in config with identityClass property for user component. Oftenly identityClass is a AR model which implements IdentityInterface.
'components' => [
'user' => [
'identityClass' => 'common\models\User',
]
]
To get access to ->role for example you must to create a new method
namespace common\models;
class User extends ActiveRecord implements IdentityInterface {
public function getRole(){
// if user can have only one role
return current( \Yii::$app->authManager->getRolesByUser( $this->id ) );
}
}
Btw implementation of ->role->role_name may be very different.

Upgrading Laravel 4.2 to 5.0, getting [ReflectionException] Class App\Http\Controllers\PagesController does not exist

I was updating my project from laravel 4.2 to laravel 5.0. But, after I am facing this error and have been trying to solve it for the past 4 hours.
I didn't face any error like this on the 4.2 version. I have tried composer dump-autoload with no effect.
As stated in the guide to update, I have shifted all the controllers as it is, and made the namespace property in app/Providers/RouteServiceProvider.php to null. So, I guess all my controllers are in global namespace, so don't need to add the path anywhere.
Here is my composer.json:
"autoload": {
"classmap": [
"app/console/commands",
"app/Http/Controllers",
"app/models",
"database/migrations",
"database/seeds",
"tests/TestCase.php"
],
Pages Controller :
<?php
class PagesController extends BaseController {
protected $layout = 'layouts.loggedout';
public function getIndex() {
$categories = Category::all();
$messages = Message::groupBy('receiver_id')
->select(['receiver_id', DB::raw("COUNT('receiver_id') AS total")])
->orderBy('total', 'DESC'.....
And, here is BaseController.
<?php
class BaseController extends Controller {
//Setup the layout used by the controller.
protected function setupLayout(){
if(!is_null($this->layout)) {
$this->layout = View::make($this->layout);
}
}
}
In routes.php, I am calling controller as follows :
Route::get('/', array('as' => 'pages.index', 'uses' => 'PagesController#getIndex'));
Anyone please help. I have been scratching my head over it for the past few hours.
Routes are loaded in the app/Providers/RouteServiceProvider.php file. If you look in there, you’ll see this block of code:
$router->group(['namespace' => $this->namespace], function($router)
{
require app_path('Http/routes.php');
});
This prepends a namespace to any routes, which by default is App\Http\Controllers, hence your error message.
You have two options:
Add the proper namespace to the top of your controllers.
Load routes outside of the group, so a namespace isn’t automatically prepended.
I would go with option #1, because it’s going to save you headaches in the long run.

Tracking data on each request in Yii2

What will the best place in the code to track user's last visit date or any data that should be tracked on each request to application? Is it good idea to extend yii\web\Controller?
You can use a base controller and of course it is a good idea. But there is another approach that is more elegant. You can do like below:
1 - Add a component into your components directory, for example(MyTrackingClass):
namespace app\components;
class MyTrackingClass extends \yii\base\Component{
public function init() {
//SOME CODE HERE
//SOME CODE HERE
//SOME CODE HERE
parent::init();
}
}
2 - Add MyTrackingClass component into your components array in config file:
'components' => [
'MyTrackingClass'=>[
'class'=>'app\components\MyTrackingClass'
],
//other components
3 - Add MyTrackingClass into bootstarp array in config file:
'bootstrap' => ['log','MyTrackingClass'],
Now, you can see everything you wrote in your init() method, will be executed in every request, in every module, controller, action and so on...
Please note that, if you do not need to use Events and Behaviors you can use \yii\base\Object instead of \yii\base\Component