From 3868bdb938aeff7e3f6fd50350f542161e8da6c8 Mon Sep 17 00:00:00 2001 From: Damien Regad Date: Sat, 27 Feb 2021 01:00:07 +0100 Subject: [PATCH] Doc: improve Dev Guide Plugins section - General revision and various improvements to the "Building a Plugin" section - Remove references to deprecated helper_alternate_class() function - remove the "Example Plugin Source Listing" section, code is now hosted in a GitHub repository in the mantisbt-plugins organization [1] - Improve the Example Plugin; for details, see [1] Fixes #27992, #27993 [1]: https://github.com/mantisbt-plugins/Example --- docbook/Developers_Guide/en-US/Plugins.xml | 1 - .../en-US/Plugins_Building.xml | 533 ++++++++++++------ .../en-US/Plugins_Building_Source.xml | 189 ------- 3 files changed, 375 insertions(+), 348 deletions(-) delete mode 100644 docbook/Developers_Guide/en-US/Plugins_Building_Source.xml diff --git a/docbook/Developers_Guide/en-US/Plugins.xml b/docbook/Developers_Guide/en-US/Plugins.xml index 8fb3b96bd0..f0aab8f790 100644 --- a/docbook/Developers_Guide/en-US/Plugins.xml +++ b/docbook/Developers_Guide/en-US/Plugins.xml @@ -48,7 +48,6 @@ -
API Usage diff --git a/docbook/Developers_Guide/en-US/Plugins_Building.xml b/docbook/Developers_Guide/en-US/Plugins_Building.xml index 4a2b06313d..19dbc0f455 100644 --- a/docbook/Developers_Guide/en-US/Plugins_Building.xml +++ b/docbook/Developers_Guide/en-US/Plugins_Building.xml @@ -8,17 +8,21 @@ Building a Plugin - This section will act as a walk through of how to build a plugin, from the - bare basics all the way up to advanced topics. A general understanding of - the concepts covered in the last section is assumed, as well as knowledge - of how the event system works. Later topics in this section will require - knowledge of database schemas and how they are used with MantisBT. + This section will act as a tutorial to build a new plugin, from the + bare basics all the way up to advanced topics. + + + A general understanding of HTML, PHP and the concepts covered in the + last section is assumed, as well as knowledge of how the event system works. + Later topics in this section will require knowledge of database schemas + and how they are used with MantisBT. - This walk through will be working towards building a single end result: the - "Example" plugin as listed in . - You may refer to the final source code along the way, + This walk-through will be working towards building a single + end result: the Example Plugin. + You may refer to the final code along the way + (see ), although every part of it will be built up in steps throughout this section. @@ -27,7 +31,7 @@ This section will introduce the general concepts of plugin structure, - and how to get a barebones plugin working with MantisBT. Not much will be + and how to get a bare-bones plugin working with MantisBT. Not much will be mentioned yet on the topic of adding functionality to plugins, just how to get the development process rolling. @@ -104,29 +108,32 @@ Example/ Example/Example.php - -<?php +name = 'Example'; # Proper name of plugin - $this->description = ''; # Short description of the plugin - $this->page = ''; # Default plugin page - - $this->version = '1.0'; # Plugin version string - $this->requires = array( # Plugin dependencies - 'MantisCore' => '2.0', # Should always depend on an appropriate - # version of MantisBT + $this->name = 'Example Plugin'; # Proper name of plugin + $this->description = 'Example Plugin from MantisBT Developers Guide'; + # Short description of the plugin + $this->page = ''; # Default plugin page + + $this->version = '2.0'; # Plugin version string + $this->requires = array( # Plugin dependencies + 'MantisCore' => '2.0', # Should always depend on an appropriate + # version of MantisBT ); - $this->author = ''; # Author/team name - $this->contact = ''; # Author/team e-mail address - $this->url = ''; # Support webpage + $this->author = 'MantisBT Team'; # Author/team name + $this->contact = 'mantisbt-dev@lists.sourceforge.net'; + # Author/team e-mail address + $this->url = 'https://mantisbt.org'; # Support webpage } } +]]> - This alone will allow the Example plugin to be installed with MantisBT, and + This alone will allow our Example plugin to be installed with MantisBT, and is the foundation of any plugin. More of the plugin development process will be continued in the next sections. @@ -161,9 +168,10 @@ class ExamplePlugin extends MantisPlugin { page The name of a plugin page for further information - and administration of the plugin. - This is used to create a link to the specified page - on Mantis' manage plugin page. + and administration of the plugin; this is usually + the Plugin's configuration page. + MantisBT will create a link to the specified page + on the Manage Plugins page. @@ -190,10 +198,10 @@ class ExamplePlugin extends MantisPlugin { An array of key/value pairs of basename/version plugin dependencies. - The special, reserved basename - MantisCore can be used to - specify the minimum requirement for MantisBT - core. + The special, reserved + MantisCore basename + can be used to specify the minimum requirement + for MantisBT core. The version string can be defined as: @@ -338,34 +346,68 @@ $this->requires = array( 'MantisCore' => '1.3, < 4.0' ); The plugin API provides a standard hierarchy and process for adding new pages and - files to your plugin. For strict definitions, pages are PHP files that will be - executed within the MantisBT core system, while files are defined as a separate - set of raw data that will be passed to the client's browser exactly as it appears - in the filesystem. + files to your plugin. For strict definitions, + pages + are PHP files that will be executed within the MantisBT core system, while + files + are defined as a separate set of raw data that will be passed to the + client's browser exactly as it appears in the filesystem. - New pages for your plugin should be placed in your plugin's - pages/ directory, and should be named using only letters and - numbers, and must have a ".php" file extension. To generate a URI to the new page - in MantisBT, the API function plugin_page() should be used. + Your Plugin's pages must: + + + be placed in the pages/ directory, + + + be named using only letters, numbers and underscores, and + + + have a .php file extension + + + To generate a URI to a page in MantisBT, the API function + plugin_page() should be used. Our Example plugin will create a page named foo.php, which - can then be accessed via plugin_page.php?page=Example/foo, the - same URI that plugin_page() would have generated: + can then be accessed via plugin_page.php?page=Example/foo: Example/pages/foo.php + -<?php -echo '<p>Here is a link to <a href="', plugin_page( 'foo' ), '">page foo</a>.</p>'; +

