ITI Logo

CMSB Plugins

Courtesy of CanadianDomainRegistry.ca

This email address in protected with ELinks ©

Last Updated Feb 15, 2013

Plugins Overview Interactive Tools does not provide a Plugins Tutorial or HowTo of any value so I created this document for my own use. Having said that, I should add that IA's CMSB program from a developers percpective (ie my percpective) is outstanding. Support via the forums is timely, and contributed documentation by others make up for any short falls in IA's documentation. The "plugins" concept provides a means of customization that will never be over written by a CMSB program upgrade or new release. This sets it apart from other software in that your customization isn't lost if/when you upgrade. My most recent plugins include code generators which like regular plugins simply make life easier. This is all work in progress, created for my own use, that I'm happy to share with you. @ToDo Add step by step example of a plugin @ToDo Add step by step example of a code generator I'll start with "What you should know before getting started". Actions and Filters
      There are 3 types of Plugin Operations: i) Actions, ii) Filters and iii) a combination of Actions and Filters.
      
      The fundamental difference between an Action and a Filter is:
      
      i)  Actions do not return a value. doAction("hookName"[, $arg [,$arg, ...]]);
          When doAction is encountered the hookName functions are executed which perform operations that do not return any results.
          doAction() may or may not have any input arguments.
          
      ii) Filters do return a value. applyFilters("hookName"[, $variable [,$arg, $arg,...]]);
          When applyFilters is encountered the $variable is evaluated by the plugin(s) that reference "hookName".
          The plugin is expected to return a value. The filter may return a empty() value if that is the desired result.
          applyFilters() will usually have an input variable although it may not.
      
  Note:
  If the hookName doesn't exist, for instance when the plugin is not active, doAction() simply returns (without error),
  where as applyFilters() returns the input variable's value unchanged.
  The net affect is as if the Action of Filter doesn't exist.
addAction() and addFilter()
      The CMSB source code for these functions is in lib/plugin_funtions.php:
      
      // alias for add_action
      function addFilter($hookName, $functionName, $priority = 10, $acceptedArgs = 1) {
        return addAction($hookName, $functionName, $priority, $acceptedArgs);
      }
      
      //
      function addAction($hookName, $functionName, $priority = 10, $acceptedArgs = 1) {
        if (!$hookName)     { die(__FUNCTION__ . ": No hookname specified!"); }
        if (!$functionName) { die(__FUNCTION__ . ": No functioname specified!"); }
      
        // add actions
        $actionList = &_getActionList();
        $actionList[$hookName][$priority][$functionName] = $acceptedArgs;
      }
      

As seen in the source code, these functions do exactly the same thing but have fundamentaly different meanings when used to build a plugin.
Use the one that represents the functions return paramater as explained in the previous section.

The first paramater "$hookName" is the sting value of any one of the HookNames found in "Admin > Plugins > Developer's Plugin Hook List" list.

The second paramater "$functionName" is the string value of the name of your custom function. It must be unique or a php error will occur.

The third paramater "$priority", an integer, is used to decide the order of the doAction("hookName") or applyFilters("hookName") when there is more than one call to the same "hookName".
The actions and filters are processed in order of lowest to highest priority.
e.g. doAction("init_complete","functionA") would be proccessed after doAction("init_complete","functionB",5) since "functionA" has a default priority of 10.

The fourth paramater "$acceptedArgs" is the number of paramaters the filter or action is passing to the plugin.
You will need to look up the "hookName" in the CMSB source file and count the number of params being used. The "$acceptedArgs" value should be the total number of args listed minus 1. Minus one because the first paramater is the filter or action "hookName" and is not an argument paramater passed to your plugin function.

      
Usage based on the operation being performed:

If the plugin is expected to return a value, use: addFilter("hookName","yourFunctionName",null, 1).

If your plugin is intended to, for example, modify the values of an array before it's accessed, use: addAction("hookName","yourFunctionName",null,1).
In this case the plugin isn't returning any thing but instead modifying an array for later use.

