laravel 8 - Return array based user role - php

I am using Metronic 8 admin dashboard template for laravel.
The developer made the nav menu as array:
<?php
return array(
// Main menu
'main' => array(
//// Dashboard
array(
'title' => 'Dashboard',
'path' => '',
'icon' => theme()->getSvgIcon("demo1/media/icons/duotune/art/art002.svg", "svg-icon-2") ,
) ,
)
)
If you want to add a new nav item, just add an object inside the array.
I am currently using Spatie user roles & permissions. According to their docs, I can use blade directives like this:
#can('permission name')
test
#endcan
OR
#role('role name')
test
#endrole
Unfortunately, the menu.php is a plain php config file and not a blade.
How can I use spatie to hide nav items based on user role?
Resources:
Metronic laravel docs
Spatie docs

You need to change configs runtime, in Controller or in Middleware or even in Provider, depending on your needs. You need to do something like:
$navs = config('config.global.menu');
if(auth()->user()->can('permission name')){
$navs[] = 'nav item';
config([
'config.global.menu' => $navs
]);
}

You can create a new listener and bind Illuminate\Auth\Events\Login event to it in EventServiceProvider. I've named this event as ModifyMetronicMenu.
protected $listen = [
Login::class=>[
ModifyMetronicMenu::class
]
];
ModifyMetronicMenu listener
namespace App\Listeners;
class ModifyMetronicMenu
{
public function handle($event)
{
$newMenu = array_filter(config('menu.main'),function ($item){
return \Auth::user()->can(data_get($item,'title'));
});
// Update the menu config file
config(['menu.main'=>$newMenu]);
}
}
As you can see, I have filtered menu and have updated the config. Notice that the condition that I wrote in the array_filter is not correct and you should tune it base on your needs.

Related

How to separate each action in a different file in Yii2

