Create CSS classes based on user role - moodle

Working with Moodle 3.9 and a fork of the current Academi theme.
I am trying to create a css class hook on the <body> element which is based on the user's role(s). I understand that a user's role(s) are context-driven, (maybe a student in one course, and something else in another), so I would output all the roles as classes for a given context.
Based on some extensive digging on the moodle forum I have the following:
// User role based CSS classes
global $USER, $PAGE;
$context = context_system::instance();
$roles = get_user_roles($context, $USER->id, true);
if(is_array($roles)) {
foreach($roles as $role) {
$PAGE->add_body_class('role-'.$role->shortname);
}
} else {
$PAGE->add_body_class('role-none');
}
Preferably I'd like this to run on every page. From within the theme, I've tried just placing this in just about every location/function I thought could be executed early enough to modify the body element. Either I get no output at all or a warning indicating that it is too late to run add_body_class().
I've had a skim of the Page and Output APIs and I still don't have a sense of how or when to execute this code. Should this be a custom plugin instead?

Override the header() function on the core renderer of your current theme template, for instance, on theme\YOUR_THEME\classes\output\core_renderer.php:
namespace theme_YOUR_THEME\output;
defined('MOODLE_INTERNAL') || die;
use context_system;
class core_renderer extends \theme_boost\output\core_renderer {
public function header() {
global $USER;
$roles = get_user_roles(context_system::instance(),$USER->id);
if (is_array($roles) && !empty($roles)){
foreach($roles as $role){
$this->page->add_body_class('role-'.$role->shortname);
}
}else{
$this->page->add_body_class('role-none');
}
return parent::header();
}
}
This way, every page would call this function and have the desired css classes added to the body tag.

Related

filter records in popup list view if assigned user is login SUITECRM