The Plugin Header The plugin file "Header" aka "Doc Comment"
      Header fields and descriptions used by CMSB are in plugin_functions.php, function getPluginData(), (CMSB version 2.08)
      
      $textKeyToFieldname['Plugin Name']            = 'name';            // shown in plugin menu
      $textKeyToFieldname['Plugin URI']             = 'uri';             // shown in plugin menu (turns "Plugin Name" into an anchor tag)
      $textKeyToFieldname['Version']                = 'version';         // shown in plugin menu
      $textKeyToFieldname['Description']            = 'description';     // shown in plugin menu
      $textKeyToFieldname['Author']                 = 'author';          // shown in plugin menu
      $textKeyToFieldname['Author URI']             = 'authorUri';       // shown in plugin menu (turns the "Author" into an anchor tag)
      #$textKeyToFieldname['Text Domain']           = 'textDomain';      // not yet used
      #$textKeyToFieldname['Domain Path']           = 'domainPath';      // not yet used
      $textKeyToFieldname['Requires at least']      = 'requiresAtLeast'; // minimum CMS version to be able to activate the plugin
      $textKeyToFieldname['Required System Plugin'] = 'isSystemPlugin';  // always load this plugin - can't be de-activated
      
      At the top of your plugin file create the header. The required lines are in red, all others may be omitted.
      
      <?php
      /*
       Plugin Name: Plugin name 
       Description: Plugin description.
       Plugin URI: http://.... Possible usage would be a link to a full description of the plugin
       Author: Your Name
       Author URI: http://www.example.com/about_me' target='_blank  (notice the single quotes used to make link open in a new window)
       Version: 1.0
       Requires at least: 2.00   // required only if your plugin would fail on an earlier version of CMSB
       Required System Plugin: 0    // optional [ 0 || 1*] (default is 0) (if 1 then "Requires at least:" is ignored)
       */ 
      
  "Plugin URI" and "Author URI":
  When the URI's are parsed they are enclosed in a set up single quotes representing the href value of an anchor tag.
  It is possible therefore to add additional anchor attributes by ending your URI value with a single quote and starting the next attribute.
  eg. Author URI: http://www.example.com/about_me'   target='_blank'   tilte='Title Text
  NOTE: Do not add a single quote to the end of the line.
  * System plugins are not listed in the "settings.dat.php" file.    They are loaded automatically by virtue of their existence in the plugins directory.
Creating a Plugin 1. Create a *.php file in the plugins directory and give it an informative name. eg. _modify_header.php 2. If you use Zend Studios, use zend editor doc comment notation so mouse over information will be readable in the zend editor sidebar. (End the "Description" line with a period to show the "Plugin Name" and "Description" in the zend editor.) 3. If activated in the Plugins Menu, these hookNames (and functions) will be made available to CMSB script files. 4. Since CMSB parses the plugins directory don't put any unneccessary *.php files in the plugins directory. Have a look at the next section to see the technique I use for managing plugins.
        <?php 
          /** 
           * Plugin Name: Plugin name 
           * Description: Plugin description.
           * Version: 1.0
           * Requires at least: 2.00 
           */ 
          
         addFilter("menulinks_array", "_menulinks_array",null,1);
         addAction("sort_menulinks_array", "_sort_menulinks_array",null,1);
         
          /**
           * Zend Editor Description
           *
           * @param Mixed $input
           * @return Mixed
           */
          function _menulinks_array($inputArray) {
            doAction("sort_menulinks_array", $inputArray);
            $var = array_shift($inputArray);
            return $var;
          } 
          /**
           * The Function Description
           *
           */
          function _sort_menulinks_array(&$inputArray) { 
            ksort($inputArray);
            // done
          } 
      ?>
      

The addFilter() Paramaters

Arg 1 (String). The Hook name "menulinks_array" is a pre set Hook Name in CMSB and is found in "lib/menus/header_functions.php".
Refer to "Admin > Plugins > Developer's Plugin Hook List" for a list of all the Hook Names.