I'm new in Yii2 framework. To give structure to my web application, I want to put each controller in a subfolder and make a separate controller for each action in each subfolder. Like that one!
controllers
**User**
IndexController
EditController
UpdateController
**Profile**
IndexController
EditController
UpdateController
How can I arrange that in Yii2.
thanks in advance
Well your example is right.
controllers/user/IndexController.php
views/user/index/index.php
Then in IndexController/EditController/UpdateController you have actionIndex and if you run domain.com/user/index or domain.com/user/edit it will execute actionIndex in current controller (IndexController or EditController)
domain.com/user/index = domain.com/user/index/index
and
domain.com/user/edit = domain.com/user/edit/index
Not sure if there are other more effective ways, but one that works would be the following.
Note: This example assumes that you're using https://github.com/yiisoft/yii2-app-advanced but it can work for the basic app also, just changing the namespaces.
So, let's say you say we have a controller, and we want to store some of its actions into different php files.
<?php
// frontend\controllers\SiteController.php
namespace frontend\controllers;
use yii\web\Controller;
class SiteController extends Controller {
public function actions() {
return [
'error' => [
'class' => 'yii\web\ErrorAction',
],
'captcha' => [
'class' => 'yii\captcha\CaptchaAction',
'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null,
],
'hello-world' => [
'class' => 'frontend\controllers\site\HelloWorldAction',
],
];
}
public function actionIndex() {
// ...
}
So you can see we've got 3 external actions and one internal one.
The first two ones, are framework's tools for Error page and Captcha generation, actually they've inspired my answer.
And the third one, is defined by us:
'hello-world' => [
'class' => 'frontend\controllers\site\HelloWorldAction',
],
So we've named the action and we created our new action class into a separate directory.
<?php
// frontend\controllers\site\HelloWorldAction.php
namespace frontend\controllers\site;
use yii\base\Action;
class HelloWorldAction extends Action {
public function run($planet='Earth') {
return $this->controller->render('hello-world', [
'planet'=>$planet,
]);
}
}
And last, our view:
<?php
// frontend\views\site\hello-world.php
/* #var $this yii\web\View */
use yii\helpers\Html;
$this->title = 'Hello world page';
?>
<h1>Hello world!</h1>
<p>We're on planet <?php echo Html::encode($planet); ?></p>
And seeing it in action:
Update
After posting the answer I realized that maybe you could benefit from another technique also.
The previous answer is good if you want to do just that: Extract actions into individual files.
But, if your application will be of certain size, maybe you should consider using Modules.
You can create them manually or generate them with Gii:
And once generated, include it in your config:
<?php
......
'modules' => [
'profile' => [
'class' => 'frontend\modules\profile\Module',
],
],
......
Modules do just that, group application logic into one directory, controllers, models, views, components, etc.
Two more tips:
Now to access your module, simply visit http://www.your-site.local/profile/default/index, as you can see, it goes like module/controller/action.
And if you want to generate links to actions inside modules, you would do:
<?php
echo Url::to([
'profile/default/index',
'param'=>'value',
]);
?>
Again as you can see we're using module/controller/action as the route.
Last thing, if you're inside a module, let's say profile/picture/edit, and you want to link to Contact page from SiteController, you would do:
<?php
echo Url::to([
'//site/contact',
'param'=>'value',
]);
?>
Note the double slash // at the beginning of the route. Without it, it will generate the url to the current module profile/site/contact.

Drupal 8 - Adding/Creating Menu Item with HTML in it

I'm creating a website using Drupal 8. I want to create a menu item link that I could add HTML/Javascript code in it (I'm trying to display a widget that expands on click in the menu rather than displaying it in its own block next to the menu). The only way I could see to add a menu item is to link to a page.
You could work with a derivative. This lets you customize pretty much everything about it and control what is to be made. An example below:
Note: I am assuming you have a general knowledge of custom modules. If not follow this link
Create the following file in your custom module:
# my_module.links.menu.yml
my_module.custom_links:
deriver: \Drupal\my_module\Plugin\Derivative\CustomLinkDerivative
And now for the derivative class (Located under my_module/src/Plugin/Derivative)
<?php
namespace Drupal\my_module\Plugin\Derivative;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class CustomLinkDerivative extends DeriverBase implements ContainerDeriverInterface {
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static();
}
/**
* {#inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
$links['custom_menulink'] = [
'title' => t('Custom menulink'),
'menu_name' => 'main',
'route_name' => 'entity.node.canonical',
'parent' => footer,
'route_parameters' => [
'node' => 1,
]
] + $base_plugin_definition;
return $links;
}
}
Note: Derivatives get triggered during rebuild of cache!
This just creates a link in the footer that directs to node 1. You can add all sort of stuff and logic to your liking. Hope this helps you :)

Laravel 5 custom form validation

I come from a codeigniter background(I know a bit overdue) and I have small issue in creating custom form validation rules in Laravel 5.2.
Many of the tutorials I checked suggested doing such in the route file and setting up a request file etc, but I still am confused.
My need is very simple.
I have product categories that have sub categories.
For each product category, there needs to have a default sub category.
So when creating sub categories i have a tick asking if that sub category is the default category.
I need the form validator to trigger false during form validation in the sub category creation process if an already created sub category has been selected as the default sub category for that category.
Below is the part of my code that contains the form validation section.
$attributeNames = array(
'category_id' => 'UOM Category Name',
'sub_category_name' => 'Sub Category Name',
'is_default' => 'Default'
);
$validator = Validator::make($request->all(), [
'category_id' => 'required',
'sub_category_name' => 'required|unique:sub_categories,sub_category_name',
'is_default' => 'required', //POSSIBLE TRIGGER GOES HERE. In CI the a custom function name went here
]);
$validator->setAttributeNames($attributeNames);
if ($validator->fails()) {
$response = new Response();
return $response->setStatusCode(400, $validator->errors());
} else {
//proceed with store
}
Sorry about bad english.
Also, I have seen that form validation is done on a FormRequest. Is this best practice?
Yes, form requests exist for this. You can create a new FormRequest with the command php artisan make:request CreateSubCategoryRequest, then add your rules:
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'category_id' => 'required|exists:categories,id', // If you're making sure the parent category ID exists
'sub_category_name' => 'required|unique:sub_categories,sub_category_name',
'is_default' => 'required|boolean'
];
}
For your default subcategory validation, I believe what you're asking is how you can validate if the parent category already has a default subcategory (only allowing 1 default subcategory per main category). In that case, you should assign a default_subcategory_id field to your main category record instead of on each subcategory itself. Then add a relationship to the main category model for the default subcategory and check if it's null.
As always the laravel docs are your best friend https://laravel.com/docs/5.2/validation#validation-quickstart
You have multiple options on how to and where to validate your request. Best practice depends on your usecase.
However, for me mostly creating a request type for a specfific entity was the best thing. You can use the artisan command to create a new reqest
php artisan make:request CategoryRequest
// or whatever your entity is called
You will find the file in app/Http/Requests.
You have a rules function where you can just paste your rules you need to validate
return [
'category' => 'required|min:5'
];
To create a custom rule you can put this in your AppServiceProvider.php
public function boot()
{
// custom rule
Validator::extend('foo', function($attribute, $value, $parameters, $validator) {
return $value == 'foo';
});
}
Custom Validation Rules: https://laravel.com/docs/5.2/validation#custom-validation-rules

Laravel: Where to store global arrays data and constants?

I just started working with Laravel. I need to rewrite a whole system I made some years ago, using Laravel 4 as base framework. In my old system, I used to have a constant.php file with some constants declared, and a globals.php file which contained lots of array sets (for example, categories statuses, type of events, langs, etc.). By doing so, I could use something like
foreach ( $langs as $code => $domain ) {
// Some stuff
}
anywhere in my app.
My question is, how can I store that info in the so called "laravel way". I tried using some sort of object to store this info, setting this as a service and creating for it a facade:
app/libraries/Project/Constants.php
namespace PJ;
class Constants {
public static $langs = [
'es' => 'www.domain.es',
'en' => 'www.domain.us',
'uk' => 'www.domain.uk',
'br' => 'www.domain.br',
'it' => 'www.domain.it',
'de' => 'www.domain.de',
'fr' => 'www.domain.fr'
];
}
app/libraries/Project/ConstantsServiceProvider.php
namespace PJ;
use Illuminate\Support\ServiceProvider;
class ConstantsServiceProvider extends ServiceProvider {
public function register() {
$this->app->singleton('PJConstants', function() {
return new Constants;
});
}
}
app/libraries/Project/ConstantsFacade.php
namespace PJ;
use Illuminate\Support\Facades\Facade;
class ConstantsFacade extends Facade {
protected static function getFacadeAccessor() {
return 'PJConstants';
}
}
composer.json
"psr-4": {
"PJ\\": "app/libraries/Project"
},
and so I access that property as PJ\Constants::$langs.
This works, but I doubt it is the most efficient or correct way of doing it. I mean, is it the right way to "propagate" a variable by creating a whole Service Provider and facades and all such stuff? Or where should I put this data?
Thanks for any advice.
EDIT # 01
Data I want to pass to all controllers and views can be directly set in script, like in the example at the beginning of my post, but it can also be generated dynamically, from a database for example. This data could be a list of categories. I need them in all views to generate a navigation bar, but I also need them to define some routing patterns (like /category/subcategory/product), and also to parse some info in several controllers (Like get info from the category that holds X product).
My array is something like:
$categories = [
1 => ['name' => 'General', 'parent' => 0, 'description' => 'Lorem ipsum...'],
2 => ['name' => 'Nature', 'parent' => 0, 'description' => 'Lorem ipsum...'],
3 => ['name' => 'World', 'parent' => 0, 'description' => 'Lorem ipsum...'],
4 => ['name' => 'Animals', 'parent' => 2, 'description' => 'Lorem ipsum...']
]
Just as an example. Index is the id of the category, and the Value is info associated with the category.
I need this array, also, available in all Controllers and Views.
So, should I save it as a Config variable? How else could I store these data; what would be the best and semantically correct way?
For most constants used globally across the application, storing them in config files is sufficient. It is also pretty simple
Create a new file in the app/config directory. Let's call it constants.php
In there you have to return an array of config values.
return [
'langs' => [
'es' => 'www.domain.es',
'en' => 'www.domain.us'
// etc
]
];
And you can access them as follows
Config::get('constants.langs');
// or if you want a specific one
Config::get('constants.langs.en');
And you can set them as well
Config::set('foo.bar', 'test');
Note that the values you set will not persist. They are only available for the current request.
Update
The config is probably not the right place to store information generated from the database. You could just use an Eloquent Model like:
class Category extends Eloquent {
// db table 'categories' will be assumed
}
And query all categories
Category::all();
If the whole Model thing for some reason isn't working out you can start thinking about creating your own class and a facade. Or you could just create a class with all static variables and methods and then use it without the facade stuff.
For Constants
Create constants.php file in the config directory:-
define('YOUR_DEFINED_CONST', 'Your defined constant value!');
return [
'your-returned-const' => 'Your returned constant value!'
];
You can use them like:-
echo YOUR_DEFINED_CONST . '<br>';
echo config('constants.your-returned-const');
For Static Arrays
Create static_arrays.php file in the config directory:-
class StaticArray
{
public static $langs = [
'es' => 'www.domain.es',
'en' => 'www.domain.us',
'uk' => 'www.domain.uk',
'br' => 'www.domain.br',
'it' => 'www.domain.it',
'de' => 'www.domain.de',
'fr' => 'www.domain.fr'
];
}
You can use it like:-
echo StaticArray::$langs['en'];
Note: Laravel includes all config files automatically, so no need of manual include :)
Create common constants file in Laravel
app/constants.php
define('YOUR_CONSTANT_VAR', 'VALUE');
//EX
define('COLOR_TWO', 'red');
composer.json
add file location at autoload in composer.json
"autoload": {
"files": [
"app/constants.php"
]
}
Before this change can take effect, you must run the following command in Terminal to regenerate Laravel’s autoload files:
composer dump-autoload
For global constants in Laravel 5, I don't like calling Config for them. I define them in Route group like this:
// global contants for all requests
Route::group(['prefix' => ''], function() {
define('USER_ROLE_ADMIN','1');
define('USER_ROLE_ACCOUNT','2');
});
I think the best way is to use localization.
Create a new file messages.php in resources/lang/en (en because that is what is set in my config/app 'locale'=>'en')
return an array of all your values
return [
'welcome' => 'Welcome to our application'
];
to retrieve for laravel 5.3 and below
echo trans('messages.welcome');
or
echo Lang::get('messages.welcome');
for 5.4 use
echo __('messages.welcome')
laravel 5.0 localization
or
laravel 5.4 localization
Just to add to the above answer you will have to include the config class before you could start using it in Laravel 5.3
use Illuminate\Support\Facades\Config;
Atleast in Laravel 5.4, in your constructor you can create them;
public function __construct()
{
\Config::set('privileged', array('user1','user2');
\Config::set('SomeOtherConstant', 'my constant');
}
Then you can call them like this in your methods;
\Config::get('privileged');
Especially useful for static methods in the Model, etc...
Reference on Laracasts.com https://laracasts.com/discuss/channels/general-discussion/class-apphttpcontrollersconfig-not-found
Just put a file constants.php file into the config directory and define your constants in that file, that file will be auto loaded,
Tested in Laravel 6+
Create a constants class:
<?php
namespace App\Support;
class Constants {
/* UNITS */
public const UNIT_METRIC = 0;
public const UNIT_IMPERIAL = 1;
public const UNIT_DEFAULT = UNIT_METRIC;
}
Then use it in your model, controller, whatever:
<?php
namespace App\Models;
use App\Support\Constants;
class Model
{
public function units()
{
return Constants::UNIT_DEFAULT;
}
}

