What or Why?

So some maniac has seen it appropriate to make me Lead and give me a team. Suddenly I am no longer just dabbling in uplifting co-workers and their professional development but it has become one of my core responsibilities.

While my mind was wandering I contemplated on the different ways that people approach work etc. I have found that there are two (albeit not the only ones) methods of approaching tasks/problems/etc. Those that simply know and do what must be done and those that don’t necessarily know what but understand why to a degree and derive the what from it.

Those that what are able respond perfectly to situations that they have learned to manage or have instructions for.

Those that why are imperfect, they don’t necessarily follow instructions well so much as assess the information they have gathered and decide what to do based on the various whys they have come to understand. This approach has its risks, the individual is essentially unable to know all the whys and so the approach is fundamentally inaccurate.

These fundamentally flawed individuals are ultimately extremely valuable because the approach makes the holy grail of business a possibility. Agility, flexibility, creativity.

Unfortunately the early years of individuals in their careers tends to punish those who ascribe to why and re-enforces the actions of what. And so all those that come to me are whats and I must determine those that can why and re-educate them.

New plugin! Advanced Config

I have started work on a new plugin for Moodle and it is now in alpha stages.

It allows for more advanced configuration management in Moodle (that sounds really boring). Despite how boring it sounds I am very excited about! It solves a problem that I have and hopefully it will solve some other people’s problems too.

Its main feature is that settings defined with it are inheritable and can be overridden in categories and the child categories in that category then inherit that new setting.

You can find out more at the github page for it: https://github.com/darrencocco/moodle-local_advancedconfig

Here is an image of those settings in one of my development instances:

Screenshot-2017-9-1 S1 Advanced Config Callista Grade Export

As you can see there is a little menu in the Administration block at the bottom of the Category menu that says Advanced Config. This leads to category context specific configuration page for one of our grade export tools.

Here is an example of a config file that defines those settings in particular:

<?php
namespace gradeexport_callista\settings;

use local_advancedconfig\model\basic_setting_definition;
use local_advancedconfig\model\setting_definition;
use local_advancedconfig\model\settings;
use local_advancedconfig\model\setting_definition\validate\param_generic;
use local_advancedconfig\model\setting_definition\input;
use local_advancedconfig\model\tree;

class basic_settings implements settings, tree {

    /** @var basic_settings */
    private static $instance = null;

    public static function get_instance() {
        if (is_null(self::$instance)) {
            self::$instance = new basic_settings();
        }
        return self::$instance;
    }

    /**
     * @return setting_definition[]
     */
    public function settings_defined() {
        return [
            'gradeexport_callista/adminnotificationemail' =&gt; new basic_setting_definition(
                'gradeexport_callista', 'adminnotificationemail',
                new param_generic(PARAM_TEXT),
                new input(input::TEXT), '',
                'gradeexport/callista:categoryadmin'),
            'gradeexport_callista/mainheader' =&gt; new basic_setting_definition(
                'gradeexport_callista', 'mainheader',
                new param_generic(PARAM_CLEANHTML),
                new input(input::HTML), '',
                'gradeexport/callista:categoryadmin'),
            'gradeexport_callista/confirmation' =&gt; new basic_setting_definition(
                'gradeexport_callista', 'confirmation',
                new param_generic(PARAM_CLEANHTML),
                new input(input::HTML), '',
                'gradeexport/callista:categoryadmin'),
        ];
    }

    /**
     * @return string
     */
    public function plugin_name() {
        return 'gradeexport_callista';
    }

    /**
     * @return branch[]
     */
    public function get_branches() {
        return [
            new tree\leaf\leaf_settings( 'gradeexports', 'gradeexportcallista',
                new \lang_string( 'pluginname', 'gradeexport_callista'),
                ['moodle/site:config', 'gradeexport/callista:categoryadmin'],[
                    new \admin_setting_configtext(
                        'gradeexport_callista/adminnotificationemail',
                        new \lang_string('adminnotificationemail', 'gradeexport_callista'),
                        new \lang_string( 'adminnotificationemail_help', 'gradeexport_callista'),
                        ''),
                    new \admin_setting_confightmleditor(
                        'gradeexport_callista/mainheader',
                        new \lang_string( 'mainheader', 'gradeexport_callista'),
                        new \lang_string( 'mainheader_desc', 'gradeexport_callista'),
                        ''),
                    new \admin_setting_confightmleditor(
                        'gradeexport_callista/confirmation',
                        new \lang_string( 'confirmation', 'gradeexport_callista'),
                        new \lang_string( 'confirmation_desc','gradeexport_callista'),
                        ''
                    ),
                ]),
        ];
    }
}

Violating OOP Encapsulation in PHP

So you NEED to access a protected or private variable in an instantiated object and you can’t just extend the class because of some sort of hard coded labyrinthine factory class made from thousands of lines of code.  Oh and for good measure it needs to be a installed as a plugin so modifying the base code is not possible.

Well sucks to be you!

Fortunately as of PHP 5.4 you can now break the OOP encapsulation protection and awaken Cthulhu from his sleep in Ry’leh… sort of.

Using those pretty things called Closures you can now bind a function into the scope of an instantiated object and access all those protected and private variables that the other developers worked so hard to keep away from you.