Arg 2 (String). "_menulinks_array" is the name of your custom function.

Arg 3 (Integer). The third paramater "null" is the filter priority.
Since "null" is being passed in this will default to 10 which is the addAction() function's $priority default value.

Arg 4 (Integer). The fouth paramater is the number of arguments the hook "menulinks_array" is passing to the plugin.
After locating the filter in "header_functions.php", "applyFilters('menulinks_array', $menus)" we see their is only 1 (one) plugin argument, "$menus".
Remember, the first arg "menulinks_array" is the filter HookName and not considered a plugin paramater argument.

addAction()
In this example, addAction() uses a Hook Name created by you, that being "_sort_menulinks_array".
Notice that this Action Hook is activated in "function _menulinks_array()" by the line: doAction("sort_menulinks_array", $inputArray);

        Caution:
        There should not be any additional white space after the closing php ?> tag.
        Turn on line numbering if your editor supports this feature, other wise place your cursor after the 
        closing php tag to check for blank lines.
        eg. consider the following :
        1. <?php
        2. /**
        3.  * Plugin Name: Plugin name 
           ...
        9.  */
        10. ... your code
            ...
        23. ... end of your code
        24. // the next line tells PHP to return to HTML mode.
        25. ?>
        26.
        27.
        28.
        
        The white space that appears on lines 27 and 28 will generate a php headers_sent() error and a 
        return value of true in some circumstances.
        
        For example, if you are using "Interactive Tools - Instant Website" the page may not display 
        "for some site visitors", not all, instead they will see the message:
        "You must load (__FILE__) at top of file!  Make sure there are no spaces before it!"
        The space being refered to is actually at the end of the plugin (which is before the __FILE__ is loaded)
        
