Note:

If you want to create a new page for developers, you should create it on the Moodle Developer Resource site.

User:Frédéric Massart: Difference between revisions

From MoodleDocs
(Edits)
No edit summary
(21 intermediate revisions by the same user not shown)
Line 1: Line 1:
= Moodle Development Kit =
{{Moodle 3.3}}If you are creating [[Persistent|persistent]] entries from forms, we've got something for you. You can use the class ''core\form\persistent'' as a base for your form instead of ''moodleform''. Our persistent form class comes handy tools, such as automatic validation.


Every developer creates simple tools to avoid repeating cumbersome and/or boring tasks, and that is precisely why MDK has been created: to pack all those useful tools in a portable way across systems. Initially developed in Bash, the project moved to Python to avoid dealing with inconsistencies between Unix platforms, and eventually to support Windows.
''Note: for more information about forms themselves, [[:Category:Formslib|head this way]].''


== Key concept ==
== Linking to a persistent ==


The most important concept of MDK is that it works with Moodle instances. An instance of Moodle is a directory in which you have checked out a particular version together with a database using a specific database engine. This means that if you want to work on Moodle 2.3 and 2.4, using both MySQL and PostgreSQL, you will have four separate instance directories. This choice was made because it is the safest, clearest, and most straightforward solution.  
In order for the form class to know what persistent we'll be dealing with, we must declare the ''protected static $persistentclass'' variable. The latter contains the fully qualified name of the persistent class.


== Typical workflows using MDK ==
<code php>
/** @var string Persistent class name. */
protected static $persistentclass = 'example\\status';
</code>


To discover what MDK can do for you, here are a few common tasks it can achieve.
== Defining the form fields ==


=== Installing a new instance ===
Unfortunately this is not automatically done for us, so let's add our fields to the ''definition()'' method like you would do for any form.


Say we need to create a new instance of Moodle 2.4 and install it with PostgreSQL. We also want the instance to be ready for development with appropriate config settings. We also want to create a bunch of user accounts.
<code php>
/**
* Define the form.
*/
public function definition() {
    $mform = $this->_form;


mdk create -i -v 24 -e pgsql -r dev users
    // User ID.
    $mform->addElement('hidden', 'userid');
    $mform->setType('userid', PARAM_INT);
    $mform->setConstant('userid', $this->_customdata['userid']);


This is equivalent to doing:
    // Message.
    $mform->addElement('editor', 'message', 'Message');
    $mform->setType('message', PARAM_RAW);


mkdir /dir/to/stable_24/moodle
    // Location.
mkdir /dir/to/stable_24/moodledata
    $mform->addElement('text', 'location', 'Location');
ln -s /dir/to/stable_24/moodle /var/www/stable_24
    $mform->setType('location', PARAM_ALPHANUMEXT);
git clone git://git.moodle.org/moodle.git /dir/to/moodle
cd /dir/to/stable_24/moodle
php admin/cli/install.php --wwwroot="http://localhost/stable_24" --dataroot=/dir/to/stable_24/moodledata --dbtype=pgsql --dbname=stable24 --dbuser=root --dbpass=root --dbhost=localhost --fullname="Stable 24 PostgreSQL" --shortname=stable_24 --adminuser=admin --adminpass=test --allow-unstable --agree-license --non-interactive
vim config.php
# Add the following settings:
# - $CFG->sessioncookiepath: /stable_24/
# - $CFG->debug: DEBUG_DEVELOPER
# - $CFG->debugdisplay: 1
# - $CFG->passwordpolicy: 0
# - $CFG->perfdebug: 15
# - $CFG->debugpageinfo: 1
# - $CFG->allowthemechangeonurl: 1
# - $CFG->cachejs: 0
# - $CFG->yuicomboloading: 0
# Include FirePHP Core
# Login to Moodle
# Create 10 students, 3 teachers and 3 managers


=== Fixing an issue ===
    $this->add_action_buttons();
}
</code>


mdk fix 12345
All of this is pretty standard, except for the ''userid''. When creating a new 'status', we do not want our users to be in control of this value. Therefore we define it as a hidden value which we lock (using ''setConstant'') to the value we created our form with. All the mandatory fields (without a default value) of the persistent need to be added to the form. If your users cannot change their values, then they must be hidden and locked with ''setConstant''.
# Committing your patch
mdk push -t