class A {
  private $secret;
  protected $hidden;
  function __constructor ($var1, $var2) {
    $this->secret = $var1;
    $this->hidden = $var2;
  }

  function showSecrets() {
    echo $this->secret;
  }
}

$a = new A("inside the llama\n", "are grand machinations\n");
$a->showSecrets();
$func1 = function($newSecret) {
  $this->secret = $newSecret;
};
//bind a new function into the scope of the
//instantiated object
$a->newFunc = $func1->bindTo($a, $a);
//Using the __invoke magic function because the objects
//doesn't recognise that the function is a callable
//because it isn't in the method table
$a->newFunc->__invoke("outside the llama\n");
$a->showSecrets();

$func2 = function($newHidden) {
  echo $this->hidden;
  $this->hidden = $newHidden;
  echo $this->hidden;
};

$func3 = function() {
  //Calling a method that doesn't even exist yet.
  $this->oldTownFunk->__invoke("get the func\n");
};
//Bind it to oldTownFunk just because we can.
$a->oldTownFunk = $func2->bindTo($a, $a);
$a->func3 = $func3->bindTo($a, $a);
//Summon Cthulhu
$a->func3->__invoke();

print_r($a);

Here is what the console outputs:

inside the llama
outside the llama
are grand machinations
get the func
A Object
(
    [secret:A:private] => outside the llama
    [hidden:protected] => get the func
    [newFunc] => Closure Object
        (
            [this] => A Object
 *RECURSION*
            [parameter] => Array
                (
                    [$newSecret] => 
                )
        )
    [oldTownFunk] => Closure Object
        (
            [this] => A Object
 *RECURSION*
            [parameter] => Array
                (
                    [$newHidden] => 
                )
        )
    [func3] => Closure Object
        (
            [this] => A Object
 *RECURSION*
        )
)

So what is happening? Well when you call bindTo on a Closure you get a clone of it with the $this of the function set to whatever object you passed in. The function doesn’t actually exist in the method table but as its independent object. This returned Closure can be called after it is bound and will be executed in the scope defined. But if you want to chain multiple methods(e.g. in a foreach loop etc) then you can set a property of the instantiated object to the new Closure and call it from inside another Closure using the magical __invoke function while passing the variables you would pass to the Closure through it.

Warning using this may sacrifice your first born to Yog-Sothoth or break everything in your program or library so use with caution.

Moodle administration interfaces

Adding settings interfaces to Moodle is a little cryptic.

From my short foray into doing so I have determined that the settings are defined in the settings.php file in the base directory of your module/plugin.

Admin menu entries

Adding a new entry into the “Site Administration” menu is done by instantiating a new settings page object and attaching it to the tree through the global $ADMIN object.

// Instantiate new settings page with the internal name 'local_examplecom_plugin'
// and the menu display name 'Example.com Plugin'
$settings = new admin_settingpage('local_examplecom_plugin', 'Example.com Plugin');
// Add the new settings page to the settings tree under the 'localplugins' branch
// which is located in 'Site administration->Plugins->Local plugins'
$ADMIN->add('localplugins', $settings);
// Add controls here

For local plugins the tree location value is ‘localplugins’ but other values can be substituted and they are not consistent as the below snippet shows:

'appearance' -> 'Site administration->Appearance'
'themes' -> 'Site administration->Appearance->Themes'
'modsettings' -> 'Site administration->Plugins->Activity modules'
'authsettings' -> 'Site administration->Plugins->Authentication'
'enrolments' -> 'Site administration->Plugins->Enrolments'

Admin menu categories

You can also define new collapsible menu items by instantiating a new admin_category object and then use its internal name to attach items underneath it.

$ADMIN->add('localplugin', new admin_category('internal_category_name', 'Example.com'));
$settings = new admin_settingpage('local_examplecom_plugin_settings1', 'Some settings');
$ADMIN->add('internal_category_name', $settings);
// Add controls here
$settings = new admin_settingpage('local_examplecom_plugin_settings2', 'Some more settings');
$ADMIN->add('internal_category_name', $settings);
// Add controls here

Settings form controls

There are a variety of different controls that can be added but they all follow a fairly common format:

new admin_setting_config*($setting_internal_name, $setting_title, $setting_description, $default_value, $validation_type);
*can be any of a variety of control types including but not limited to htmleditor, text, checkbox etc.

$setting_internal_name and $setting_title seem to be the only mandatory ones.
$setting_internal_name should be either ‘plugin_name/setting_name’ for plugin only settings and just ‘setting_name’ for global $CFG related settings.

Adding a control to the form requires that you instantiate the control and add it to the admin_settingpage by calling the add method:

// See previous examples for where this fits in.
// Add controls here
$settings->add(new admin_setting_confightmleditor('local_examplecom/setting_one',
    'Setting title', 'Setting one description', 'this is the default value'));
$settings->add(new admin_setting_confightmleditor('local_examplecom/eats_grass',
    'Grass preference', 'What type of grass does it prefer'));
$settings->add(new admin_setting_confightmleditor('a_global_setting',
    'Not plugin specific', 'This is accessed through $CFG->a_global_setting'));