I want to filter records so that the assigned user can only see the records that are assigned to him from the popup list view.
The reason why I'm not doing this in the roles management is because if I assigned a user to a client record then other users that have the same role wouldn't able to see it so I've set the role->list tab to "all" and added custom code in list view that only the login user can see their own records.
Here's what I've done.
<?php
require_once('include/MVC/View/views/view.popup.php');
class AccountsViewPopup extends ViewPopup
{
public function display()
{
parent::display(); // TODO: Change the autogenerated stub
require_once 'modules/ACLRoles/ACLRole.php';
$ACLRole = new ACLRole();
$roles = $ACLRole->getUserRoles($GLOBALS['current_user']->id);
if (in_array('User1', $roles)) {
global $db, $current_user;
$this->where .= " AND accounts.assigned_user_id = '$current_user->id' AND deleted=0 ";
}
}
}
But i get this error:
Undefined property: AccountsViewPopup::$where
For list view only: custom/modules/MODULE_NAME/views/view.list.php
and following is the helping code:
require_once('include/MVC/View/views/view.list.php');
class MODULE_NAMEViewList extends ViewList {
function listViewProcess() {
global $current_user;
$this->params['custom_where'] = ' AND module_name.name = "test" ';
parent::listViewProcess();
}
}
For list and popup view(both):
You need to change the logic inside create_new_list_query function which actually prepares a query. Some modules have override it a bean level(e.g. see modules/Leads/Lead.php).
If you want to override it in upgrade safe manner then create a file in custom directory e.g: custom/modules/Leads/Lead.php, then extend it from the core bean class like following:
<?php
if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
require_once('modules/Leads/Lead.php');
class CustomLead extends Lead {
function create_new_list_query($order_by, $where,$filter=array(),$params=array(), $show_deleted = 0,$join_type='', $return_array = false,$parentbean=null, $singleSelect = false, $ifListForExport = false)
{
// Code from create_new_list_query in and then modify it accordingly.
}
}
Register new bean class in this location: custom/Extension/application/Ext/Include/custom_leads_class.php and registration code will look like following:
<?php
$objectList['Leads'] = 'Lead';
$beanList['Leads'] = 'CustomLead';
$beanFiles['CustomLead'] = 'custom/modules/Leads/Lead.php';
?>
I know this has been answered, but decided to post my solution anyway. I had almost the same problem some time ago (7.10.7).
PopupView has method getCustomWhereClause() which you can implement in your custom view.
It has to return containing string with the conditions.
Example:
custom/modules/Meetings/views/view.popup.php
/*class declaration and other stuff*/
protected function getCustomWhereClause()
{
global $current_user;
return " ( {$this->bean->table_name}.assigned_user_id='{$current_user->id}') ";
}
Remember to leave at least one space at the start and the end because SuiteCRM actually forgets to add it and it may result in broken query (but it's fairly easy to find in logs).

How to give a fixed Uid to my Action

Hy,
I'm trying to call my action with allways a fixed Uid (configured by TS) so I could put a plugin on my page to register for a specific Event. And don't have to go over a Event List click the Event click register.
I tried the following which did not work out:
public function newAction(
\XYZ\xyz\Domain\Model\Registration $newRegistration = NULL,
\XYZ\xyz\Domain\Model\Event $event = 'DD8B2164290B40DA240D843095A29904'
)
The next didn't one work either!
public function newAction(
\XYZ\xyz\Domain\Model\Registration $newRegistration = NULL,
\XYZ\xyz\Domain\Model\Event $event = Null
) {
$myinstance = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(
'XYZ\\xyz\\Domain\\Model\\Event'
);
$event = $myinstance->findByUid('DD8B2164290B40DA240D843095A29904');
.......
}
So I was woundering is there a way to give my fixed Uid to the action?
In TYPO3 calling Extbase actions is done in the routing and dispatching components - to pass anything from the outside that is different from a numeric uid value a custom property TypeConverter would have to be implemented that transforms a particular string pattern into a value domain object of type Event.
However, there's a simpler approach by using configuration:
1) Provide configuration in TypoScript
Extbase uses a strong naming convention based on the extension name and optionally the plugin name. Thus, either tx_myextension or tx_myextension_someplugin can be used - latter is more specific for for according somePlugin. Besides that settings are automatically forwarded and provided in an Extbase controller context - accessible by $this->settings.
plugin.tx_xyz {
settings {
newActionEventIdentifier = DD8B2164290B40DA240D843095A29904
}
}
2) Retrieve data via repository
\XYZ\xyz\Domain\Repository\EventRepository
Use a dedicated EventRepository::findByIdentifier(string) method to retrieve the data. The property names are just assumptions since there are no explicit mentions how exactly the event data is persisted and whether it is persisted in a relational DBMS at all.
<?php
namespace XYZ\xyz\Domain\Repository;
class EventRepository
{
public function findByIdentifier($identifier)
{
$query = $this->createQuery();
$query->matching(
$query->equals('event_id', $identifier)
);
return $query->execute();
}
}
3) Putting all together in the according controller
The $event property was removed from the action since that entity is pre-defined and cannot be submitted from the outside (and to support the string to Event entity transformation a custom TypeConverter would be required as mentioned earlier).
public function newAction(
\XYZ\xyz\Domain\Model\Registration $newRegistration = null
) {
$event = $this->eventRepository->findByIdentifier(
$this->settings['newActionEventIdentifier']
);
if ($event === null) {
throw new \RuntimeException('No event found', 1522070079);
}
// the regular controller tasks
$this->view->assign(...);
}

Agiletoolkit: Autocomplete/Plus error: null model