+ Here is a link to page foo. +

+ +
+ + + The calls to layout_page_begin() and + layout_page_end() trigger the standard MantisBT + header and footer portions, respectively, which also displays things + such as the menus and triggers other layout-related events. + layout_page_header() pulls in the CSS classes for + alternating row colors in the table, amongst other things. + + Adding non-PHP files, such as images or CSS stylesheets, follows a very similar pattern as pages. Files should be placed in the plugin's files/ directory, and can only contain a single period in the name. The file's URI is generated with the plugin_file() - function. For our Example plugin, we'll create a basic CSS stylesheet, and modify + function. + + + For our Example plugin, we'll create a basic CSS stylesheet, and modify the previously shown page to include the stylesheet: @@ -376,20 +418,26 @@ p.foo { } - Example/pages/foo.php + Example/pages/foo.phppage foo</a>.</p>'; -echo '<link rel="stylesheet" type="text/css" href="', plugin_file( 'foo.css' ), '"/>', - '<p class="foo">This is red text.</p>'; + +

+ Our custom stylesheet paints this text red. +

+]]>
- - Note that while plugin_page() expects only the page's name - without the extension, plugin_file() requires the entire - filename so that it can distinguish between foo.css and - a potential file foo.png. - + + + While plugin_page() expects only + the page's name without the extension, + plugin_file() on the other hand + requires the entire filename, so that it can distinguish + e.g. between foo.css and + a potential image file called foo.png. + + The plugin's filesystem structure at this point looks like this: @@ -416,11 +464,14 @@ Example/ - To declare a new event, or a set of events, that your plugin will trigger, override + To declare a new event, or a set of events that your plugin will trigger, override the events() method of your plugin class, and return an associative array with event names as the key, and the event type as the value. - Let's add an event "foo" to our Example plugin that does not expect a return value - (an "execute" event type), and another event 'bar' that expects a single value that + + + Let's add to our Example plugin, a new event foo + that does not expect a return value (an "execute" event type), + and another event bar expecting a single value that gets modified by each hooked function (a "chain" event type): @@ -441,18 +492,29 @@ class ExamplePlugin extends MantisPlugin { When the Example plugin is loaded, the event system in MantisBT will add these two - events to its list of events, and will then allow other plugins or functions to hook - them. Naming the events "EVENT_PLUGINNAME_EVENTNAME" is not necessary, but is considered - best practice to avoid conflicts between plugins. + items to its list of events, and will then allow other plugins or functions to hook + them. + + + Naming the events "EVENT_PLUGINBASENAME_EVENTNAME" is not required, + but is considered best practice to avoid conflicts between plugins. + + - Hooking other events (or events from your own plugin) is almost identical to declaring - them. Instead of passing an event type as the value, your plugin must pass the name - of a class method on your plugin that will be called when the event is triggered. For - our Example plugin, we'll create a foo() and - bar() method on our plugin class, and hook them to the events we - declared earlier. + Hooking events, whether they are your own plugin's or defined elsewhere, + is almost identical to declaring them. + Instead of passing an event type as the value, your plugin must give the name + of a class method that will be called when the event is triggered. + + + For our Example plugin, we'll create a foo() + and a bar() methods in our plugin class, + then hook them to the events we declared earlier. + We'll also hook the standard EVENT_MENU_MAIN event, + to link the custom page we created in + . Example/Example.php @@ -463,34 +525,30 @@ class ExamplePlugin extends MantisPlugin { function hooks() { return array( + 'EVENT_MENU_MAIN' => 'menu', + 'EVENT_EXAMPLE_FOO' => 'foo', 'EVENT_EXAMPLE_BAR' => 'bar', ); } - - function foo( $p_event ) { - ... - } - - function bar( $p_event, $p_chained_param ) { - ... - return $p_chained_param; - } } - Note that both hooked methods need to accept the $p_event - parameter, as that contains the event name triggering the method (for cases where - you may want a method hooked to multiple events). The bar() - method also accepts and returns the chained parameter in order to match the - expectations of the "bar" event. + Function signatures vary depending on the hooked event's type + (see ). + They generally should accept the $p_event + parameter, which contains the event name triggering the method + (allowing a single method to respond to multiple events). + The bar() method also accepts and returns + a second parameter, in order to match the expectations of + chained events. - Now that we have our plugin's events declared and hooked, let's modify our earlier - page so that triggers the events, and add some real processing to the hooked - methods: + Now that we have our plugin's events declared and hooked, + let's define the hook methods and + modify our earlier page so it triggers the new events: Example/Example.php @@ -498,6 +556,15 @@ class ExamplePlugin extends MantisPlugin { <?php class ExamplePlugin extends MantisPlugin { ... + function menu() { + $t_menu[] = array( + 'title' => $this->name, + 'url' => plugin_page( 'foo' ), + 'access_level' => ANYBODY, + 'icon' => 'fa-smile-o' + ); + return $t_menu; + } function foo( $p_event ) { echo 'In method foo(). '; @@ -509,19 +576,20 @@ class ExamplePlugin extends MantisPlugin { } - Example/pages/foo.php - -<?php -echo '<p>Here is a link to <a href="', plugin_page( 'foo' ), '">page foo</a>.</p>'; - '<link rel="stylesheet" type="text/css" href="', plugin_file( 'foo.css' ), '"/>', - '<p class="foo">'; + Example/pages/foo.php + Custom event hooks: + +

+]]>
@@ -571,51 +639,91 @@ class ExamplePlugin extends MantisPlugin { using the plugin API's plugin_config_get() function, and can be set to a modified value in the database using plugin_config_set(). With these functions, the config option - is prefixed with the plugin's name, in attempt to automatically avoid conflicts in - naming. Our Example plugin will demonstrate this by adding a secure form to the - "config_page", and handling the form on a separate page "config_update" that will - modify the value in the database, and redirect back to page "config_page", just - like any other form and action page in MantisBT: + is prefixed with the plugin's name, in an attempt to automatically avoid conflicts in + naming. + + Our Example plugin will demonstrate this by adding a secure form to a + new configuration page we'll call config.php, and + handling the form on a separate config_update.php page + that will modify the value in the database, and redirect back to the config + page, just like any other form and action page in MantisBT: - Example/pages/config_page.php - -<form action="<?php echo plugin_page( 'config_update' ) ?>" method="post"> -<?php echo form_security_field( 'plugin_Example_config_update' ) ?> - -<label>Foo or Bar?<br/><input name="foo_or_bar" value="<?php echo string_attribute( $t_foo_or_bar ) ?>"/></label> -<br/> -<label><input type="checkbox" name="reset"/> Reset</label> -<br/> -<input type="submit"/> + + + This code sample is a bare-bones implementation, focusing + on functionality and meant for illustration purposes. + Please refer to + for a more realistic example, including layout and styling. + + -</form> + Example/pages/config.php + + +
+ + +
+ +
+ +
+]]>
- Example/pages/config_update.php - -<?php + - - Note that the form_security_*() functions are part of the - form API, and prevent CSRF attacks against forms that make changes to the system. + + + The form_security_*() functions are part of the + Form API, and prevent CSRF attacks against forms that make changes to the system. + + + + The last step is to add a link our new config page from + the Manage Plugins page. + This is as simple as referencing the config page's name in the + register() method, which will turn + the Plugin's name into an hyperlink. + + Example/Example.php + + function register() { + ... + $this->page = 'config'; # Default plugin page + +
@@ -641,70 +749,179 @@ print_successful_redirect( plugin_page( 'foo', true ) ); - We'll use the "configuration" pages from the previous examples, and dress them up - with localized language strings, and add a few more flourishes to make the page act - like a standard MantisBT page. First we need to create a language file for English, - the default language of MantisBT and the default fallback language in the case that - some strings have not yet been localized to the user's language: + We'll use the pages from the previous examples, + and dress them up with localized language strings. + First we need to create a language file for English, + MantisBT's default language which is also used as fallback in case + some strings have not been localized to the user's language: Example/lang/strings_english.txt <?php - +$s_plugin_Example_title = "Example Plugin"; +$s_plugin_Example_description = "Example Plugin from MantisBT Developers Guide"; $s_plugin_Example_configuration = "Configuration"; $s_plugin_Example_foo_or_bar = "Foo or Bar?"; $s_plugin_Example_reset = "Reset Value"; - Example/pages/config_page.php -<?php - -layout_page_header( plugin_lang_get( 'configuration' ) ); -layout_page_begin(); -$t_foo_or_bar = plugin_config_get( 'foo_or_bar' ); + + We can now use these strings to localize our code using the + plugin_lang_get() API function, + replacing the hardcoded text and adding page title: + -?> + Example/Example.php -<br/> + function register() { + ... + $this->description = plugin_lang_get( 'title' ); + -<form action="<?php echo plugin_page( 'config_update' ) ?>" method="post"> -<?php echo form_security_field( 'plugin_Example_config_update' ) ?> -<table class="width60"> + Example/pages/foo.php + +... +]]> + -<tr> - <td class="form-title" rowspan="2"><?php echo plugin_lang_get( 'configuration' ) ?></td> -</tr> + Example/pages/config.php + + +
+ + +
+ +
+ +
+]]> +
+
-<tr <?php echo helper_alternate_class() ?>> - <td class="category"><php echo plugin_lang_get( 'reset' ) ?></td> - <td><input type="checkbox" name="reset"/></td> -</tr> +
+ Layout -<tr> - <td class="center" rowspan="2"><input type="submit"/></td> -</tr> + + To finalize our plugin, we'll add some markup and flourishes + to make the config page look like a standard MantisBT page. + -</table> -</form> + + + Alternating colors are applied automatically to the table's + rows with CSS, it is no longer necessary to rely on the + now-deprecated helper_alternate_rows() + API function. + + -<?php + Example/pages/config.php + + +
+
+ +
+
+ +
+
+

