HackerOne users: Testing against this community violates our program's Terms of Service and will result in your bounty being denied.

How to override functions (Vanilla 3.3)?

I've read this tutorial https://docs.vanillaforums.com/developer/addons/function-overrides/, and am trying to override the checkPermission method that's defined in <vanilla>/library/core/functions.general.php.

This is basically what I've tried:

<?php

class PlainPlatformIntegrationPlugin extends Gdn_Plugin
{
    // Here are my own methods that handle events etc., everything works nicely...
}

if (!function_exists('checkPermission')) {
    // We never get here :(
    function checkPermission()
    {
        echo 'Checking';
    }
}

The problem seems to be (I've used a debugger) that the functions inside functions.general.php are loaded before my override, so !function_exists('checkPermission') is never truthy in my implementation. Is there something wrong with my code/configs?

Comments

  • I think I've done my override attempt as instructed in the documentation. Have I misunderstood something or is the documentation outdated?

  • R_JR_J Ex-Fanboy Munich Admin

    You did it right, but some functions are loaded so early that you cannot override them in a plugin. You need to put them in the bootstrap.before.pbp file. I have no keyboard here, so please use the forum search to find out more about that

  • R_JR_J Ex-Fanboy Munich Admin

    Now with more time:

    1. Vanillas index.php includes two files: environment.php and bootstrap.php
    2. The environment.php sets very basic information (the required php version, Vanillas version, defining constants etc) and it also includes the /conf/bootstrap.before.php
    3. The bootstrap.php file includes functions.general.php and functions.render.php
    4. The framework is started after that

    Since the framework starts up after all those initial steps, you cannot override any function from functions.general or functions.render from within a plugin. You need to do that in the bootstrap.before file.

    But beware: just recently I tried to debug very strange behaviours in my own forum and in the end it turned out that I had a function overridden in that file that I totally forgot. Since it is not connected to a plugin, it is somewhat risky to use it. If you find better other ways to solve a problem, they most probably are better ways.


    You've said you want to override the checkPermission function. That function is not used everwhere. Since it is only a wrapper for Gdn::session()->checkPermission, sometimes that code is used instead of the function checkPermission. If you want to override a permission function, it should be bullet proof. Overriding only the checkPermission function is not safe.

    But why do you think you have to override it? What are you trying to achieve? I would assume creating a custom permission and working with that might be the better approach.

  • Thanks for the clarification @R_J . If at all possible maybe you or some other moderator/admin could update the docs with that information. I'm sure it would help others, as well.

    Yeah, the checkPermission function wasn't the right place for me to do anything. I was just trying to get the function override to work before taking a more closer look at what I was going to do next... 😁

    What I was/am trying to do is to call my own API on every page load, comment POST, discussion POST etc. to check if the logged in Vanilla user is still logged in to my API. If their session to my API is no longer valid I'm logging them out of Vanilla. Currently this check is done inside a base_Initialize_handler function in my plugin file, where I then filter some unnecessary events out so that each page load/POST action results in 1-2 checks to my API. It works, but the occasional 2 checks per request looks a bit ugly. Would you happen to have a better suggestion on where/how I should catch these sorts of events to make the session check to my API?

    Here's the function I'm talking about:

    public function base_Initialize_handler($caller, $args)
    {
        if (
            in_array($caller->ControllerName, [
                "notificationscontroller",
                "addoncachecontroller",
                "dashboardcontroller",
                "utilitycontroller"
            ])
            || in_array($caller->SelfUrl, [
                "dashboard/log/count/moderate",
                "post/comment2.json"
            ])
            || in_array($caller->Request->getPath(), [
                "/entry/signout",
                "/kvaak/kvaak"
            ])
        ) {
            // no need to validate these events, continuing...
            return;
        }
        $this->checkSession(); // checks the session with my API
    }
    


  • R_JR_J Ex-Fanboy Munich Admin

    If at all possible maybe you or some other moderator/admin could update the docs with that information. I'm sure it would help others, as well.

    The docs are generated from a public repo https://github.com/vanilla/docs, so you can update them if you like to. English is not my mothertongue so I shriek back from editing documentation.


    Would you happen to have a better suggestion on where/how I should catch these sorts of events to make the session check to my API?

    In the index.php file the dispathcer calls two different methods:

    $dispatcher->start();
    $dispatcher->dispatch();
    

    Both of which are firing events:

    • AppStartup
    • BeforeDispatch
    • BeforeAnalyzeRequest

    I would try it with the very first:

    public function gdn_dispatcher_appStartup_handler($sender) {
       if (isLoggedIn()) {
           return;
       }
    
       Gdn::session()->end();
    }
    


  • R_JR_J Ex-Fanboy Munich Admin

    Why don't you invalidate Vanilla users as soon as they log out from your system?

  • Thanks for the suggestions. However, all of those events seem to fire several times per request as well, so I'd end up building a similar IF structure than in my current implementation if I were to use e.g. gdn_dispatcher_appStartup_handler. 😕

    I do invalidate Vanilla users when they log out of my system, but I also want to make sure an active session to my API exists before letting them do anything if, for some reason, they are logged in to Vanilla and not my API.

  • R_JR_J Ex-Fanboy Munich Admin

    It is only fired once per request, but there is more than one request per page view - somehow...

    I just threw together this:

       public function gdn_dispatcher_appStartup_handler($sender) {
           $request = Gdn::request();
           Gdn::set('debug_'.microtime(), $request->getMethod().': '.$request->getPath());
       }
    

    The result of this and clicking around a little bit is that:

    Each of this requests is valid for checking your API. But if you want to, you can cache the result of your checks for 5 seconds or so

       public function gdn_dispatcher_appStartup_handler($sender) {
           $userID = Gdn::session()->UserID;
           if ($userID == 0) {
               // User not logged in => nothing to do.
               return;
           }
    
           // Get logged in information from cache.
           $cacheKey = 'ExternallyLoggedIn_'.$userID;
           $hasValidExternalSession = Gdn::cache()->get($cacheKey);
    
           if ($hasValidExternalSession == Gdn_Cache::CACHEOP_FAILURE) {
               // Do your check
               $hasValidExternalSession = theExternalCheck();
               Gdn::cache()->store(
                   $cacheKey,
                   $hasValidExternalSession,
                   [Gdn_Cache::FEATURE_EXPIRY, 5]
               );
           }
    
           if (!$hasValidExternalSession) {
               Gdn::session()->end();
           }
       }
    


  • @R_J thanks a lot, that's an awesome idea and a big improvement to my current implementation. Will definitely use that. 😊

  • Hmm, I tried saving $hasValidExternalSession to the cache using the Gdn::cache()->store function. And the data seems to go to the cache, but it is wiped empty on the next request that fires, no matter how long an expiry time I set. Something wrong with my app config?

  • R_JR_J Ex-Fanboy Munich Admin

    Hopefully I do not have suggested something that is not possible for you. Do you have a server with memcached? If yes, add that to your config.php

    // Cache
    $Configuration['Cache']['Enabled'] = true;
    $Configuration['Cache']['Method'] = 'memcached';
    $Configuration['Cache']['Memcached']['Store'] = array (
     0 => 'localhost:11211',
    );
    


    Otherwise you only have "DirtyCache" which is only a fake cache...

  • Ah, ok. I seem to only have DirtyCache enabled. I'll have to look into enabling memcached. Thanks!

  • R_JR_J Ex-Fanboy Munich Admin

    If you own the server, it is not more than simply sudo apt install memcached php-memcached and you are ready to boost your forum!

Sign In or Register to comment.