I am trying to use the autocomplete addon in agile toolkit (I am still very much new to this, but it seems to be very well suited for my needs). Autocomplete Basic works, but when I use Plus and press the plus-button I get an error connected to no model set. In the Plus source the self-model should be used - but I don't understand how I should set the model of the autocomplete form.
This is the important part of the stack trace, I think:
Frontend_page_form: Form->setModel(Null)
Frontend_createquestions_form_question_id:
autocomplete\Form_Field_Plus->autocomplete{closure}(Object(Page))
This is my model:
class Model_QuestionInCollection extends Model_Table {
public $entity_code='questionincollection';
function init(){
parent::init();
$this->hasOne('Question')->display(array('form'=>'autocomplete/Plus'));
This is the code:
$form=$this->add('Form');
$form->setModel('QuestionInCollection');
--- EDIT
I ended up changing the model in autocomplete, and now it works, showing that something is wrong in the original "$self->model" - but of course it cannot be generalized. I did some extra changes (to make the new record show up in the autocomplete-field), so Autocomplete/Plus now is like this:
<?php
namespace autocomplete;
class Form_Field_Plus extends Form_Field_Basic
{
function init()
{
parent::init();
$self = $this;
$f = $this->other_field;
// Add buttonset to name field
$bs = $f->afterField()->add('ButtonSet');
// Add button - open dialog for adding new element
$bs->add('Button')
->set('+')
->add('VirtualPage')
->bindEvent('Add New Record', 'click')
->set(function($page)use($self) {
$model=$this->add('Model_Question');
$form = $page->add('Form');
$form->setModel($model); //Was: $self->model
//Would be nice if it worked...: $form->getElement($model->title_field)->set($self->other_field->js()->val());
if ($form->isSubmitted()) {
$form->update();
$js = array();
$js[] = $self->js()->val($form->model[$model->id_field]);
$js[] = $self->other_field->js()->val($form->model[$model->title_field]);
$form->js(null, $js)->univ()->closeDialog()->execute();
}
});
}
}
Here is an example on using autocomplete: http://codepad.demo.agiletech.ie/interactive-views/autocomplete
You can manually create a form and link the field with Model.
If that works fine, you can move on to trying and get your own example working. It seems OK, and should work in theory.

ZF2 Use Redirect in outside of controller

I'm working on an ACL which is called in Module.php and attached to the bootstrap.
Obviously the ACL restricts access to certain areas of the site, which brings the need for redirects. However, when trying to use the controller plugin for redirects it doesn't work as the plugin appears to require a controller.
What's the best way to redirect outside from outside of a controller? The vanilla header() function is not suitable as I need to use defined routes.
Any help would be great!
Cheers-
In general, you want to short-circuit the dispatch process by returning a response. During route or dispatch you can return a response to stop the usual code flow stop and directly finish the result. In case of an ACL check it is very likely you want to return that response early and redirect to the user's login page.
You either construct the response in the controller or you check the plugin's return value and redirect when it's a response. Notice the second method is like how the PRG plugin works.
An example of the first method:
use Zend\Mvc\Controller\AbstractActionController;
class MyController extends AbstractActionController
{
public function fooAction()
{
if (!$this->aclAllowsAccess()) {
// Use redirect plugin to redirect
return $this->redirect('user/login');
}
// normal code flow
}
}
An example like the PRG plugin works:
use Zend\Mvc\Controller\AbstractActionController;
use Zend\Http\Response;
class MyController extends AbstractActionController
{
public function fooAction()
{
$result = $this->aclCheck();
if ($result instanceof Response) {
// Use return value to short-circuit
return $result
}
// normal code flow
}
}
The plugin could then look like this (in the second case):
use Zend\Mvc\Controller\Plugin\AbstractPlugin;
class AclCheck extends AbstractPlugin
{
public function __invoke()
{
// Check the ACL
if (false === $result) {
$controller = $this->getController();
$redirector = $controller->getPluginManager()->get('Redirect');
$response = $redirector->toRoute('user/login');
return $response;
}
}
}
In your question you say:
[...] it doesn't work as the plugin appears to require a controller.
This can be a problem inside the controller plugin when you want to do $this->getController() in the plugin. You either must extend Zend\Mvc\Controller\Plugin\AbstractPlugin or implement Zend\Mvc\Controller\Plugin\PluginInterface to make sure your ACL plugin is injected with the controller.
If you do not want this, there is an alternative you directly return a response you create yourself. It is a bit less flexible and you create a response object while there is already a response object (causing possible conflicts with both responses), but the plugin code would change like this:
use Zend\Mvc\Controller\Plugin\AbstractPlugin;
use Zend\Http\PhpEnvironment\Response;
class AclCheck extends AbstractPlugin
{
public function __invoke()
{
// Check the ACL
if (false === $result) {
$response = new Response;
$response->setStatusCode(302);
$response->getHeaders()
->addHeaderLine('Location', '/user/login');
return $response;
}
}
}

Zend Framework: How to display multiple actions, each requiring different authorizations levels, on a single page

Imagine I have 4 database tables, and an interface that presents forms for the management of the data in each of these tables on a single webpage (using the accordion design pattern to show only one form at a time). Each form is displayed with a list of rows in the table, allowing the user to insert a new row or select a row to edit or delete. AJAX is then used to send the request to the server.
A different set of forms must be displayed to different users, based on the application ACL.
My question is: In terms of controllers, actions, views, and layouts, what is the best architecture for this interface?
For example, so far I have a controller with add, edit and delete actions for each table. There is an indexAction for each, but it's an empty function. I've also extended Zend_Form for each table. To display the forms, I then in the IndexController pass the Forms to it's view, and echo each form. Javascript then takes care of populating the form and sending requests to the appropraite add/edit/delete action of the appropriate controller. This however doesn't allow for ACL to control the display or not of Forms to different users.
Would it be better to have the indexAction instantiate the form, and then use something like $this->render(); to render each view within the view of the indexAction of the IndexController? Would ACL then prevent certain views from being rendered?
Cheers.
There are a couple of places you could run your checks against your ACL:
Where you have your loop (or hardcoded block) to load each form.
In the constructor of each of the Form Objects, perhaps throwing a custom exception, which can be caught and appropriately handled.
From the constructor of an extension of Zend_Form from which all your custom Form objects are extended (probably the best method, as it helps reduce code duplication).
Keep in mind, that if you are using ZF to perform an AJAXy solution for your updating, your controller needs to run the ACL check in it's init() method as well, preventing unauthorized changes to your DB.
Hope that helps.
Have you solved this one yet?
I'm building a big database app with lots of nested sub-controllers as panels on a dashboard shown on the parent controller.
Simplified source code is below: comes from my parentController->indexAction()
$dashboardControllers = $this->_helper->model( 'User' )->getVisibleControllers();
foreach (array_reverse($dashboardControllers) as $controllerName) // lifo stack so put them on last first
{
if ($controllerName == 'header') continue; // always added last
// if you are wondering why a panel doesn't appear here even though the indexAction is called: it is probably because the panel is redirecting (eg if access denied). The view doesn't render on a redirect / forward
$this->_helper->actionStack( 'index', $this->parentControllerName . '_' . $controllerName );
}
$this->_helper->actionStack( 'index', $this->parentControllerName . '_header' );
If you have a better solution I'd be keen to hear it.
For my next trick I need to figure out how to display these in one, two or three columns depending on a user preference setting
I use a modified version of what's in the "Zend Framework in Action" book from Manning Press (available as PDF download if you need it now). I think you can just download the accompanying code from the book's site. You want to look at the Chapter 7 code.
Overview:
The controller is the resource, and the action is the privilege.
Put your allows & denys in the controller's init method.
I'm also using a customized version of their Controller_Action_Helper_Acl.
Every controller has a public static getAcls method:
public static function getAcls($actionName)
{
$acls = array();
$acls['roles'] = array('guest');
$acls['privileges'] = array('index','list','view');
return $acls;
}
This lets other controllers ask about this controller's permissions.
Every controller init method calls $this->_initAcls(), which is defined in my own base controller:
public function init()
{
parent::init(); // sets up ACLs
}
The parent looks like this:
public function init()
{
$this->_initAcls(); // init access control lists.
}
protected function _initAcls()
{
$to_call = array(get_class($this), 'getAcls');
$acls = call_user_func($to_call, $this->getRequest()->getActionName());
// i.e. PageController::getAcls($this->getRequest()->getActionName());
if(isset($acls['roles']) && is_array($acls['roles']))
{
if(count($acls['roles'])==0) { $acls['roles'] = null; }
if(count($acls['privileges'])==0){ $acls['privileges'] = null; }
$this->_helper->acl->allow($acls['roles'], $acls['privileges']);
}
}
Then I just have a function called:
aclink($link_text, $link_url, $module, $resource, $privilege);
It calls {$resource}Controller::getAcls() and does permission checks against them.
If they have permission, it returns the link, otherwise it returns ''.
function aclink($link_text, $link_url, $module, $resource, $privilege)
{
$auth = Zend_Auth::getInstance();
$acl = new Acl(); //wrapper for Zend_Acl
if(!$acl->has($resource))
{
$acl->add(new Zend_Acl_Resource($resource));
}
require_once ROOT.'/application/'.$module.'/controllers/'.ucwords($resource).'Controller.php';
$to_call = array(ucwords($resource).'Controller', 'getAcls');
$acls = call_user_func($to_call, $privilege);
if(isset($acls['roles']) && is_array($acls['roles']))
{
if(count($acls['roles'])==0) { $acls['roles'] = null; }
if(count($acls['privileges'])==0){ $acls['privileges'] = null; }
$acl->allow($acls['roles'], $resource, $acls['privileges']);
}
$result = $acl->isAllowed($auth, $resource, $privilege);
if($result)
{
return ''.$link_text.'';
}
else
{
return '';
}
}