+ +

+
+ +
+
+ + + + + + + + + +
+ + + +
+ + + + +
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+ +
+ Example Plugin source code - The two calls to layout_page_being() and - layout_page_end() trigger the standard MantisBT header and - footer portions, respectively, which also displays things such as the menus and - triggers other layout-related events. layout_page_header() - pulls in the CSS classes for alternating row colors in the table. The rest of the - HTML and CSS follows the "standard" MantisBT markup styles for content and layout. + The + complete source code + for the Example Plugin built in this tutorial + is available in the mantisbt-plugins + GitHub organization. + + + It has been released into the public domain under the + Unlicense, + which means you are free to use it in any way you want, + without warranty of any kind.
diff --git a/docbook/Developers_Guide/en-US/Plugins_Building_Source.xml b/docbook/Developers_Guide/en-US/Plugins_Building_Source.xml deleted file mode 100644 index 22f9f1de96..0000000000 --- a/docbook/Developers_Guide/en-US/Plugins_Building_Source.xml +++ /dev/null @@ -1,189 +0,0 @@ - - -%BOOK_ENTITIES; -]> -
- Example Plugin Source Listing - - - The code in this section, for the Example plugin, is available for use, - modification, and redistribution without any restrictions and without any - warranty or implied warranties. You may use this code however you want. - - - -Example/ - Example.php - files/ - foo.css - lang/ - strings_english.txt - pages/ - config_page.php - config_update.php - foo.php - - -
- Example/Example.php - - Example/Example.php -<?php -class ExamplePlugin extends MantisPlugin { - function register() { - $this->name = 'Example'; # Proper name of plugin - $this->description = ''; # Short description of the plugin - $this->page = ''; # Default plugin page - - $this->version = '1.0'; # Plugin version string - $this->requires = array( # Plugin dependencies - 'MantisCore' => '2.0', # Should always depend on an appropriate - # version of MantisBT - ); - - $this->author = ''; # Author/team name - $this->contact = ''; # Author/team e-mail address - $this->url = ''; # Support webpage - } - - function events() { - return array( - 'EVENT_EXAMPLE_FOO' => EVENT_TYPE_EXECUTE, - 'EVENT_EXAMPLE_BAR' => EVENT_TYPE_CHAIN, - ); - } - - function hooks() { - return array( - 'EVENT_EXAMPLE_FOO' => 'foo', - 'EVENT_EXAMPLE_BAR' => 'bar', - ); - } - - function config() { - return array( - 'foo_or_bar' => 'foo', - ); - } - - function foo( $p_event ) { - echo 'In method foo(). '; - } - - function bar( $p_event, $p_chained_param ) { - return str_replace( 'foo', 'bar', $p_chained_param ); - } - -} - -
- -
- Example/files/foo.css - - Example/files/foo.css -p.foo { - color: red; -} - -
- -
- Example/lang/strings_english.txt - - Example/lang/strings_english.txt -<?php - -$s_plugin_Example_configuration = "Configuration"; -$s_plugin_Example_foo_or_bar = "Foo or Bar?"; -$s_plugin_Example_reset = "Reset Value"; - -
- -
- Example/page/config_page.php - - Example/pages/config_page.php -<?php - -layout_page_header( plugin_lang_get( 'configuration' ) ); -layout_page_begin(); -$t_foo_or_bar = plugin_config_get( 'foo_or_bar' ); - -?> - -<br/> - -<form action="<?php echo plugin_page( 'config_update' ) ?>" method="post"> -<?php echo form_security_field( 'plugin_Example_config_update' ) ?> -<table class="width60"> - -<tr> - <td class="form-title" rowspan="2"><?php echo plugin_lang_get( 'configuration' ) ?></td> -</tr> - -<tr <?php echo helper_alternate_class() ?>> - <td class="category"><php echo plugin_lang_get( 'foo_or_bar' ) ?></td> - <td><input name="foo_or_bar" value="<?php echo string_attribute( $t_foo_or_bar ) ?>"/></td> -</tr> - -<tr <?php echo helper_alternate_class() ?>> - <td class="category"><php echo plugin_lang_get( 'reset' ) ?></td> - <td><input type="checkbox" name="reset"/></td> -</tr> - -<tr> - <td class="center" rowspan="2"><input type="submit"/></td> -</tr> - -</table> -</form> - -<?php - -layout_page_end(); - -
- -
- Example/pages/config_update.php - - Example/pages/config_update.php -<?php -form_security_validate( 'plugin_Example_config_update' ); - -$f_foo_or_bar = gpc_get_string( 'foo_or_bar' ); -$f_reset = gpc_get_bool( 'reset', false ); - -if( $f_reset ) { - plugin_config_delete( 'foo_or_bar' ); -} else { - if( $f_foo_or_bar == 'foo' || $f_foo_or_bar == 'bar' ) { - plugin_config_set( 'foo_or_bar', $f_foo_or_bar ); - } -} - -form_security_purge( 'plugin_Example_config_update' ); -print_successful_redirect( plugin_page( 'foo', true ) ); - -
- -
- Example/page/foo.php - - Example/pages/foo.php -<?php -echo '<p>Here is a link to <a href="', plugin_page( 'foo' ), '">page foo</a>.</p>'; - '<link rel="stylesheet" type="text/css" href="', plugin_file( 'foo.css' ), '"/>', - '<p class="foo">'; - -event_signal( 'EVENT_EXAMPLE_FOO' ); - -$t_string = 'A sentence with the word "foo" in it.'; -$t_new_string = event_signal( 'EVENT_EXAMPLE_BAR', array( $t_string ) ); - -echo $t_new_string, '</p>'; - -
-