Note: You are currently viewing documentation for Moodle 3.6. Up-to-date documentation for the latest stable version of Moodle is likely available here: Plugin skeleton generator.

Plugin skeleton generator: Difference between revisions

From MoodleDocs
No edit summary
m (Mudrd8mz moved page admin/tool/pluginskel/index to Plugin skeleton generator: Human friendly page title)
 
(One intermediate revision by one other user not shown)
Line 17: Line 17:


To generate the plugin skeleton by using the comand line interface:
To generate the plugin skeleton by using the comand line interface:
#Create a recipe file in the YAML format, either by writing it from scratch or by editing the template recipe located at "cli/example.yaml" (recommended)
#Create a recipe file in the YAML format, either by writing it from scratch or by editing the template recipe located at cli/example.yaml (recommended)
#Invoke the command line script at "cli/generate.php":
#Invoke the command line script at cli/generate.php:
   php cli/generate.php --recipe=recipe.yaml
   php cli/generate.php --recipe=recipe.yaml
This will generate the plugin in the [https://docs.moodle.org/dev/Plugin_types Moodle path directory] corresponding to the plugin type being generated.
This will generate the plugin in the [https://docs.moodle.org/dev/Plugin_types Moodle path directory] corresponding to the plugin type being generated.
Line 59: Line 59:
*'''--help, -h''' - prints a message about how to use the script.
*'''--help, -h''' - prints a message about how to use the script.
*'''--recipe=RECIPE.YAML''' - the location of the recipe file, as a relative or absolute path to the file. Mandatory.
*'''--recipe=RECIPE.YAML''' - the location of the recipe file, as a relative or absolute path to the file. Mandatory.
*'''--loglevel=LOGLEVEL''' - the verbosity of the log message. The default is WARNING. The plugin uses the Monolog logging library, for a full list of the different log levels please examine the file ''vendor/monolog/monolog/src/Monolog/Logger.php'' located in the plugin directory.
*'''--loglevel=LOGLEVEL''' - the verbosity of the log message. The default is WARNING. The plugin uses the Monolog logging library, for a full list of the different log levels please examine the file vendor/monolog/monolog/src/Monolog/Logger.php located in the plugin directory.
*'''--target-dir=DIR''' - the path to the directory where the plugin files will be generate (either relative or absolute). Optional. Inside DIR a directory with the component name will be created where all the plugin files will be placed. For example, when creating the plugin mod_newmodule:
*'''--target-dir=DIR''' - the path to the directory where the plugin files will be generate (either relative or absolute). Optional. Inside DIR a directory with the component name will be created where all the plugin files will be placed. For example, when creating the plugin mod_newmodule:
     php cli/generate.php --recipe=mod_newmodule_recipe.yaml --target-dir=/tmp
     php cli/generate.php --recipe=mod_newmodule_recipe.yaml --target-dir=/tmp
Line 82: Line 82:
#The last part consists of plugin specific features. All features that correspond to a plugin type will be under the ''<componenttype>_features'' section of the recipe.
#The last part consists of plugin specific features. All features that correspond to a plugin type will be under the ''<componenttype>_features'' section of the recipe.


Explanation about what the features do as well as links to relevant Moodle documentation for each of the above options can be found on the web interface, by click the help icons.
Explanation about what the features do as well as links to relevant Moodle documentation for each of the above options can be found on the web interface, by clicking the help icons.


==Adding new features==
==Adding new features==
Line 98: Line 98:
Note that the manager does no validation (if it exists, if it's writable, etc) on the $targetdirectory.
Note that the manager does no validation (if it exists, if it's writable, etc) on the $targetdirectory.


Each file content is generated from a template using Mustache. The Mustache templates are located in the ''skel'' directory. Responsible for making use of the Mustache engine to generate the files is a skeleton class located at ''classes/local/skel'', and each instance of the skeleton class will generate one file from one template. The template to generate the file from and the recipe data for the template are specified when the skeleton class is instantiated inside the manager function ''prepare_file_skeleton()''.
Each file content is generated from a template using [https://github.com/bobthecow/mustache.php/wiki Mustache]. The Mustache templates are located in the skel/ directory. Responsible for making use of the Mustache engine to generate the files are skeleton classes located at classes/local/skel, and each instance of the skeleton class will generate one file from one template. The template to generate the file from and the recipe data for the template are specified when the skeleton class is instantiated inside the manager function ''prepare_file_skeleton()''.


Now let's take a closer look at what happens when the function ''make()'' is called:
Now let's take a closer look at what happens when the function ''make()'' is called:
*The function ''prepare_files_skeletons()'' is invoked. This function will create the all the files' content and save it in the ''files'' class attribute. One entry in the ''files'' is composed of the file name and the instance of the skeleton class responsible for generating that file.
*The function ''prepare_files_skeletons()'' is invoked. This function will create the all the files' content and save it in the ''files'' class attribute. One entry in the ''files'' is composed of the file name and the instance of the skeleton class responsible for generating that file.
*Inside the ''prepare_files_skeletons()'' function, files are queued for generation (by invoking the function ''prepare_file_skeleton()'') based on the recipe options. To check if a given file should be generated there are two different functions: ''has_common_feature()'', which deals with the features common to all plugin types, and ''has_component_features()'' which checks whether a component specific feature is present.
*Inside the ''prepare_files_skeletons()'' function, files are queued for generation by invoking the function ''prepare_file_skeleton()'' based on the recipe options. To check if a given file should be generated there are two different functions: ''has_common_feature()'', which deals with the features common to all plugin types, and ''has_component_features()'' which checks whether a component specific feature is present.
*Once all the files have been added to the ''files'' manager attribute, the skeleton class method ''render()'' will be invoked for each skeleton class, and that will generated the content of the files.
*Once all the files have been added to the ''files'' list, the skeleton class method ''render()'' will be invoked for each skeleton class, and that will generate the content of each file.


===Adding new features===
===Adding new features===
Line 109: Line 109:


After this has been decided, the following steps are necessary:
After this has been decided, the following steps are necessary:
#Create a Mustache template (or more) for the feature. If the feature is a component feature, then the template should go in the ''skel/file/<componenttype>/'' directory, where ''componenttype'' is the [https://docs.moodle.org/dev/Plugin_types plugin type]. Otherwise, it should be in the ''skel/file/'' directory.
#Create a Mustache template (or more) for the feature. If the feature is a component feature, then the template should go in the skel/file/<componenttype>/ directory, where ''componenttype'' is the [https://docs.moodle.org/dev/Plugin_types plugin type]. Otherwise, it should be in the skel/file/ directory.
#Choose or create a new skeleton class to render the template. If creating a new skeleton class please extend one of the following three skeleton classes: php_internal_file (for files that are internal to Moodle and should not be accessible from the web page, they have the expression ''defined('MOODLE_INTERNAL') || die()'' at the beginning of the file), php_web_file (these are intended to be used from the web interface and include the config.php file), php_cli_file (these are command line scripts and define the constant 'CLI_SCRIPT' to be true) or txt_file (which is a common text file, like the LICENSE.md or README.md files).
#Choose or create a new skeleton class to render the template. If creating a new skeleton class please extend one of the following three skeleton classes: ''php_internal_file'' (for files that are internal to Moodle and should not be accessible from the web page, they have the expression ''defined('MOODLE_INTERNAL') || die()'' at the beginning of the file), ''php_web_file'' (these are intended to be used from the web interface and include the config.php file), ''php_cli_file'' (these are command line scripts and define the constant 'CLI_SCRIPT' to be true) or ''txt_file'' (which is a common text file, like the LICENSE.md or README.md files).
#Add code to the manager function ''prepare_plugin_skeleton()'' to make sure that the file(s) are queued for generation. Based on the feature type one of the functions ''has_component_feature()'' or ''has_common_feature()'' should return true if the feature is present in the recipe.
#Add code to the manager function ''prepare_plugin_skeleton()'' to make sure that the file(s) are queued for generation. Based on the feature type one of the functions ''has_component_feature()'' or ''has_common_feature()'' should return true if the feature is present in the recipe.
#(Optional, but highly recommended)Create [https://docs.moodle.org/dev/PHPUnit PHPUnit] tests for the new feature in the ''tests'' directory. You can use one of the existing tests as a template.
#(Optional, but highly recommended)Create [https://docs.moodle.org/dev/PHPUnit PHPUnit] tests for the new feature in the tests/ directory. You can use one of the existing tests as a template.
#To have the new feature present on the web interface:
#To have the new feature present on the web interface:
##The feature should be added to one (and not both) of the static functions: ''manager->get_component_variables($component)'' or ''manager->get_features_variables()''. Based on the code already present it should be self explanatory how that is done.
##The feature should be added to one (and not both) of the static functions: ''manager->get_component_variables($component)'' or ''manager->get_features_variables()''. Based on the code already present it should be self explanatory how that is done.
##Create the language strings for the new feature in the file ''lang/en/tool_pluginskel.php''. At the very least a feature will require two strings: the human readable name for the feature and the help message for that feature. It should be obvious about how to do that from the strings already present there.
##Create the language strings for the new feature in the file lang/en/tool_pluginskel.php. At the very least a feature will require two strings: the human readable name for the feature and the help message for that feature. It should be obvious about how to do that from the strings already present there.
#That's all.
#That's all.


Line 121: Line 121:
Here's an example of how to add a feature to the plugin.  
Here's an example of how to add a feature to the plugin.  


First, we decide on the feature type and name. We'll add a new common feature, ''myfeature'', which will be located in the ''features'' section of the recipe (meaning it will just toggle the creation of some file). If enabled, the file ''myfeature.php'' will generated in the directory ''mydirectory/''.
First, we decide on the feature type and name. We'll add a new common feature, "myfeature:, which will be located in the ''features'' section of the recipe (meaning it will just toggle the creation of some file). If enabled, the file myfeature.php will generated in the directory mydirectory/.


The bare-bones recipe should look something like this:
A simple recipe should look something like this:
<pre>
<pre>
component: local_test
component: local_test
Line 150: Line 150:
</pre>
</pre>


Next we create the template file at ''skel/file/myfeature.mustache''. We will use the skeleton class php_internal_file for generating this file, but we are free to use any other base class, or create our own.
Next we create the template file at skel/file/myfeature.mustache. We will use the skeleton class ''php_internal_file'' for generating this file, but we are free to use any other base class, or create our own.


Now we modify the manager class to make sure the file is generated properly:
Now we modify the manager class to make sure the file is generated properly:
*We make sure that the function ''has_common_feature()'' return true when the feature is specified in the recipe file. By default, if a feature is set to true and it resides in the ''features'' section of the recipe the function will return true. As this is our case, we don't have to modify ''has_common_feature()''.
*We make sure that the function ''has_common_feature()'' returns true when the feature is specified in the recipe file. By default, if a feature is set to true and it resides in the ''features'' section of the recipe the function will return true. As this is our case, we don't have to modify ''has_common_feature()''.
*Next we queue the file for generation inside the ''prepare_files_skeletons()'' function:
*Next we queue the file for generation inside the ''prepare_files_skeletons()'' function:
<code php>
<code php>
Line 164: Line 164:
}
}
</code>
</code>
*(Optional, but highly recommended) Create the PHPUnit test file ''tests/myfeature_test.php''. You can use one of the existing test files as an example (''readme_test.php'' is one of the simplest).
*(Optional, but highly recommended) Create the PHPUnit test file tests/myfeature_test.php. You can use one of the existing test files as an example (readme_test.php is one of the simplest).
*Now we have to make sure that we have access to this feature in the web interface. For that we modify the function ''get_features_variables()'':
*Now we have to make sure that we have access to this feature in the web interface. For that we modify the function ''get_features_variables()'':
<code php>
<code php>
Line 175: Line 175:
}
}
</code>
</code>
This means that when constructing the recipe from the form data the value for this options will be converted to a boolean.
This means that when constructing the recipe from the form data the value for this option will be converted to a boolean.
*The last step is adding the language strings for the new variable. Add the following to the ''lang/en/tool_pluginskel.php'' file:
*The last step is adding the language strings for the new variable. Add the following to the lang/en/tool_pluginskel.php file:
<code php>
<code php>
// The name of the feature is 'features_myfeature' because in the recipe the feature is under 'features'.
// The name of the feature is 'features_myfeature' because in the recipe the feature is under 'features'.

Latest revision as of 07:46, 7 September 2016

Generate plugin skeleton

The plugin will generate the skeleton files needed for a specific plugin type. The full functionality of the plugin can be accessed either via a web interface or the command line script.

To generate a skeleton plugin via the web interface:

  1. Proceed to Administration > Site administration > Development > Generate plugin skeleton
  2. Fill in the various plugin features then click on the "Download plugin skeleton button"
Generate plugin skeleton

To generate the plugin skeleton by using the comand line interface:

  1. Create a recipe file in the YAML format, either by writing it from scratch or by editing the template recipe located at cli/example.yaml (recommended)
  2. Invoke the command line script at cli/generate.php:
 php cli/generate.php --recipe=recipe.yaml

This will generate the plugin in the Moodle path directory corresponding to the plugin type being generated.

Installation

This plugin has been tested to work with Moodle 3.1 and newer. There are no guarantess it will work with earlier versions.

General installation procedures are those common for all Moodle plugins: Installing plugins.

When downloading the plugin from the git repository tool_pluginskel there are several options available: cloning the repository, downloading the zip file and extracting it or using the zip file for the plugin install interface accessible at Administration > Site administration > Plugins > Install plugins.

If you choose to clone the repository, then you need to clone it into MOODLE_ROOT_DIRECTORY/admin/tool/pluginskel:

 git clone https://github.com/mudrd8mz/moodle-tool_pluginskel MOODLE_ROOT_DIRECTORY/admin/tool/pluginskel

replacing MOODLE_ROOT_DIRECTORY with the actual Moodle installation root directory path. The zip file should be extracted to the same location.

Keep in mind that cloning the repository also creates a hidden .git directory, which you may not want on live servers.

Note: If you decide to use the install plugin interface don't forget to rename the folder inside the archive to pluginskel.

The web interface

The web interface is accessible from Administration > Site administration > Development > Generate plugin skeleton. You are given a choice between filling in all the required fields manually, uploading a previously created recipe file or writing the recipe by hand in the text area (presumably by copying and pasting an existing recipe).

The second page of the web interface presents you with all the options needed to generate the complete plugin skeleton. There are help icons associated with each element, as well as links to the relevant Moodle documentation, to guide you through the creation process.

Plugin skeleton creation

After you have filled the necessary fields, you are given three options:

  1. You can generate the plugin files and download them by clicking on the Download plugin skeleton button. The files will be packaged as a zip archive.
  2. You can choose to generate and save a recipe file in the YAML format from the form input. This is accomplished by clicking on the Download recipe button.
  3. You can view and edit the recipe created from the form by clicking the Show recipe button. You will be taken to the edit recipe web page:
View and edit the recipe

It's worth noting that any changes you make here to the recipe will be reflected on the previous page if you click the Back button.

The command line script

The command line script is located at cli/generate.php inside the plugin installation directory (that is MOODLE_ROOT_DIRECTORY/admin/tool/pluginskel). The general syntax of the command line script is as follows:

 php cli/generate.php --recipe=RECIPE.YAML [--help] [--target-dir=DIR] [--target-moodle=DIR]

The available arguments are:

  • --help, -h - prints a message about how to use the script.
  • --recipe=RECIPE.YAML - the location of the recipe file, as a relative or absolute path to the file. Mandatory.
  • --loglevel=LOGLEVEL - the verbosity of the log message. The default is WARNING. The plugin uses the Monolog logging library, for a full list of the different log levels please examine the file vendor/monolog/monolog/src/Monolog/Logger.php located in the plugin directory.
  • --target-dir=DIR - the path to the directory where the plugin files will be generate (either relative or absolute). Optional. Inside DIR a directory with the component name will be created where all the plugin files will be placed. For example, when creating the plugin mod_newmodule:
   php cli/generate.php --recipe=mod_newmodule_recipe.yaml --target-dir=/tmp

the plugin files will be located at /tmp/newmodule/.

  • target-moodle=DIR - this will generate all the plugin files in the Moodle path directory coresponding to the plugin type being generated, but with DIR considered the Moodle root directory. Optional. For example, to generate the plugin tool_newtool:
   php cli/generate.php --recipe=tool_newtool_recipe.yaml --target-moodle=/tmp

the plugin file will be located at /tmp/admin/tool/newtool/.

Note that if not using neither --target-dir, nor --target-moodle the plugin files will be generated in the Moodle path directory in the current Moodle installation.

The recipe format

The recipe needs to be written in the YAML format. It is highly recommended to use the example recipe located at cli/example.yaml as a template for the plugin and edit or remove options as needed.

Conceptually, the recipe is split into three different parts:

  1. The first part represents the options needed to create the version.php file and the language strings file located at lang/en/<component>.php. These two files are mandatory for all plugins, regardless of the plugin type, and the options are declared at the top level of the recipe. These are:
    component, name, release, version, requies, maturiy, copyright, dependencies, lang_strings
  1. The second part consists of features that are common to (most) all plugin types. This in turn is split into two sub-parts:
    1. Options that have a boolean value and they control if a file is to be generated or not. These are defined under the features section of the recipe. They are:
      install, uninstall, settings, readme, license, upgrade, upgradelib
    2. Options that needed to be defined as an array (either associative or numerically indexed). They reside at the top level of the recipe and they are:
      capabilities, message_providers, cli_scripts, observers_events, mobile_addons, phpunit_tests
  1. The last part consists of plugin specific features. All features that correspond to a plugin type will be under the <componenttype>_features section of the recipe.

Explanation about what the features do as well as links to relevant Moodle documentation for each of the above options can be found on the web interface, by clicking the help icons.

Adding new features

The plugin has been designed to be easily expanded by adding new features. But first let's take a look at how the plugin generates the skeleton files from a recipe.

The generation process

The generation process is handled by the manager class located at classes/local/util/manager.php. The input for the manager class is the array representation of a recipe. The conversion from the YAML format must be done before invoking the manager.

Invoking the manager class to create a plugin skeleton is done following these steps:

  1. First, an instance of the class is created by passing a logger class to the static constructor manager::instance($logger).
  2. The recipe is parsed and saved by the manager by invoking the public method load_recipe($recipe). The argument is an array representation of the recipe.
  3. The file content are created and saved by using the function make(). No files are created on disk at this stage.
  4. Actually creating the files can be done by invoking the method write_files($targetdirectory). As an alternative, you can access the files' content by using get_files_content() (this approach is used when creating the PHPUnit test cases for the plugin).

Note that the manager does no validation (if it exists, if it's writable, etc) on the $targetdirectory.

Each file content is generated from a template using Mustache. The Mustache templates are located in the skel/ directory. Responsible for making use of the Mustache engine to generate the files are skeleton classes located at classes/local/skel, and each instance of the skeleton class will generate one file from one template. The template to generate the file from and the recipe data for the template are specified when the skeleton class is instantiated inside the manager function prepare_file_skeleton().

Now let's take a closer look at what happens when the function make() is called:

  • The function prepare_files_skeletons() is invoked. This function will create the all the files' content and save it in the files class attribute. One entry in the files is composed of the file name and the instance of the skeleton class responsible for generating that file.
  • Inside the prepare_files_skeletons() function, files are queued for generation by invoking the function prepare_file_skeleton() based on the recipe options. To check if a given file should be generated there are two different functions: has_common_feature(), which deals with the features common to all plugin types, and has_component_features() which checks whether a component specific feature is present.
  • Once all the files have been added to the files list, the skeleton class method render() will be invoked for each skeleton class, and that will generate the content of each file.

Adding new features

To add a new feature, one must first decide the type feature: if it's a common feature (it applies to all plugins), or if it's a component specific feature (it applies only to a specific plugin type). See the recipe format for more details.

After this has been decided, the following steps are necessary:

  1. Create a Mustache template (or more) for the feature. If the feature is a component feature, then the template should go in the skel/file/<componenttype>/ directory, where componenttype is the plugin type. Otherwise, it should be in the skel/file/ directory.
  2. Choose or create a new skeleton class to render the template. If creating a new skeleton class please extend one of the following three skeleton classes: php_internal_file (for files that are internal to Moodle and should not be accessible from the web page, they have the expression defined('MOODLE_INTERNAL') || die() at the beginning of the file), php_web_file (these are intended to be used from the web interface and include the config.php file), php_cli_file (these are command line scripts and define the constant 'CLI_SCRIPT' to be true) or txt_file (which is a common text file, like the LICENSE.md or README.md files).
  3. Add code to the manager function prepare_plugin_skeleton() to make sure that the file(s) are queued for generation. Based on the feature type one of the functions has_component_feature() or has_common_feature() should return true if the feature is present in the recipe.
  4. (Optional, but highly recommended)Create PHPUnit tests for the new feature in the tests/ directory. You can use one of the existing tests as a template.
  5. To have the new feature present on the web interface:
    1. The feature should be added to one (and not both) of the static functions: manager->get_component_variables($component) or manager->get_features_variables(). Based on the code already present it should be self explanatory how that is done.
    2. Create the language strings for the new feature in the file lang/en/tool_pluginskel.php. At the very least a feature will require two strings: the human readable name for the feature and the help message for that feature. It should be obvious about how to do that from the strings already present there.
  6. That's all.

Example

Here's an example of how to add a feature to the plugin.

First, we decide on the feature type and name. We'll add a new common feature, "myfeature:, which will be located in the features section of the recipe (meaning it will just toggle the creation of some file). If enabled, the file myfeature.php will generated in the directory mydirectory/.

A simple recipe should look something like this:

component: local_test

## Human readable name of the plugin.
name: Example plugin

## Human readable release number.
release: "0.1.0"

## Plugin version number, e.g. 2016062100. Will be set to current date if left empty.
version: 2016121200

## Required Moodle version, e.g. 2015051100 or "2.9".
requires: "2.9"

## Plugin maturity level. Possible options are MATURIY_ALPHA, MATURITY_BETA,
## MATURITY_RC or MATURIY_STABLE.
maturity: MATURITY_BETA

## Copyright holder(s) of the generated files and classes.
copyright: 2016 Alexandru Elisei <alexandru.elisei@gmail.com>, David Mudrák <david@moodle.com>

features:
    myfeature:true

Next we create the template file at skel/file/myfeature.mustache. We will use the skeleton class php_internal_file for generating this file, but we are free to use any other base class, or create our own.

Now we modify the manager class to make sure the file is generated properly:

  • We make sure that the function has_common_feature() returns true when the feature is specified in the recipe file. By default, if a feature is set to true and it resides in the features section of the recipe the function will return true. As this is our case, we don't have to modify has_common_feature().
  • Next we queue the file for generation inside the prepare_files_skeletons() function:

protected function prepare_files_skeletons() {

   ...
   if ($this->has_common_feature('myfeature')) {
       $this->prepare_file_skeleton('mydirectory/myfeature.php', 'php_internal_file', 'myfeature');
   }
   ...

}

  • (Optional, but highly recommended) Create the PHPUnit test file tests/myfeature_test.php. You can use one of the existing test files as an example (readme_test.php is one of the simplest).
  • Now we have to make sure that we have access to this feature in the web interface. For that we modify the function get_features_variables():

public static function get_features_variables() {

   $featuresvars = array();
   $featuresvars[] = array('name' => 'myfeature', 'type' => 'boolean');
   ...

} This means that when constructing the recipe from the form data the value for this option will be converted to a boolean.

  • The last step is adding the language strings for the new variable. Add the following to the lang/en/tool_pluginskel.php file:

// The name of the feature is 'features_myfeature' because in the recipe the feature is under 'features'. $string['features_myfeature'] = 'The name of the feature on the web form'; $string['features_myfeature_help'] = 'Help message for myfeature'; $string['features_myfeature_link'] = 'Link to additional resources regarding the feature';

  • That's it, we're done.