how to exclude some routes when user is not logged in zfcuser?

So, i am new in Zend Framework, i installed ZfcUser and i want when the user is not logged to didn't access to some routes for example : /blog/article/add,
actually i use <?php if($this->zfcUserIdentity()) :?> to check if the user is logged but how can i redirect the user to my login route/user if is try to access to /blog/article/add,
so plz if someone has any idea i will be very appreciative :)
I disagree with the selected answer because it's not really practical to do these kind of things inside every action of every controller that you want to deny access to.
There are two big modules out there that are used for this task. The first one being BjyAuthorize and the other big one being ZfcRbac. Please check them out. ZfcRbac is my favorite (because i wrote documentation for it) but it requires PHP 5.4+
Simple Way:
in controller:
if (!$this->zfcUserAuthentication()->hasIdentity()) {
return $this->redirect()->toRoute('route_to_login',array('param' => $param));
}
Some more:
(after auth zfcuset will redirect you to previous controller)
in module.config.php
'controllers' => array(
'invokables' => array(
'zfcuser' => 'Application\Controller\UserController',
),
...
),
next copy file vendor\zf-commons\zfc-user\src\ZfcUser\Controller\UserController.php
to module Application (same folder as in module.config.php)
delete all function except authenticateAction
add:
use Zend\Session\Container;
and change
return $this->redirect()->toRoute($this->getOptions()->getLoginRedirectRoute());
to
$redirect_session = new Container('redirect');
$redirect = $redirect_session->redirect;
if($redirect!='')
{
unset($_SESSION['redirect']);
return $this->redirect()->toRoute($redirect, array('lang' => $lang));
}
return $this->redirect()->toRoute($this->getOptions()->getLoginRedirectRoute(),array('param' => $param));
and at last add in controller
use Zend\Session\Container;
...
if (!$this->zfcUserAuthentication()->hasIdentity()) {
$redirect_session = new Container('redirect');
$redirect_session->redirect = 'route_to_this_controller_action';
return $this->redirect()->toRoute('route_to_login',array('param' => $param));
}

Categories