[ Index ]

PHP Cross Reference of MantisBT

title

Body

[close]

/docbook/Developers_Guide/en-US/ -> Plugins_Building.xml (source)

   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  &lt;?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, &lt;= 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  &lt;?php
 136  echo '&lt;p&gt;Here is a link to &lt;a href="', plugin_page( 'foo' ), '">page foo&lt;/a&gt;.&lt;/p&gt;';
 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  &lt;?php
 158  echo '&lt;p&gt;Here is a link to &lt;a href="', plugin_page( 'foo' ), '">page foo&lt;/a&gt;.&lt;/p&gt;';
 159  echo '&lt;link rel="stylesheet" type="text/css" href="', plugin_file( 'foo.css' ), '"/&gt;',
 160       '&lt;p class="foo"&gt;This is red text.&lt;/p&gt;';
 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  &lt;?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  &lt;?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  &lt;?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  &lt;?php
 291  echo '&lt;p&gt;Here is a link to &lt;a href="', plugin_page( 'foo' ), '">page foo&lt;/a&gt;.&lt;/p&gt;';
 292       '&lt;link rel="stylesheet" type="text/css" href="', plugin_file( 'foo.css' ), '"/&gt;',
 293       '&lt;p class="foo"&gt;';
 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, '&lt;/p&gt;';
 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  &lt;?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  &lt;form action="&lt;?php echo plugin_page( 'config_update' ) ?&gt;" method="post"&gt;
 360  &lt;?php echo form_security_field( 'plugin_Example_config_update' ) ?&gt;
 361  
 362  &lt;label&gt;Foo or Bar?&lt;br/&gt;&lt;input name="foo_or_bar" value="&lt;?php echo string_attribute( $t_foo_or_bar ) ?&gt;"/&gt;&lt;/label&gt;
 363  &lt;br/&gt;
 364  &lt;label&gt;&lt;input type="checkbox" name="reset"/&gt; Reset&lt;/label&gt;
 365  &lt;br/&gt;
 366  &lt;input type="submit"/&gt;
 367  
 368  &lt;/form&gt;
 369              </programlisting>
 370  
 371              <programlisting><filename>Example/pages/config_update.php</filename>
 372  
 373  &lt;?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  &lt;?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  &lt;?php
 438  
 439  html_page_top( plugin_lang_get( 'configuration' ) );
 440  $t_foo_or_bar = plugin_config_get( 'foo_or_bar' );
 441  
 442  ?&gt;
 443  
 444  &lt;br/&gt;
 445  
 446  &lt;form action="&lt;?php echo plugin_page( 'config_update' ) ?&gt;" method="post"&gt;
 447  &lt;?php echo form_security_field( 'plugin_Example_config_update' ) ?&gt;
 448  &lt;table class="width60"&gt;
 449  
 450  &lt;tr&gt;
 451      &lt;td class="form-title" rowspan="2"&gt;&lt;?php echo plugin_lang_get( 'configuration' ) ?&gt;&lt;/td&gt;
 452  &lt;/tr&gt;
 453  
 454  &lt;tr &lt;?php echo helper_alternate_class() ?&gt;&gt;
 455      &lt;td class="category"&gt;&lt;php echo plugin_lang_get( 'foo_or_bar' ) ?&gt;&lt;/td&gt;
 456      &lt;td&gt;&lt;input name="foo_or_bar" value="&lt;?php echo string_attribute( $t_foo_or_bar ) ?&gt;"/&gt;&lt;/td&gt;
 457  &lt;/tr&gt;
 458  
 459  &lt;tr &lt;?php echo helper_alternate_class() ?&gt;&gt;
 460      &lt;td class="category"&gt;&lt;php echo plugin_lang_get( 'reset' ) ?&gt;&lt;/td&gt;
 461      &lt;td&gt;&lt;input type="checkbox" name="reset"/&gt;&lt;/td&gt;
 462  &lt;/tr&gt;
 463  
 464  &lt;tr&gt;
 465      &lt;td class="center" rowspan="2"&gt;&lt;input type="submit"/&gt;&lt;/td&gt;
 466  &lt;/tr&gt;
 467  
 468  &lt;/table&gt;
 469  &lt;/form&gt;
 470  
 471  &lt;?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>


Generated: Thu Jul 28 15:48:31 2011 Cross-referenced by PHPXref 0.7