This is equivalent to doing:
Also note that the ''id'' property is not included. It is not required, nor recommended, to add it to your fields as it will be handled automatically.


git branch --track MDL-12345-24 origin/MOODLE_24_STABLE
== Using the form ==
git checkout MDL-12345-24
# Committing your patch
git push github MDL-12345-24
# Editing the tracker issue to add
# - Git repository URL
# - Git branch for 2.4
# - Git compare URL for 2.4


=== Peer reviewing a patch ===
When instantiating the form, there are two little things that you need to pay attention to.


Here we want to pull a patch from a tracker issue into a new testing branch, and then run the PHPUnit tests.
Firstly you should always pass the URL of the current page, including its query parameters. We need this to be able to display the form with its validation errors without affecting anything else.


mdk pull 12345 -t
Secondly, the persistent instance must be provided to the form through the custom data. That persistent instance will be used to populate the form with initial data, typically when you are editing an object. When you don't have a persistent instance yet, probably because your user will be creating a new one, then simply pass null.
mdk phpunit -r


This is the equivalent to doing:
<code php>
$customdata = [
    'persistent' => $persistent,  // An instance, or null.
    'userid' => $USER->id        // For the hidden userid field.
];
$form = new status_form($PAGE->url->out(false), $customdata);
</code>


cd /dir/to/stable_24/moodle
Just like any other form, we will be using ''get_data()'' to validate the form. The only difference is that to determine whether we are editing an object, or creating a new one, we will check if the ''id'' value was returned to us. The persistent form will return the ID value from the persistent we gave it. Then it's up to you to decide how to apply the data, most likely you will defer the logic to another part of your code, one that ensures that all capability checks are fulfilled.  
git branch --tracker MDL-12345-24-test MOODLE_24_STABLE
git checkout MDL-12345-24-test
git pull git://github.org/Someone/moodle.git MDL-12345-24
# And now the PHPUnit part
mkdir /dir/to/stable_24/moodledata_phpu
vim config.php
# To add
# - $CFG->phpunit_dataroot = '/dir/to/stable_24/moodledata_phpu';
# - $CFG->phpunit_prefix = 'phpu_';
php admin/tool/phpunit/cli/init.php
phpunit


=== Upgrading the instances ===
<code php>
// Get the data. This ensures that the form was validated.
if (($data = $form->get_data())) {


Say we need to upgrade our instances, as a new weekly release has just been made available.
    if (empty($data->id)) {
        // If we don't have an ID, we know that we must create a new record.
mdk upgrade -u --all
        // Call your API to create a new persistent from this data.
        // Or, do the following if you don't want capability checks (discouraged).
        $persistent = new status(null, $data);
        $persistent->create();
    } else {
        // We had an ID, this means that we are going to update a record.
        // Call your API to update the persistent from the data.
        // Or, do the following if you don't want capability checks (discouraged).
        $persistent->from_record($data);
        $persistent->update();
    }


This is the equivalent to doing:
    // We are done, so let's redirect somewhere.
    redirect(new moodle_url('/'));
}
</code>


# For each instance of Moodle...
== Additional validation ==
cd /dir/to/stable_24/moodle
git checkout MOODLE_24_STABLE
git fetch origin
git reset --hard origin/MOODLE_24_STABLE
php admin/cli/upgrade.php --non-interactive --allow-unstable


== Features ==
There are times when the built-in validation of the persistent is not enough. Usually you would use the method ''validation()'', but as the form persistent class does some extra stuff to make it easier for you, you must use the ''extra_validation()'' method. The latter works almost just like the ''validation()'' one.


