The Basic Meeting List Toolbox

The BMLT Satellite Base Class

SYNOPSYS

The BMLT Satellite Base Class is a PHP class that has been designed specifically to provide a consistent, powerful engine for implementation of a BMLT Satellite.

All of the standard BMLT Satellites are derived from this class, and the class implements all of the various shortcodes. If you create a satellite based on the BMLT Satellite Base Class, you automatically get all of these capabilities.

Additionally, the Satellite Base Class provides a gorgeous, AJAX-driven administration console.

WHERE IT LIVES

You can get the BMLT Satellite Base Class from its Git repo. There is a zipped download available.

Please note that this class uses a Git submodule. This means that if you clone the Git repo, or use the Download Repository” link, you will not automatically get the BMLT Satellite Driver submodule. Just use the zipped download.

BASIC STRUCTURE

PMThe BMLT Satellite Base Class uses a Presentation Model Pattern. This is different from the more common MVC Pattern, because it aggregates the Controller layer with portions of both the Model and View Layers, yet also exports portions of the Model and View.

Basically, it contains the entire Controller, about 75% of the View, and about 90% of the Model.

It works by providing “hooks” to the coder that allows them to provide things like a storage system and a view rendering system. It also allows “routers” for things like AJAX calls.

USING THE BMLT SATELLITE BASE CLASS

In order to use the class, you need to derive a concrete subclass from it, and “fill in the blanks.”

You do that like so:

// Include the satellite driver class.
require_once ( dirname ( __FILE__ ).'/BMLT-Satellite-Base-Class/bmlt-cms-satellite-plugin.php' );
class BMLTWPPlugin extends BMLTPlugin
{
                          •
                          •
                          •
}

BMLTPlugin” is the base class. When you extend it, you subclass it.

BMLTPlugin is an abstract class, and has a number of functions which can be overridden, should be overridden, or are required to be overridden.

The code in this page will come from the WordPress CMS Plugin. You can see the code for that plugin in its WordPress SVN code repository.

REQUIRED CONSTRUCTOR

Your subclass must define a __contruct() function. At minimum, you need to call parent::__construct(); (PHP does not automatically call superclass constructors -you need to do it explicitly).

Here is the WordPress version of this function:

    function __construct ()
        {
        parent::__construct ();
        }

As you can see, all it does is call the parent constructor.

REQUIRED OVERRIDES (MODEL FUNCTIONS)

These are the functions that you MUST override in order for the class to work:

    cms_get_option ( $in_option_key );

    cms_set_option ( $in_option_key, $in_option_value );

    cms_delete_option ( $in_option_key );

These are the three Model Storage functions. The data is stored in a key/value pair form. There are only a couple of keys that are used: one key is the general “index,” and the other is actually an array of ID-sorted sets of key/value pairs that will contain the various option sets.

You don’t need to know what’s in the data. It’s an associative array that will be serialized when stored. The data is accessed by a key ($in_option_key), accompanied by an array of data ($in_option_value, in the cms_set_option function). Your class will be responsible for storing the array in a form that can be retrieved from its key.

When cms_set_option is called, it has the key, and the data the class wants stored. You need to store the value of $in_option_value in a persistent form that can be retrieved or deleted when referenced by the value in $in_option_key. Most CMSes already have this mechanism, and it’s very straightforward. You simply shuttle between the two APIs.

Here is the WordPress version of this function. It uses the WordPress Options API:

    protected function cms_set_option ( $in_option_key,   ///< The name of the option
                                        $in_option_value  ///< the values to be set (associative array)
                                        )
        {
        // Added a test, to prevent the creation of multiple empty settings by low-rank users trying SQL injections.
        if ( function_exists ( 'update_option' ) && is_array ( $in_option_value ) && count ( $in_option_value ) )
            {
            $ret = update_option ( $in_option_key, $in_option_value );
            }
        elseif ( is_array ( $in_option_value ) && count ( $in_option_value ) ) 
            {
            echo "";
            }
        }

If the class calls cms_get_option, it will provide a key in $in_option_key. You need to retrieve the stored data, and return it in the function return.

