0
votes

I am planning on converting a website to using MVC and php. I am planning on using the Twig template engine so that I can remove all code from the page templates, but I have hit on a problem, and that is with my menu. I only want a certain level of user to access each menu item.

I have a function which I call which returns true if a level is allowed. So for instance if you have a level of purchase you will be allowed to see Menu Item 1, but not allowed to see Menu Item 2. For instance Sales would not be able to see the first two menu items.

I know that you cannot call functions or classes in Twig.

eg (my function is in a class called logon, but that shouldn't matter)

<?php if ($logon->user_access('purchase','accountant','poweruser')):?>
    Menu Item 1
<?php endif;?>

 <?php if ($logon->user_access('accountant','poweruser')):?>
    Menu Item 2 
 <?php endif;?>

  <?php if ($logon->user_access('sales')):?>
    Menu Item 2 
 <?php endif;?>

The question is how do I make a template in twig with this functionality without using php? I am totally new to using a template engine.

edit... @Yoshi

Ok I include twig in my index.php with

require_once dirname(__DIR__).'/vendor/Twig/vendor/autoload.php';

Then whenever I want to render the page I call a function from my core/view

public static function renderTwig($template,$args =[])
{
    static $twig = null;

    if($twig === null){
        $loader = new \Twig_Loader_Filesystem('../App/Views');
        $twig = new \Twig_Environment($loader);
        echo $twig->render($template, $args);   
    }
}

Then in my controller I call

View::renderTwig('Home/index.html',[
            'css'=> $css,
            'name' => 'Dave',
            'colours' => ['red','green','blue']
        ]);

I set this all up from a tutorial I have been following called "Create php mvc project from scratch".

PS. I suppose in my renderTwig function I could send in variables which are used on every page and add it to the arg array for the page. That way I wouldn't need to add authentication to every page.

Edit: I am making a small amendment here. Even though Yoshi's answer is correct. I had a small problem which I have now solved, and I thought I would share this. In the twig template I am using it like this

{% if user_access('sales','purchase') %}Do something{% endif %}

The problem is that I don't know the number of items that are coming in so I need to use args, and this caused me a small problem, as the following line sent the string to my function, but only one item.

return $this->logon->user_access($roles);

The problem with func_get_args is that you cannot pass it to another function. It can only be read in the function that you send it to. So my small amendment was this.

return $this->logon->user_access($args = func_get_args());

Now the problem here is that it sends an array instead, but my php routines were set up to use args, and I wanted to use the same function in php that I used in twig. So what I did was to check if an array came in, I just looped through the array, but if a non array came in I checked the args. eg

public function user_access($levels)
{
    $bool=false;
    if(is_array($levels)):
        foreach($levels as $item):
            if($this->userlevel($item)==true $bool=true;
        endforeach;
    else:
        foreach(func_get_args() as $arg):
            if($this->userlevel($arg)==true $bool=true;
        endforeach;
    endif;
    return $bool;
}

So thankyou Yoshi

1
Just add the function as a twig extension.Yoshi
Or pass the $logon variable to your templateDarkBee
True, but in case of such general functionality it's a bit of an inconvenience. One would have to remember to always, on each render, get and pass the $logon to the view.Yoshi
$logon is not a variable. It is a class, and user_access is a function in that class that only returns true if you are at the correct level.Thomas Williams
@ThomasWilliams Obviously it's a variable storing an instance of the class. But that really doesn't matter. Be it a method call on an object, or a static class call, to transport it to the twig view, you'll need an extension or something callable passed to the view on each render.Yoshi

1 Answers

1
votes

Here are some ways to add the function (or the object) to your views.

Disclaimer: I think it's really bad practice to setup the twig environment inside a static method of View. I'm just going along with it, so to not over complicate things here. But if you're interested in how to do it better. Read up on dependency injection.

<?php

require_once dirname(__DIR__).'/vendor/Twig/vendor/autoload.php';

// only as an example example
class AccessChecker
{
    public function user_access(...$levels)
    {
        return true;
    }
}

class YourTwigExtension extends \Twig_Extension
{
    /**
     * @var AccessChecker
     */
    private $logon;

    /**
     * @param AccessChecker $logon
     */
    public function __construct(AccessChecker $logon)
    {
        $this->logon = $logon;
    }

    /**
     * @inheritdoc
     */
    public function getFunctions()
    {
        return [
            new Twig_Function('user_access', [$this, 'checkUserAccess']),
        ];
    }

    /**
     * @param array ...$roles
     *
     * @return mixed
     */
    public function checkUserAccess(...$roles)
    {
        return $this->logon->user_access(...$roles);
    }
}

class View
{
    public static function renderTwig($template, $args =[])
    {
        static $twig = null;

        if($twig === null) {
            $loader = new \Twig_Loader_Filesystem('../App/Views');
            $twig = new \Twig_Environment($loader);

            // the extras:
            $logon = new AccessChecker();

            // with globals:
            $twig->addGlobal('logon', $logon);

            /*
             * {% if logon.user_access('purchase','accountant','poweruser') %}
             *   ...
             * {% endif %}
             */

            // with functions:
            $twig->addFunction(new Twig_Function('user_access', [$logon, 'user_access']));

            /*
             * {% if user_access('purchase','accountant','poweruser') %}
             *   ...
             * {% endif %}
             */

            // with an extension
            $twig->addExtension(new YourTwigExtension($logon));

            /*
             * {% if checkUserAccess('purchase','accountant','poweruser') %}
             *   ...
             * {% endif %}
             */
        }

        echo $twig->render($template, $args);
    }
}

Note: You obviously only need to choose one method of adding the functionality. I'd suggest to go for the extension. This way, later additions can happen in the same place, without a need to change the wiring up code.