This error is extremely hard to reproduce during development and is inconsistent on a production server, however it is suffice to say it will not occur if you make sure you delete all white space after the closing php tag (after line 26 in the above example).
(I'm not sure if this should be refered to as a bug or quirk but it is good programming to remove all white space anyway.)
This is OK. 24. ... 25. ?> 26.
        If you are using Interative Tools Instant Website you should do the following in "website_init.php":
        1.  On line 11, comment out:
            // if (headers_sent()){ die("You must load " .basename(__FILE__). " at top of file!
            // Make sure there are no spaces before it!\n"); }
            
            This will prevent displaying the error message and the page will load and display as expected.
            
        2.  Or comment it out and send your self an email error message instead.
            if(headers_sent($file,$line)) { 
              $to = $SETTINGS['adminEmail'];
              $headers = "From: " . $SETTINGS['adminEmail'];
              $subject = "Headers Sent on ".$_SERVER['SERVER_NAME'];
              $msg = "Headers already sent accessing: ".$_SERVER['SERVER_NAME'].
                     "\nIn $file, on line $line, from IP: ".$_SERVER['REMOTE_ADDR'];
              mail($to,$subject,$msg,$headers);
            }
        
        This will also prevent displaying the error message and the site will load as expected but will also send you an
        email alerting you the file that the has additional white space.
        
        The above also applies if you are calling headers_sent() in any custom scripts you have created.
        

Hook/Task referenceHook Reference with Task Descriptions
Hook List (CMSB 2.51) Task Action/Filter hook paramaters... By
1. admin_footer
lib/menus/footer.php
Add content to the bottom of the HTML file (the footer) addAction ( 'admin_footer', 'my_function', null, 0 );
doAction('admin_footer');
ITI
2. admin_head
lib/menus/header.php
Add content in the head section of the HTML file. addAction ( 'admin_head', 'my_function', null, 0 );
doAction('admin_head');
ITI
3. admin_postlogin
admin.php
Do something after user authenticates addAction ( 'admin_postlogin', 'my_function', null, 0 );
doAction('admin_postlogin');
ITI
4. admin_prelogin
admin.php
Do something before user authenticates addAction ( 'admin_prelogin', 'my_function', null, 0 );
doAction('admin_prelogin');
ITI
5. backupDatabase_skippedTables
lib/database_functions.php
  addFilter ( 'backupDatabase_skippedTables', 'my_function', null, 1 );
$skippedTables = applyFilters('backupDatabase_skippedTables', $skippedTables);
 
6. edit_advancedCommands
lib/menus/default/edit.php
  addFilter ( 'edit_advancedCommands', 'my_function', null, 1 );
$advancedCommands = applyFilters('edit_advancedCommands', $advancedCommands);
 
7. edit_buttonsRight
lib/menus/default/edit.php
  addFilter ( 'edit_buttonsRight', 'my_function', null, 3 );
$buttonsRight = applyFilters('edit_buttonsRight', $buttonsRight, $tableName, $GLOBALS['RECORD']);
 
8. edit_fieldSchema
lib/menus/default/edit_functions.php
Modify schema record values before proccessing HTML in "edit" view. addFilter ( 'edit_fieldSchema', 'my_function', null, 2 );
$fieldSchema = applyFilters('edit_fieldSchema', $fieldSchema, $tableName);
ITI
9. edit_show_field
lib/menus/default/edit_functions.php
Modify the HTML ouput of a schema record in "edit" view. addFilter ( 'edit_show_field', 'my_function', null, 3 );
if (!applyFilters('edit_show_field', true, $fieldSchema, $record)) { continue;
ITI
10. edit_show_upload_link
lib/menus/default/edit_functions.php
  addFilter ( 'edit_show_upload_link', 'my_function', null, 3 );
$displayDefaultLink = applyFilters('edit_show_upload_link', true, $fieldSchema, $record);
 
11. emailTemplate_addDefaults
lib/common.php
  addAction ( 'emailTemplate_addDefaults', 'my_function', null, 0 );
doAction('emailTemplate_addDefaults');
 
12. execute_seconds
lib/menus/footer.php
  addFilter ( 'execute_seconds', 'my_function', null, 1 );
echo applyFilters('execute_seconds', $executeSecondsString);
 
13. header_links
lib/menus/sidebar.php
Modify links in the CMSB left frame (the side bar) addFilter ( 'header_links', 'my_function', null, 1 );
echo applyFilters('header_links', $headerLinks);
ITI
14. home_content
lib/menus/home.php
Modify the content that appears after login on the home page addFilter ( 'home_content', 'my_function', null, 1 );
$content = applyFilters('home_content', $content);
ITI
15. home_title
lib/menus/home.php
Modify the title that appears after login on the home page addFilter ( 'home_title', 'my_function', null, 1 );
$title = applyFilters('home_title', $title);
ITI
16. init_complete
lib/init.php
Do something after CMSB has been initialized (before admin_prelogin) addAction ( 'init_complete', 'my_function', null, 0 );
doAction('init_complete');
ITI
17. listHeader_checkAll
lib/menus/default/list_functions.php
  addFilter ( 'listHeader_checkAll', 'my_function', null, 2 );
$html = applyFilters('listHeader_checkAll', $html, $tableName);
 
18. listHeader_displayLabel
lib/menus/default/list_functions.php
  addFilter ( 'listHeader_displayLabel', 'my_function', null, 3 );
$label = applyFilters('listHeader_displayLabel', $label, $tableName, $fieldname);
 
19. listHeader_thAttributes
lib/menus/default/list_functions.php
  addFilter ( 'listHeader_thAttributes', 'my_function', null, 3 );
$thAttrs = applyFilters('listHeader_thAttributes', $thAttrs, $tableName, $fieldname);
 
20. listPage_footer
lib/menus/default/list.php
  addFilter ( 'listPage_footer', 'my_function', null, 1 );
applyFilters('listPage_footer', $tableName);
 
21. listRow_actionLinks
lib/menus/default/list_functions.php
  addFilter ( 'listRow_actionLinks', 'my_function', null, 3 );
$actionLinks = applyFilters('listRow_actionLinks', $actionLinks, $tableName, $record);
 
22. listRow_displayValue
lib/menus/default/list_functions.php
  addFilter ( 'listRow_displayValue', 'my_function', null, 4 );
$displayValue = applyFilters('listRow_displayValue', $displayValue, $tableName, $fieldname, $record);
 
23. listRow_tdAttributes
lib/menus/default/list_functions.php
  addFilter ( 'listRow_tdAttributes', 'my_function', null, 4 );
$tdAttributes = applyFilters('listRow_tdAttributes', $tdAttributes, $tableName, $fieldname, $record);
 
24. listRow_trStyle
lib/menus/default/list_functions.php
  addFilter ( 'listRow_trStyle', 'my_function', null, 3 );
$trStyle = applyFilters('listRow_trStyle', '', $tableName, $record);
 
25. list_advancedCommands
lib/menus/default/list.php
  addFilter ( 'list_advancedCommands', 'my_function', null, 1 );
$advancedCommands = applyFilters('list_advancedCommands', $advancedCommands);
 
26. list_buttonsRight
lib/fieldtypes/relatedRecords.php
lib/menus/default/list.php
  addFilter ( 'list_buttonsRight', 'my_function', null, 3 );
$buttonsRight = applyFilters('list_buttonsRight', $buttonsRight, $tableName, $isRelatedTable);
 
27. list_orderBy
lib/menus/default/list_functions.php
  addFilter ( 'list_orderBy', 'my_function', null, 2 );
$orderBy = applyFilters('list_orderBy', $orderBy, $tableName);
 
28. list_postAdvancedSearch
lib/menus/default/list.php
  addAction ( 'list_postAdvancedSearch', 'my_function', null, 1 );
doAction('list_postAdvancedSearch', @$_REQUEST['menu']);
 
29. list_postselect
lib/menus/default/list.php
  addAction ( 'list_postselect', 'my_function', null, 3 );
doAction('list_postselect', $records, $listFields, $metaData);
 
30. list_where
lib/menus/default/list_functions.php
  addFilter ( 'list_where', 'my_function', null, 2 );
$accessWhere = applyFilters('list_where', $accessWhere, $tableName);
 
31. login_content
lib/menus/login.php
  addFilter ( 'login_content', 'my_function', null, 1 );
$content = applyFilters('login_content', $content);
 
32. login_isValidLogin
lib/login_functions.php
  addFilter ( 'login_isValidLogin', 'my_function', null, 3 );
list($isValidLogin, $user, $updateLastLogin) = applyFilters('login_isValidLogin', array($isValidLogin, $user, $updateLastLogin));
 
33. menulinks_array
lib/menus/header_functions.php
  addFilter ( 'menulinks_array', 'my_function', null, 1 );
$menus = applyFilters('menulinks_array', $menus);
 
34. menulinks_rowHtml
lib/menus/header_functions.php
Modify the menu links in the left side bar addFilter ( 'menulinks_rowHtml', 'my_function', null, 2 );
$rowHtml = applyFilters('menulinks_rowHtml', $rowHtml, $row);
ITI
35. plugin_actions
lib/menus/admin/plugins.php
  addAction ( 'plugin_actions', 'my_function', null, 1 );
doAction('plugin_actions', $pluginData['filename']);
 
36. plugin_activate
lib/plugin_functions.php
Do something when a plugin is being activated.
(i.e. when inactive and "Activate" is clicked in "Admin > Plugins")
addAction ( 'plugin_activate', 'my_function', null, 1 );
doAction( 'plugin_activate', $file );
ITI
37. plugin_deactivate
lib/plugin_functions.php
Do something when a plugin is being deactivated.
(i.e. when active and "Deactivate" is clicked in "Admin > Plugins")
addAction ( 'plugin_deactivate', 'my_function', null, 1 );
doAction( 'plugin_deactivate', $file );
ITI
38. record_access_where
lib/menus/default/actionHandler.php
lib/menus/default/list_functions.php
  addFilter ( 'record_access_where', 'my_function', null, 2 );
$accessWhere = applyFilters('record_access_where', $accessWhere, $tableName);
 
39. record_posterase
lib/menus/default/common.php
  addAction ( 'record_posterase', 'my_function', null, 2 );
doAction('record_posterase', $tableName, $recordNumsAsCSV);
 
40. record_postsave
lib/menus/default/save.php
Do something after saving the record to its database table addAction ( 'record_postsave', 'my_function', null, 4 );
doAction('record_postsave', $tableName, $isNewRecord, $oldRecord, $_REQUEST['num']);
ITI
41. record_preedit
lib/menus/default/edit.php
lib/menus/default/view.php
  addAction ( 'record_preedit', 'my_function', null, 2 );
doAction('record_preedit', $tableName, @$_REQUEST['num']);
 
42. record_preerase
lib/menus/default/common.php
  addAction ( 'record_preerase', 'my_function', null, 2 );
doAction('record_preerase', $tableName, $recordNumsAsCSV);
 
43. record_presave
lib/menus/default/save.php
Do something before saving the record to its database table. addAction ( 'record_presave', 'my_function', null, 3 );
doAction('record_presave', $tableName, $isNewRecord, $oldRecord);
ITI
44. record_save_errorchecking
lib/menus/default/save.php
  addAction ( 'record_save_errorchecking', 'my_function', null, 3 );
doAction('record_save_errorchecking', $tableName, $recordExists, $oldRecord);
 
45. record_save_posterrorchecking
lib/menus/default/save.php
  addAction ( 'record_save_posterrorchecking', 'my_function', null, 3 );
doAction('record_save_posterrorchecking', $tableName, $recordExists, $oldRecord);
 
46. record_saved_message
lib/menus/default/actionHandler.php
  addFilter ( 'record_saved_message', 'my_function', null, 3 );
$message = applyFilters('record_saved_message', $message, $tableName, $recordNum);
 
47. section_init
lib/menus/default/actionHandler.php
  addAction ( 'section_init', 'my_function', null, 2 );
doAction('section_init', $tableName, $action);
 
48. section_preDispatch
lib/menus/default/actionHandler.php
  addAction ( 'section_preDispatch', 'my_function', null, 2 );
doAction('section_preDispatch', $tableName, $action);
 
49. section_unknownAction
lib/menus/default/actionHandler.php
  addAction ( 'section_unknownAction', 'my_function', null, 2 );
doAction('section_unknownAction', $tableName, $action);
 
50. sendMessage
lib/common.php
  addFilter ( 'sendMessage', 'my_function', null, 2 );
$eventState = applyFilters('sendMessage', $eventState, $options);
 
51. ui_footer
lib/admin_functions.php
  addFilter ( 'ui_footer', 'my_function', null, 1 );
if (applyFilters('ui_footer', TRUE)) {
 
52. ui_header
lib/admin_functions.php
  addFilter ( 'ui_header', 'my_function', null, 1 );
if (applyFilters('ui_header', TRUE)) {
 
53. uploadModify_infoFieldHTML
lib/menus/default/uploadModify.php
  addFilter ( 'uploadModify_infoFieldHTML', 'my_function', null, 6 );
$fieldHTML = applyFilters('uploadModify_infoFieldHTML', $fieldHTML, $tableName, $fieldName, $infoFieldname, $formFieldName, $uploadRecord);
 
54. upload_adopted
lib/upload_functions.php
  addAction ( 'upload_adopted', 'my_function', null, 2 );
doAction('upload_adopted', $tableName, $newRecordNum);
 
55. upload_save
lib/upload_functions.php
  addAction ( 'upload_save', 'my_function', null, 4 );
doAction('upload_saved', $tablename, $fieldname, $recordNum, $newUploadNum);
 
56. upload_saveAsFilename
lib/upload_functions.php
  addFilter ( 'upload_saveAsFilename', 'my_function', null, 3 );
$saveAsFilename = applyFilters('upload_saveAsFilename', $saveAsFilename, $uploadedAsFilename, $uploadDir);
 
57. upload_saved
lib/upload_functions.php
  addAction ( 'upload_saved', 'my_function', null, 4 );
doAction('upload_saved', $tablename, $fieldname, $recordNum, $newUploadNum);
 
58. upload_thumbnail_save
lib/upload_functions.php
  addAction ( 'upload_thumbnail_save', 'my_function', null, 4 );
doAction('upload_thumbnail_save', array($tableNameWithoutPrefix, $_REQUEST['fieldname'], $thumbNum, $thumbSavePath));
 
59. userHasFieldAccess
lib/user_functions.php
  addFilter ( 'userHasFieldAccess', 'my_function', null, 2 );
$hasAccess = applyFilters('userHasFieldAccess', $hasAccess, $fieldSchema);
 
60. userSectionAccess
lib/user_functions.php
  addFilter ( 'userSectionAccess', 'my_function', null, 2 );
$accessLevel = applyFilters('userSectionAccess', $accessLevel, $tableName);
 
61. view_buttonsRight
lib/menus/default/view.php
  addFilter ( 'view_buttonsRight', 'my_function', null, 3 );
$buttonsRight = applyFilters('view_buttonsRight', $buttonsRight, $tableName, $GLOBALS['RECORD']);
 
62. viewerOnly_allowed_actions
lib/menus/default/actionHandler.php
  addFilter ( 'viewerOnly_allowed_actions', 'my_function', null, 1 );
$validActions = applyFilters('viewerOnly_allowed_actions', $validActions);
 
63. viewer_link_field_content
lib/viewer_functions.php
  addFilter ( 'viewer_link_field_content', 'my_function', null, 3 );
$filenameValue = applyFilters('viewer_link_field_content', $filenameValue, $fieldValue, $record);
 
64. viewer_output_rows
lib/viewer_functions.php
  addFilter ( 'viewer_output_rows', 'my_function', null, 3 );
$rows = applyFilters('viewer_output_rows', $rows, $listDetails, $schema);
 
65. viewer_postinit
lib/viewer_functions.php
  addAction ( 'viewer_postinit', 'my_function', null, 0 );
doAction('viewer_postinit');
 

Plugin DirectoryMy Plugin Directory Structure

As mentioned earlier, CMSB reads and parses all files in the "plugins" directory as well as 2 sub-directories below it.
For every file it reads, a regular expression search is performed looking for the key words listed in the "plugins Header" section above.
This happens 7 times in every file (once for each key word). In the case of a missing optional key word the entire file is searched from beginning to end.
When the first occurence of any "header-key-word & value" is matched, the searching stops and its value is stored and displayed in CMSB's Admin plugins list.

There is a lot of unnecessary file reading and proccessing which can easily be eliminated with the technique I use:

        cmsAdmin
            |
            3rdParty
            |
            data
            |
            lib
            |
            plugins - addApplyButton.php
            |       - displayDemoNotice.php
            |       - DirA - myPluginX1.php
            |              - DirB - myPluginX2php
            |                     - myPluginX3.php
            |                     - DirC - otherFile.php < This directory will not be read by CMSB as it is 3 levels deep.
            |
            plugin-files - addApplyButton.php
            |            - displayDemoNotice.php
            |            - DirA - myPluginX1.php
            |                   - DirB - myPluginX2.php
            |                          - myPluginX3.php
            |
            ...
       
      

   The original plugin files are moved to the new "plugin-files" directory.

   A new file is created in the CMSB "plugins" directory with the same name as the plugin file.
   Copy only the header information from the plugin and add one line of code linking it to the file in the "plugin-files" directory.

The new "addApplyButton.php" file in the CMSB "plugins" directory now looks like this:
      <?php
        /*
        Plugin Name: Add Apply Button
        Description: Adds an "Apply" button beside "Save" buttons
        Author: Chris
        Version: 0.02
        Requires at least: 2.06
        */
        require_once(str_ireplace('plugins','plugin-files',__FILE__));
      ?>
Note: The action/filter lines:
     addFilter("menulinks_array", "_menulinks_array",null,1);
     addAction("sort_menulinks_array", "_sort_menulinks_array",null,1);
Should be in the require_once() file to be correctly referenced in the "Where it's used..." column of the "Developer's Plugin Hook List"

Exception: Plugin Code Generators
If you create your own Code Generators the filter "addGenerator(...)" must be in plugin header file not in the plugin-files dir.
/*
...
*/
addGenerator("functionName", "name", "description);
require_once(str_ireplace('plugins','plugin-files',__FILE__));
As you can see the entire file consists of only the Doc Comment and a single line linking it to the original file.
  There are 3 reasons why I use this technique.
  1. A more efficient means of loading and listing the plugins within the CMSB Admin Interface
  2. When performing an upgrade the plugin-files directory containing the actual plugin code will not be over written thus preserving any customization.
  3. Every click on every page by every site visitor parses the plugin files ...
  Certainly one could argue with todays processing power that loading large files is irrelevant but why load a file containing of 1000's of lines of code and parse it 7 times
  when all you ever need is the first half dozen lines from the original file.

  The second reason is self explanatory especially if you don't backup your files regularly.

  As for the third reason, I can only imagine the wasted proccessing that would occur on High volume site.
  And what's worse is that in all likely hood none of the plugins are needed to view the web pages.
  (I actually use a seperate file of viewing functions that does not require the plugin files to be loaded/parsed to eliminate this issue when viewing website pages.)

  The example file I used above is quite small and really irrelevant. However, I have and am working on a lot of different plugins for different purposes.
  My plugins vary in size from 300 to 1000 lines each. When you add it up, that's a lot of unnecessary parsing (times 7)!

  Your feedback is welcome. (Send a note.)
Appendix
CMSB Errors in version 2.11 (and earlier)
    Upgrade Notice:
    This bug has been fixed in CMSB version 2.12.
    If you are using a version earlier than 2.12 you should make the changes indicated below or upgrade to CMSB 2.12 or newer.
As of CMSB ver 2.09 the plugins page contains a link for displaying all hooks and filters.
    As of CMSB ver 2.11 the "Developer's Plugin Hook List" only lists actions and filters that have been enclosed in "single quotes". 

    eg. The following will appear in the list	
    addAction('admin_postlogin','_admin_postlogin');
    
    where as this will not appear in the list:	
    addAction("admin_postlogin","_admin_postlogin");
    
    The reason is that the array of hookTypes is only looking for single quotes. 
    // top of lib/menus/admin/pluginHooks.php 
      $hookTypes = array( 
        'filter' => array( 'callerRegex' => "|applyFilters\(\s*'(.*?)'|", 
                           'pluginRegex' => "|addAction\('(.*?)'|" ), << in wrong array
        'action' => array( 'callerRegex' => "|doAction\(\s*'(.*?)'|", 
                           'pluginRegex' => "|addFilter\('(.*?)'|" ), << in wrong array
      );
    
    The following modification will find either single or double quotes and list it with the correct "type":
      $hookTypes = array( 
        'filter' => array( 'callerRegex' => "|applyFilters\(\s*['\"](.*?)['\"]|", 
                           'pluginRegex' => "|addFilter\(['\"](.*?)['\"]|" ), 
        'action' => array( 'callerRegex' => "|doAction\(\s*['\"](.*?)['\"]|", 
                           'pluginRegex' => "|addAction\(['\"](.*?)['\"]|" ), 
      );

    While your at it, you may want to add the counter value at the beginning of the line since the $counter variable is set anyway. 
     //Line 126 in CMSB ver 2.11: 
    <?php echo htmlspecialchars($hookName); ?> 
    // changed to: 
    <?php echo htmlspecialchars("[".$counter."] ".$hookName); ?>
    
    The plugins list will now be numbered as: 
    Hook Name                Type        ..... 
    [1] admin_footer         action
    [2] admin_head           action
    [3] admin_postlogin      action
    ... etc 
    It would make references to hooks a little easier since the list can be quite lengthy.