Here is the WordPress version of this function:

    protected function cms_get_option ( $in_option_key    ///< The name of the option
                                           )
        {
        $ret = $this->geDefaultBMLTOptions();
        
        if ( function_exists ( 'get_option' ) )
            {
            $ret = get_option ( $in_option_key );
            }
        else
            {
            echo "<!-- BMLTPlugin ERROR (cms_get_option)! No get_option()! -->";
            }
        
        return $ret;
        }

If the class calls cms_delete_option, it will provide a key in $in_option_key. You need to delete the stored data for the given key.

Here is the WordPress version of this function:

    protected function cms_delete_option ( $in_option_key   ///< The name of the option
                                        )
        {
        if ( function_exists ( 'delete_option' ) )
            {
            $ret = delete_option ( $in_option_key );
            }
        else
            {
            echo "<!-- BMLTPlugin ERROR (cms_delete_option)! No delete_option()! -->";
            }
        }

RECOMMENDED OVERRIDES (MODEL FUNCTIONS)

    get_ajax_base_uri();

    process_text ( $in_string );

    set_callbacks();

These are three functions that you may override. Especially, get_ajax_base_uri. This function will return the HTTP (not file) path to the page for use by AJAX callbacks. The default behavior does its best to guess, and that may be fine for some implementations. However, a lot of time, there may be a special URI that needs to be called for AJAX. In these cases, you would emit that from this function.

WordPress simply uses the default implementation of get_ajax_base_uri. That works fine.

process_text is used to process any text that will be sent to the screen. Most CMSes use a token-based system for localization (Here is the one WordPress uses). The text is passed through a function that can replace specific text with localized versions (We don’t use that system, ourselves, but we support the CMS in their efforts). In the process_text function, you would call whatever system text processor is used. Remember that you must return the processed string, not output it.

Here is the WordPress version of this function (“__()” is the WordPress text processing function):

    protected function process_text (  $in_string  ///< The string to be processed.
                                    )
        {
        if ( function_exists ( '__' ) )
            {
            $in_string = htmlspecialchars ( __( $in_string, 'BMLTPlugin' ) );
            }
        else
            {
            echo "<!-- BMLTPlugin Warning (process_text): __() does not exist! -->";
            }
            
        return $in_string;
        }

set_callbacks is called at class construction time (by the base class constructor, so that means that you need to call parent::__construct() in your own __construct() function). This is used to establish callbacks (WordPress, for example, asks that the handler functions all be registered in this function).

The Base Class version of this function is empty (does nothing). You should override it, and use the function to set up the various callbacks.

Here is the WordPress version of this function:

    protected function set_callbacks ( )
        {
        if ( function_exists ( 'add_filter' ) )
            {
            add_filter ( 'the_content', array ( self::get_plugin_object(), 'content_filter')  );
            add_filter ( 'wp_head', array ( self::get_plugin_object(), 'standard_head' ) );
            add_filter ( 'admin_head', array ( self::get_plugin_object(), 'admin_head' ) );
	    add_filter ( 'plugin_action_links', array ( self::get_plugin_object(), 'filter_plugin_actions' ), 10, 2 );
            }
        else
            {
            echo "<!-- BMLTPlugin ERROR (set_callbacks)! No add_filter()! -->";
            }
        
        if ( function_exists ( 'add_action' ) )
            {
            add_action ( 'pre_get_posts', array ( self::get_plugin_object(), 'stop_filter_if_not_main' ) );
            add_action ( "in_plugin_update_message-".$this->plugin_file_name, array ( self::get_plugin_object(), 'in_plugin_update_message' ) );
            add_action ( 'admin_init', array ( self::get_plugin_object(), 'admin_ajax_handler' ) );
            add_action ( 'admin_menu', array ( self::get_plugin_object(), 'option_menu' ) );
            add_action ( 'init', array ( self::get_plugin_object(), 'filter_init' ) );
            }
        else
            {
            echo "<!-- BMLTPlugin ERROR (set_callbacks)! No add_action()! -->";
            }
        }

RENDERING THE CONTENT

Assuming that you have your callbacks set up correctly, the CMS will ask the plugin to either display a page, or return the HTML for the page.