For a complete list of the commands MDK has to offer, read through the [https://github.com/FMCorz/mdk#command-list MDK README file]. For more detail about each command, simply run them with the flag '--help'.
<code php>
/**
* Extra validation.
*
* @param  stdClass $data Data to validate.
* @param  array $files Array of files.
* @param  array $errors Currently reported errors.
* @return array of additional errors, or overridden errors.
*/
protected function extra_validation($data, $files, array &$errors) {
    $newerrors = array();


== Installation ==
    if ($data->location === 'SFO') {
        $newerrors['location'] = 'San-Francisco Airport is not accepted from the form.';
    }


=== Debian/Ubuntu ===
    return $newerrors;
}
</code>


sudo apt-add-repository ppa:2x1cq-fred-7nqa6/ppa
The typical additional validation will return an array of errors, those will override any previously defined errors. Sometimes, though rarely, you will need to remove previously reported errors, hence the reference to ''$errors'' given, which you can modify directly. Do not abuse it though, this should only be used when you have no other choice.
sudo apt-get update
sudo apt-get install moodle-sdk
sudo mdk init
# The next line prevents your from logging out and in again.
sudo su `whoami`


=== Mac OS ===
== Foreign fields ==


brew tap danpoltawski/homebrew-mdk
By default, the form class tries to be smart at detecting foreign fields such as the submit button. Failure to do so will cause troubles during validation, or when getting the data. So when your form becomes more complex, if it includes more submit buttons, or when it deals with other fields, for example file managers, we must indicate it.
brew install moodle-sdk
sudo mdk init


Installing MDK on Mac requires Homebrew; to install it use the following command.
=== Fields to ignore completely ===


ruby -e "$(curl -fsSL https://raw.github.com/mxcl/homebrew/go)"
The fields to remove are never validated and they are not returned when calling ''get_data()''. By default the submit button is added to this list so that when we call ''get_data()'' we only get the persistent-related fields. To remove more fields, re-declare the ''protected static $fieldstoremove'' class variable.


=== Windows ===
<code php>
/** @var array Fields to remove when getting the final data. */
protected static $fieldstoremove = array('submitbutton', 'areyouhappy');
</code>


MDK has been tested on Windows, but it doesn't work yet. It will possibly work after a few minor bug fixes.
Do not forget to add the ''submitbutton'' back in there.


=== Manual ===
=== Fields to validate ===


Please follow the instructions from the [https://github.com/FMCorz/mdk#manual-installation README file].
What about when we have a ''legit'' field but it does not belong to the persistent? We still want to validate it ourselves, but we don't want it to be validated by the persistent as it will cause an error. In that case we define it in the ''protected static $foreignfields'' class variable.


== The MDK Suite ==
<code php>
/** @var array Fields to remove from the persistent validation. */
protected static $foreignfields = array('updatedelay');
</code>


Some other tools have been developed using the name MDK as they are considered as part of the development kit but are often mistaken with the ''real'' MDK. The ''real'' MDK is the command line tool described above.
Now the persistent will not validate this field, and we will get the ''updatedelay'' value when we call ''get_data()''. Just don't forget to remove it before you feed the data to your persistent.


=== MDK Browser Extension ===
<code php>
if (($data = $form->get_data())) {
    $updatedelay = $data->updatedelay;
    unset($data->updatedelay);
    $newpersistent = new status(0, $data);
}
</code>


Available for both [https://addons.mozilla.org/en-US/firefox/addon/mdk-browser-extension/ Firefox] and [https://chrome.google.com/webstore/detail/mdk-browser-extension/iadpkkojcdoflinpncpkbonnhdlaicnc Chrome], this is a browser extension that allows quick access to useful user-scripts. The scripts add functionality to Moodle.org, Moodle Tracker and your Moodle instances. You can find more information about it on its [https://github.com/danpoltawski/userscripts-moodle public repository].
This method is particularily useful when dealing with file managers.


=== MDK Authentication ===
== Examples ==


This is an authentication plugin for Moodle 2.x, which not only creates the user if it does not exist in the database yet, but also enrols it as a student, teacher or manager in all the available courses. More information about this plugin is available on the [https://github.com/FMCorz/moodle-auth_mdk public repository].
=== Minimalist ===
 
<code php>
class status_form extends \core\form\persistent {
   
    /** @var string Persistent class name. */
    protected static $persistentclass = 'example\\status';
 
    /**
    * Define the form.
    */
    public function definition() {
        $mform = $this->_form;
 
        // User ID.
        $mform->addElement('hidden', 'userid');
        $mform->setType('userid', PARAM_INT);
        $mform->setConstant('userid', $this->_customdata['userid']);
 
        // Message.
        $mform->addElement('editor', 'message', 'Message');
        $mform->setType('message', PARAM_RAW);
 
        // Location.
        $mform->addElement('text', 'location', 'Location');
        $mform->setType('location', PARAM_ALPHANUMEXT);
 
        $this->add_action_buttons();
    }
 
}
</code>
 
=== More advanced ===
 
<code php>
class status_form extends \core\form\persistent {
 
    /** @var string Persistent class name. */
    protected static $persistentclass = 'example\\status';
 
    /** @var array Fields to remove when getting the final data. */
    protected static $fieldstoremove = array('submitbutton', 'areyouhappy');
 
    /** @var array Fields to remove from the persistent validation. */
    protected static $foreignfields = array('updatedelay');
 
    /**
    * Define the form.
    */
    public function definition() {
        $mform = $this->_form;
 
        // User ID.
        $mform->addElement('hidden', 'userid');
        $mform->setType('userid', PARAM_INT);
        $mform->setConstant('userid', $this->_customdata['userid']);
 
        // Message.
        $mform->addElement('editor', 'message', 'Message');
        $mform->setType('message', PARAM_RAW);
 
        // Location.
        $mform->addElement('text', 'location', 'Location');
        $mform->setType('location', PARAM_ALPHANUMEXT);
 
        // Status update delay.
        $mform->addElement('duration', 'updatedelay', 'Status update delay');
 
        // Are you happy?
        $mform->addElement('selectyesno', 'areyouhappy', 'Are you happy?');
 
        $this->add_action_buttons();
    }
 
    /**
    * Extra validation.
    *
    * @param  stdClass $data Data to validate.
    * @param  array $files Array of files.
    * @param  array $errors Currently reported errors.
    * @return array of additional errors, or overridden errors.
    */
    protected function extra_validation($data, $files, array &$errors) {
        $newerrors = array();
 
        if ($data->location === 'SFO') {
            $newerrors['location'] = 'San-Francisco Airport is not accepted from the form.';
        }
 
        return $newerrors;
    }
}
</code>
 
=== Using the form ===
 
Consider the following code to be a page you users will access at '/example.php'.
 
<code php>
require 'config.php';
 
// Check if we go an ID.
$id = optional_param('id', null, PARAM_INT);
 
// Set the PAGE URL (and mandatory context). Note the ID being recorded, this is important.
$PAGE->set_context(context_system::instance());
$PAGE->set_url(new moodle_url('/example.php', ['id' => $id]));
 
// Instantiate a persistent object if we received an ID. Typically receiving an ID
// means that we are going to be updating an object rather than creating a new one.
$persistent = null;
if (!empty($id)) {
    $persistent = new status($id);
}
 
// Create the form instance. We need to use the current URL and the custom data.
$customdata = [
    'persistent' => $persistent,
    'userid' => $USER->id        // For the hidden userid field.
];
$form = new status_form($PAGE->url->out(false), $customdata);
 
// Get the data. This ensures that the form was validated.
if (($data = $form->get_data())) {
 
    if (empty($data->id)) {
        // If we don't have an ID, we know that we must create a new record.
        // Call your API to create a new persistent from this data.
        // Or, do the following if you don't want capability checks (discouraged).
        $persistent = new status(null, $data);
        $persistent->create();
    } else {
        // We had an ID, this means that we are going to update a record.
        // Call your API to update the persistent from the data.
        // Or, do the following if you don't want capability checks (discouraged).
        $persistent->from_record($data);
        $persistent->update();
    }
 
    // We are done, so let's redirect somewhere.
    redirect(new moodle_url('/'));
}
 
// Display the mandatory header and footer.
// And display the form, and its validation errors if there are any.
echo $OUTPUT->header();
$form->display();
echo $OUTPUT->footer();
</code>

Revision as of 07:58, 16 December 2016

Moodle 3.3 If you are creating persistent entries from forms, we've got something for you. You can use the class core\form\persistent as a base for your form instead of moodleform. Our persistent form class comes handy tools, such as automatic validation.

Note: for more information about forms themselves, head this way.

Linking to a persistent

In order for the form class to know what persistent we'll be dealing with, we must declare the protected static $persistentclass variable. The latter contains the fully qualified name of the persistent class.

/** @var string Persistent class name. */ protected static $persistentclass = 'example\\status';

Defining the form fields

Unfortunately this is not automatically done for us, so let's add our fields to the definition() method like you would do for any form.

/**

* Define the form.
*/

public function definition() {

   $mform = $this->_form;
   // User ID.
   $mform->addElement('hidden', 'userid');
   $mform->setType('userid', PARAM_INT);
   $mform->setConstant('userid', $this->_customdata['userid']);
   // Message.
   $mform->addElement('editor', 'message', 'Message');
   $mform->setType('message', PARAM_RAW);
   // Location.
   $mform->addElement('text', 'location', 'Location');
   $mform->setType('location', PARAM_ALPHANUMEXT);
   $this->add_action_buttons();

}

All of this is pretty standard, except for the userid. When creating a new 'status', we do not want our users to be in control of this value. Therefore we define it as a hidden value which we lock (using setConstant) to the value we created our form with. All the mandatory fields (without a default value) of the persistent need to be added to the form. If your users cannot change their values, then they must be hidden and locked with setConstant.

Also note that the id property is not included. It is not required, nor recommended, to add it to your fields as it will be handled automatically.

Using the form

When instantiating the form, there are two little things that you need to pay attention to.

Firstly you should always pass the URL of the current page, including its query parameters. We need this to be able to display the form with its validation errors without affecting anything else.

Secondly, the persistent instance must be provided to the form through the custom data. That persistent instance will be used to populate the form with initial data, typically when you are editing an object. When you don't have a persistent instance yet, probably because your user will be creating a new one, then simply pass null.

$customdata = [

   'persistent' => $persistent,  // An instance, or null.
   'userid' => $USER->id         // For the hidden userid field.

]; $form = new status_form($PAGE->url->out(false), $customdata);

Just like any other form, we will be using get_data() to validate the form. The only difference is that to determine whether we are editing an object, or creating a new one, we will check if the id value was returned to us. The persistent form will return the ID value from the persistent we gave it. Then it's up to you to decide how to apply the data, most likely you will defer the logic to another part of your code, one that ensures that all capability checks are fulfilled.

// Get the data. This ensures that the form was validated. if (($data = $form->get_data())) {

   if (empty($data->id)) {
       // If we don't have an ID, we know that we must create a new record.
       // Call your API to create a new persistent from this data.
       // Or, do the following if you don't want capability checks (discouraged).
       $persistent = new status(null, $data);
       $persistent->create();
   } else {
       // We had an ID, this means that we are going to update a record.
       // Call your API to update the persistent from the data.
       // Or, do the following if you don't want capability checks (discouraged).
       $persistent->from_record($data);
       $persistent->update();
   }
   // We are done, so let's redirect somewhere.
   redirect(new moodle_url('/'));

}

Additional validation

There are times when the built-in validation of the persistent is not enough. Usually you would use the method validation(), but as the form persistent class does some extra stuff to make it easier for you, you must use the extra_validation() method. The latter works almost just like the validation() one.

/**

* Extra validation.
*
* @param  stdClass $data Data to validate.
* @param  array $files Array of files.
* @param  array $errors Currently reported errors.
* @return array of additional errors, or overridden errors.
*/

protected function extra_validation($data, $files, array &$errors) {

   $newerrors = array();
   if ($data->location === 'SFO') {
       $newerrors['location'] = 'San-Francisco Airport is not accepted from the form.';
   }
   return $newerrors;

}

The typical additional validation will return an array of errors, those will override any previously defined errors. Sometimes, though rarely, you will need to remove previously reported errors, hence the reference to $errors given, which you can modify directly. Do not abuse it though, this should only be used when you have no other choice.

Foreign fields

By default, the form class tries to be smart at detecting foreign fields such as the submit button. Failure to do so will cause troubles during validation, or when getting the data. So when your form becomes more complex, if it includes more submit buttons, or when it deals with other fields, for example file managers, we must indicate it.

Fields to ignore completely

The fields to remove are never validated and they are not returned when calling get_data(). By default the submit button is added to this list so that when we call get_data() we only get the persistent-related fields. To remove more fields, re-declare the protected static $fieldstoremove class variable.

/** @var array Fields to remove when getting the final data. */ protected static $fieldstoremove = array('submitbutton', 'areyouhappy');

Do not forget to add the submitbutton back in there.

Fields to validate

What about when we have a legit field but it does not belong to the persistent? We still want to validate it ourselves, but we don't want it to be validated by the persistent as it will cause an error. In that case we define it in the protected static $foreignfields class variable.

/** @var array Fields to remove from the persistent validation. */ protected static $foreignfields = array('updatedelay');

Now the persistent will not validate this field, and we will get the updatedelay value when we call get_data(). Just don't forget to remove it before you feed the data to your persistent.

if (($data = $form->get_data())) {

   $updatedelay = $data->updatedelay;
   unset($data->updatedelay);
   $newpersistent = new status(0, $data);

}

This method is particularily useful when dealing with file managers.

Examples

Minimalist

class status_form extends \core\form\persistent {

   /** @var string Persistent class name. */
   protected static $persistentclass = 'example\\status';
   /**
    * Define the form.
    */
   public function definition() {
       $mform = $this->_form;
       // User ID.
       $mform->addElement('hidden', 'userid');
       $mform->setType('userid', PARAM_INT);
       $mform->setConstant('userid', $this->_customdata['userid']);
       // Message.
       $mform->addElement('editor', 'message', 'Message');
       $mform->setType('message', PARAM_RAW);
       // Location.
       $mform->addElement('text', 'location', 'Location');
       $mform->setType('location', PARAM_ALPHANUMEXT);
       $this->add_action_buttons();
   }

}

More advanced

class status_form extends \core\form\persistent {

   /** @var string Persistent class name. */
   protected static $persistentclass = 'example\\status';
   /** @var array Fields to remove when getting the final data. */
   protected static $fieldstoremove = array('submitbutton', 'areyouhappy');
   /** @var array Fields to remove from the persistent validation. */
   protected static $foreignfields = array('updatedelay');
   /**
    * Define the form.
    */
   public function definition() {
       $mform = $this->_form;
       // User ID.
       $mform->addElement('hidden', 'userid');
       $mform->setType('userid', PARAM_INT);
       $mform->setConstant('userid', $this->_customdata['userid']);
       // Message.
       $mform->addElement('editor', 'message', 'Message');
       $mform->setType('message', PARAM_RAW);
       // Location.
       $mform->addElement('text', 'location', 'Location');
       $mform->setType('location', PARAM_ALPHANUMEXT);
       // Status update delay.
       $mform->addElement('duration', 'updatedelay', 'Status update delay');
       // Are you happy?
       $mform->addElement('selectyesno', 'areyouhappy', 'Are you happy?');
       $this->add_action_buttons();
   }
   /**
    * Extra validation.
    *
    * @param  stdClass $data Data to validate.
    * @param  array $files Array of files.
    * @param  array $errors Currently reported errors.
    * @return array of additional errors, or overridden errors.
    */
   protected function extra_validation($data, $files, array &$errors) {
       $newerrors = array();
       if ($data->location === 'SFO') {
           $newerrors['location'] = 'San-Francisco Airport is not accepted from the form.';
       }
       return $newerrors;
   }

}

Using the form

Consider the following code to be a page you users will access at '/example.php'.

require 'config.php';

// Check if we go an ID. $id = optional_param('id', null, PARAM_INT);

// Set the PAGE URL (and mandatory context). Note the ID being recorded, this is important. $PAGE->set_context(context_system::instance()); $PAGE->set_url(new moodle_url('/example.php', ['id' => $id]));

// Instantiate a persistent object if we received an ID. Typically receiving an ID // means that we are going to be updating an object rather than creating a new one. $persistent = null; if (!empty($id)) {

   $persistent = new status($id);

}

// Create the form instance. We need to use the current URL and the custom data. $customdata = [

   'persistent' => $persistent,
   'userid' => $USER->id         // For the hidden userid field.

]; $form = new status_form($PAGE->url->out(false), $customdata);

// Get the data. This ensures that the form was validated. if (($data = $form->get_data())) {

   if (empty($data->id)) {
       // If we don't have an ID, we know that we must create a new record.
       // Call your API to create a new persistent from this data.
       // Or, do the following if you don't want capability checks (discouraged).
       $persistent = new status(null, $data);
       $persistent->create();
   } else {
       // We had an ID, this means that we are going to update a record.
       // Call your API to update the persistent from the data.
       // Or, do the following if you don't want capability checks (discouraged).
       $persistent->from_record($data);
       $persistent->update();
   }
   // We are done, so let's redirect somewhere.
   redirect(new moodle_url('/'));

}

// Display the mandatory header and footer. // And display the form, and its validation errors if there are any. echo $OUTPUT->header(); $form->display(); echo $OUTPUT->footer();