| [ Index ] |
PHP Cross Reference of MantisBT |
[Summary view] [Print] [Text view]
1 <?xml version='1.0' encoding='utf-8' ?> 2 <!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [ 3 <!ENTITY % BOOK_ENTITIES SYSTEM "Developers_Guide.ent"> 4 %BOOK_ENTITIES; 5 ]> 6 7 <sect1 id="dev.plugins.building"> 8 <title>Building a Plugin</title> 9 10 <para> 11 This section will act as a walkthrough of how to build a plugin, from the 12 bare basics all the way up to advanced topics. A general understanding of 13 the concepts covered in the last section is assumed, as well as knowledge 14 of how the event system works. Later topics in this section will require 15 knowledge of database schemas and how they are used with MantisBT. 16 </para> 17 18 <para> 19 This walkthrough will be working towards building a single end result: the 20 "Example" plugin as listed in the <link linkend="dev.plugins.building.source"> 21 next section</link>. You may refer to the final source code along the way, 22 although every part of it will be built up in steps throughout this section. 23 </para> 24 25 <sect2 id="dev.plugins.building.basics"> 26 <title>The Basics</title> 27 28 <para> 29 This section will introduce the general concepts of plugin structure, 30 and how to get a barebones plugin working with MantisBT. Not much will be 31 mentioned yet on the topic of adding functionality to plugins, just how to 32 get the development process rolling. 33 </para> 34 35 <sect3 id="dev.plugins.building.basics.structure"> 36 <title>Plugin Structure</title> 37 38 <para> 39 The backbone of every plugin is what MantisBT calls the "basename", a 40 succinct, and most importantly, unique name that identifies the plugin. 41 It may not contain any spacing or special characters beyond the ASCII 42 upper- and lowercase alphabet, numerals, and underscore. This is used 43 to identify the plugin everywhere except for what the end-user sees. 44 For our "Example" plugin, the basename we will use should be obvious 45 enough: "Example". 46 </para> 47 48 <para> 49 Every plugin must be contained in a single directory named to match the 50 plugin's basename, as well as contain at least a single PHP file, also 51 named to match the basename, as such: 52 </para> 53 54 <programlisting> 55 Example/ 56 Example.php 57 </programlisting> 58 59 <para> 60 This top-level PHP file must then contain a concrete class deriving from 61 the <classname>MantisPlugin</classname> class, which must be named in the 62 form of <classname>%Basename%Plugin</classname>, which for our purpose 63 becomes <classname>ExamplePlugin</classname>. 64 </para> 65 66 <para> 67 Because of how <classname>MantisPlugin</classname> declares the 68 <function>register()</function> method as <literal>abstract</literal>, our 69 plugin must implement that method before PHP will find it semantically 70 valid. This method is meant for one simple purpose, and should never be 71 used for any other task: setting the plugin's information properties, 72 including the plugin's name, description, version, and more. 73 </para> 74 75 <para> 76 Once your plugin defines its class, implements the <function>register()</function> 77 method, and sets at least the name and version properties, it is then 78 considered a "complete" plugin, and can be loaded and installed within 79 MantisBT's plugin manager. At this stage, our Example plugin, with all the 80 possible plugin properties set at registration, looks like this: 81 </para> 82 83 <programlisting><filename>Example/Example.php</filename> 84 85 <?php 86 class ExamplePlugin extends MantisPlugin { 87 function register() { 88 $this->name = 'Example'; # Proper name of plugin 89 $this->description = ''; # Short description of the plugin 90 $this->page = ''; # Default plugin page 91 92 $this->version = '1.0'; # Plugin version string 93 $this->requires = array( # Plugin dependencies, array of basename => version pairs 94 'MantisCore' => '1.2.0, <= 1.2.0', # Should always depend on an appropriate version of MantisBT 95 ); 96 97 $this->author = ''; # Author/team name 98 $this->contact = ''; # Author/team e-mail address 99 $this->url = ''; # Support webpage 100 } 101 } 102 </programlisting> 103 104 <para> 105 This alone will allow the Example plugin to be installed with MantisBT, and 106 is the foundation of any plugin. More of the plugin development process 107 will be continued in the next sections. 108 </para> 109 </sect3> 110 </sect2> 111 112 <sect2 id="dev.plugins.building.pages"> 113 <title>Pages and Files</title> 114 115 <para> 116 The plugin API provides a standard hierarchy and process for adding new pages and 117 files to your plugin. For strict definitions, pages are PHP files that will be 118 executed within the MantisBT core system, while files are defined as a separate 119 set of raw data that will be passed to the client's browser exactly as it appears 120 in the filesystem. 121 </para> 122 123 <para> 124 New pages for your plugin should be placed in your plugin's 125 <filename>pages/</filename> directory, and should be named using only letters and 126 numbers, and must have a ".php" file extension. To generate a URI to the new page 127 in MantisBT, the API function <function>plugin_page()</function> should be used. 128 Our Example plugin will create a page named <filename>foo.php</filename>, which 129 can then be accessed via <literal>plugin_page.php?page=Example/foo</literal>, the 130 same URI that <function>plugin_page()</function> would have generated: 131 </para> 132 133 <programlisting><filename>Example/pages/foo.php</filename> 134 135 <?php 136 echo '<p>Here is a link to <a href="', plugin_page( 'foo' ), '">page foo</a>.</p>'; 137 </programlisting> 138 139 <para> 140 Adding non-PHP files, such as images or CSS stylesheets, follows a very similar 141 pattern as pages. Files should be placed in the plugin's 142 <filename>files/</filename> directory, and can only contain a single period in 143 the name. The file's URI is generated with the <function>plugin_file()</function> 144 function. For our Example plugin, we'll create a basic CSS stylesheet, and modify 145 the previously shown page to include the stylesheet: 146 </para> 147 148 <programlisting><filename>Example/files/foo.css</filename> 149 150 p.foo { 151 color: red; 152 } 153 </programlisting> 154 155 <programlisting><filename>Example/pages/foo.php</filename> 156 157 <?php 158 echo '<p>Here is a link to <a href="', plugin_page( 'foo' ), '">page foo</a>.</p>'; 159 echo '<link rel="stylesheet" type="text/css" href="', plugin_file( 'foo.css' ), '"/>', 160 '<p class="foo">This is red text.</p>'; 161 </programlisting> 162 163 <para> 164 Note that while <function>plugin_page()</function> expects on the page's name, 165 without the extension, <function>plugin_file()</function> requires the entire 166 filename so that it can distinguish between <filename>foo.css</filename> and 167 a potential file <filename>foo.png</filename>. 168 </para> 169 170 <para> 171 The plugin's filesystem structure at this point looks like this: 172 </para> 173 174 <programlisting> 175 Example/ 176 Example.php 177 pages/ 178 foo.php 179 files/ 180 foo.css 181 </programlisting> 182 183 </sect2> 184 185 <sect2 id="dev.plugins.building.events"> 186 <title>Events</title> 187 188 <para> 189 Plugins have an integrated method for both declaring and hooking events, without 190 needing to directly call the event API functions. These take the form of class 191 methods on your plugin. 192 </para> 193 194 <para> 195 To declare a new event, or a set of events, that your plugin will trigger, override 196 the <function>events()</function> method of your plugin class, and return an 197 associative array with event names as the key, and the event type as the value. 198 Let's add an event "foo" to our Example plugin that does not expect a return value 199 (an "execute" event type), and another event 'bar' that expects a single value that 200 gets modified by each hooked function (a "chain" event type): 201 </para> 202 203 <programlisting><filename>Example/Example.php</filename> 204 205 <?php 206 class ExamplePlugin extends MantisPlugin { 207 ... 208 209 function events() { 210 return array( 211 'EVENT_EXAMPLE_FOO' => EVENT_TYPE_EXECUTE, 212 'EVENT_EXAMPLE_BAR' => EVENT_TYPE_CHAIN, 213 ); 214 } 215 } 216 </programlisting> 217 218 <para> 219 When the Example plugin is loaded, the event system in MantisBT will add these two 220 events to its list of events, and will then allow other plugins or functions to hook 221 them. Naming the events "EVENT_PLUGINNAME_EVENTNAME" is not necessary, but is considered 222 best practice to avoid conflicts between plugins. 223 </para> 224 225 <para> 226 Hooking other events (or events from your own plugin) is almost identical to declaring 227 them. Instead of passing an event type as the value, your plugin must pass the name 228 of a class method on your plugin that will be called when the event is triggered. For 229 our Example plugin, we'll create a <function>foo()</function> and 230 <function>bar()</function> method on our plugin class, and hook them to the events we 231 declared earlier. 232 </para> 233 234 <programlisting><filename>Example/Example.php</filename> 235 236 <?php 237 class ExamplePlugin extends MantisPlugin { 238 ... 239 240 function hooks() { 241 return array( 242 'EVENT_EXAMPLE_FOO' => 'foo', 243 'EVENT_EXAMPLE_BAR' => 'bar', 244 ); 245 } 246 247 function foo( $p_event ) { 248 ... 249 } 250 251 function bar( $p_event, $p_chained_param ) { 252 ... 253 return $p_chained_param; 254 } 255 } 256 </programlisting> 257 258 <para> 259 Note that both hooked methods need to accept the <parameter>$p_event</parameter> 260 parameter, as that contains the event name triggering the method (for cases where 261 you may want a method hooked to multiple events). The <function>bar()</function> 262 method also accepts and returns the chained parameter in order to match the 263 expectations of the "bar" event. 264 </para> 265 266 <para> 267 Now that we have our plugin's events declared and hooked, let's modify our earlier 268 page so that triggers the events, and add some real processing to the hooked 269 methods: 270 </para> 271 272 <programlisting><filename>Example/Example.php</filename> 273 274 <?php 275 class ExamplePlugin extends MantisPlugin { 276 ... 277 278 function foo( $p_event ) { 279 echo 'In method foo(). '; 280 } 281 282 function bar( $p_event, $p_chained_param ) { 283 return str_replace( 'foo', 'bar', $p_chained_param ); 284 } 285 } 286 </programlisting> 287 288 <programlisting><filename>Example/pages/foo.php</filename> 289 290 <?php 291 echo '<p>Here is a link to <a href="', plugin_page( 'foo' ), '">page foo</a>.</p>'; 292 '<link rel="stylesheet" type="text/css" href="', plugin_file( 'foo.css' ), '"/>', 293 '<p class="foo">'; 294 295 event_signal( 'EVENT_EXAMPLE_FOO' ); 296 297 $t_string = 'A sentence with the word "foo" in it.'; 298 $t_new_string = event_signal( 'EVENT_EXAMPLE_BAR', array( $t_string ) ); 299 300 echo $t_new_string, '</p>'; 301 </programlisting> 302 303 <para> 304 When the first event "foo" is signaled, the Example plugin's 305 <function>foo()</function> method will execute and echo a string. After that, 306 the second event "bar" is signaled, and the page passes a string parameter; the 307 plugin's <function>bar()</function> gets the string and replaces any instance of 308 "foo" with "bar", and returns the resulting string. If any other plugin had 309 hooked the event, that plugin could have further modified the new string from the 310 Example plugin, or vice versa, depending on the loading order of plugins. The 311 page then echos the modified string that was returned from the event. 312 </para> 313 </sect2> 314 315 <sect2 id="dev.plugins.building.config"> 316 <title>Configuration</title> 317 318 <para> 319 Similar to events, plugins have a simplified method for declaring configuration 320 options, as well as API functions for retrieving or setting those values at runtime. 321 </para> 322 323 <para> 324 Declaring a new configuration option is achieved just like declaring events. By 325 overriding the <function>config()</function> method on your plugin class, your 326 plugin can return an associative array of configuration options, with the option 327 name as the key, and the default option as the array value. Our Example plugin 328 will declare an option "foo_or_bar", with a default value of "foo": 329 </para> 330 331 <programlisting><filename>Example/Example.php</filename> 332 333 <?php 334 class ExamplePlugin extends MantisPlugin { 335 ... 336 337 function config() { 338 return array( 339 'foo_or_bar' => 'foo', 340 ); 341 } 342 } 343 </programlisting> 344 345 <para> 346 Retrieving the current value of a plugin's configuration option is achieved by 347 using the plugin API's <function>plugin_config_get()</function> function, and can 348 be set to a modified value in the database using 349 <function>plugin_config_set()</function>. With these functions, the config option 350 is prefixed with the plugin's name, in attempt to automatically avoid conflicts in 351 naming. Our Example plugin will demonstrate this by adding a secure form to the 352 "config_page", and handling the form on a separate page "config_update" that will 353 modify the value in the database, and redirect back to page "config_page", just 354 like any other form and action page in MantisBT: 355 </para> 356 357 <programlisting><filename>Example/pages/config_page.php</filename> 358 359 <form action="<?php echo plugin_page( 'config_update' ) ?>" method="post"> 360 <?php echo form_security_field( 'plugin_Example_config_update' ) ?> 361 362 <label>Foo or Bar?<br/><input name="foo_or_bar" value="<?php echo string_attribute( $t_foo_or_bar ) ?>"/></label> 363 <br/> 364 <label><input type="checkbox" name="reset"/> Reset</label> 365 <br/> 366 <input type="submit"/> 367 368 </form> 369 </programlisting> 370 371 <programlisting><filename>Example/pages/config_update.php</filename> 372 373 <?php 374 form_security_validate( 'plugin_Example_config_update' ); 375 376 $f_foo_or_bar = gpc_get_string( 'foo_or_bar' ); 377 $f_reset = gpc_get_bool( 'reset', false ); 378 379 if ( $f_reset ) { 380 plugin_config_delete( 'foo_or_bar' ); 381 } else { 382 if ( $f_foo_or_bar == 'foo' || $f_foo_or_bar == 'bar' ) { 383 plugin_config_set( 'foo_or_bar', $f_foo_or_bar ); 384 } 385 } 386 387 form_security_purge( 'plugin_Example_config_update' ); 388 print_successful_redirect( plugin_page( 'foo', true ) ); 389 </programlisting> 390 391 <para> 392 Note that the <function>form_security_*()</function> functions are part of the 393 form API, and prevent CSRF attacks against forms that make changes to the system. 394 </para> 395 </sect2> 396 397 <sect2 id="dev.plugins.building.language"> 398 <title>Language and Localization</title> 399 400 <para> 401 MantisBT has a very advanced set of localization tools, which allow all parts of of 402 the application to be localized to the user's preferred language. This feature has 403 been extended for use by plugins as well, so that a plugin can be localized in much 404 the same method as used for the core system. Localizing a plugin involves creating 405 a language file for each localization available, and using a special API call to 406 retrieve the appropriate string for the user's language. 407 </para> 408 409 <para> 410 All language files for plugins follow the same format used in the core of MantisBT, 411 should be placed in the plugin's <filename>lang/</filename> directory, and named 412 the same as the core language files. Strings specific to the plugin should be 413 "namespaced" in a way that will minimize any risk of collision. Translating the 414 plugin to other languages already supported by MantisBT is then as simple as 415 creating a new strings file with the localized content; the MantisBT core will find 416 and use the new language strings automatically. 417 </para> 418 419 <para> 420 We'll use the "configuration" pages from the previous examples, and dress them up 421 with localized language strings, and add a few more flourishes to make the page act 422 like a standard MantisBT page. First we need to create a language file for English, 423 the default language of MantisBT and the default fallback language in the case that 424 some strings have not yet been localized to the user's language: 425 </para> 426 427 <programlisting><filename>Example/lang/strings_english.txt</filename> 428 429 <?php 430 431 $s_plugin_Example_configuration = "Configuration"; 432 $s_plugin_Example_foo_or_bar = "Foo or Bar?"; 433 $s_plugin_Example_reset = "Reset Value"; 434 </programlisting> 435 436 <programlisting><filename>Example/pages/config_page.php</filename> 437 <?php 438 439 html_page_top( plugin_lang_get( 'configuration' ) ); 440 $t_foo_or_bar = plugin_config_get( 'foo_or_bar' ); 441 442 ?> 443 444 <br/> 445 446 <form action="<?php echo plugin_page( 'config_update' ) ?>" method="post"> 447 <?php echo form_security_field( 'plugin_Example_config_update' ) ?> 448 <table class="width60"> 449 450 <tr> 451 <td class="form-title" rowspan="2"><?php echo plugin_lang_get( 'configuration' ) ?></td> 452 </tr> 453 454 <tr <?php echo helper_alternate_class() ?>> 455 <td class="category"><php echo plugin_lang_get( 'foo_or_bar' ) ?></td> 456 <td><input name="foo_or_bar" value="<?php echo string_attribute( $t_foo_or_bar ) ?>"/></td> 457 </tr> 458 459 <tr <?php echo helper_alternate_class() ?>> 460 <td class="category"><php echo plugin_lang_get( 'reset' ) ?></td> 461 <td><input type="checkbox" name="reset"/></td> 462 </tr> 463 464 <tr> 465 <td class="center" rowspan="2"><input type="submit"/></td> 466 </tr> 467 468 </table> 469 </form> 470 471 <?php 472 473 html_page_bottom(); 474 </programlisting> 475 476 <para> 477 The two calls to <function>html_page_top()</function> and 478 <function>html_page_bottom()</function> trigger the standard MantisBT header and 479 footer portions, respectively, which also displays things such as the menus and 480 triggers other layout-related events. <function>helper_alternate_class()</function> 481 generates the CSS classes for alternating row colors in the table. The rest of the 482 HTML and CSS follows the "standard" MantisBT markup styles for content and layout. 483 </para> 484 </sect2> 485 486 </sect1>
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Thu Jul 28 15:48:31 2011 | Cross-referenced by PHPXref 0.7 |