The routines in the Base Class return processed HTML, ready for output to the browser. They do not return an entire page. Instead, they generally return a <div> element, with the BMLT code inside.

In the case of the shortcodes, what the base class does, is actually search some content that is provided for the presence of shortcodes. If it finds a shortcode, it then replaces that shortcode with the generated HTML, and then returns the modified page content.

This is done internally in the base class in the content_filter ( $in_the_content ) function. If you send page content (which may include shortcodes) into this function, the returned content will have the shortcodes replaced with HTML (which may also include inline JavaScript). This HTML will be the rendering of the shortcode; appropriate for whatever command was embedded in the shortcode (the base class will parse the shortcode for things like option IDs and query parammeters).

Basically, to render the BMLT, simply pass the page content (as HTML) into the content_filter ( $in_the_content ) function, and replace it with what comes out.

The WordPress plugin goes a bit farther than most, and has a rather complex variant of this. It overrides the content_filter function. For simplicity’s sake, we’ll show you the BMLT Basic implementation, which directly calls the function:

    function output_body ()
        {
        echo $this->content_filter ( $this->my_shortcode );
        }

See how simple that is? The base class takes care of all the smart content and AJAX.

RENDERING THE ADMIN DISPLAY

Rendering the interactive administration page is just as simple. You simply call the return_admin_page() function, and display the HTML and JavaScript returned.

Here is the WordPress version of this function ( admin_page() is the WordPress callback that is set in the options_menu() function of the plugin):

    function admin_page ( )
        {
        echo $this->return_admin_page ( );
        }

All of the interaction, option storage/retrieval/deletion, AJAX, etc, is taken care of by the base class.

<head> CONTENT

These pages need some support from stylesheets and JavaScript in the headers. The standard pattern is for the plugins to create a standard_head() function, and a admin_head() function. These should be called during the plugin hook to display the headers. They can get rather involved, as we need to look for implementation of the [[BMLT_MOBILE]] shortcode. Here’s the part of the WordPress plugin that does that:

        $support_mobile = $this->cms_get_page_settings_id ( $page->post_content, true );
        
        if ( $support_mobile )
            {
            $mobile_options = $this->getBMLTOptions_by_id ( $support_mobile );
            }
        else
            {
            $support_mobile = null;
            }
        
        $options = $this->getBMLTOptions_by_id ( $this->cms_get_page_settings_id( $page->post_content ) );

        if ( $support_mobile && is_array ( $mobile_options ) && count ( $mobile_options ) )
            {
            $mobile_url = $_SERVER['PHP_SELF'].'?BMLTPlugin_mobile&bmlt_settings_id='.$support_mobile;
            if ( isset ( $this->my_http_vars['WML'] ) )
                {
                $mobile_url .= '&WML='.intval ( $this->my_http_vars['WML'] );
                }
            if ( isset ( $this->my_http_vars['simulate_smartphone'] ) )
                {
                $mobile_url .= '&simulate_smartphone';
                }
            ob_end_clean();
            header ( "location: $mobile_url" );
            die ( );
            }

That’s not so simple, and it’s beyond the scope of this page to specify exactly what’s going on. Suffice it to say that we are “sniffing” for the mobile shortcode. If we find it, and we are on a mobile device, then we interrupt the process, and directly call the mobile handler; which acts as a small standalone Web site.

Note the highlighted “true”, above. That tells the Base Class to “sniff” for a mobile client. It will not return an ID unless the browser is considered a mobile browser (the Base Class has a fairly basic check that is 99% accurate, and you can always force it by appending “&simulate_smartphone=1” to the parameters in the query).

AJAX

AJAX handling requires that the base class get access to the filter execution as early as possible, but after the CMS has had time to set up some of its basic stuff, like the options/storage subsystem.

To properly handle AJAX, you need to call the base class ajax_router() function as early as possible.

WordPress has a great hook for this: the init action. They also have one for the admin action.

CONCLUSION

This may seem like a crazy complicated mess, but it’s not. If you have familiarity with writing CMS plugins, it’s amazing how much of the heavy lifting is done by the base class.