Status.net Plugin Developer Helper Plugin (PDhelper)

01 Apr Tagged code, development, FLOSS, Laconica, microblogging, php, status.net

As with most open source projects, the Laconica (now Status.net) team focuses much more on development than documentation. That's not necessarily a bad thing for the project but makes it harder for third party developers. I had to use a lot of trial and error when working on the TWiT Army Laconica Plugin.

Here's where I had some trouble:

  • Finding the right hook to insert elements in specific places
  • Determining the 'action' name for the loaded page
  • Figuring out how to remove elements

For the last one, it turns out that returning bool FALSE for some hooks will prevent the default output. That was a good idea by the dev team. As for the first two, I decided to create another plugin that just spits some markers and useful PHP object data. You can see a demo of this plugin on my Status.net test site: status.kylehasegawa.com. Some of the markers are not in the body element so these markers are added as HTML comments. View the source of the test site to see these markers.

Comments, feedback, and suggestions are more than welcome. Full source code below the break.

Update v0.3 2010/01/05

  • Status.net v9.0 support (tested on 0.9.0rc2)
  • XHTML now mostly valid (
  • Other minor enhancements

For older versions, click the "revisions" tab above. This plugin is strictly for development and should not be left enabled on a production site even if "showMarksers" is disabled.

Installation

Save the plugin to local/PDhelperPlugin.php and add the following to config.php

// Change 'TRUE' to 'FALSE' to hide visible markers. Comment markers will still be in the XHTML source.

addPlugin('PDhelper', array('showMarkers' => TRUE));

Plugin source code

<?php
/**
 * Plugin Developer Helper Plugin
 *
 * @category Plugin
 * @package  Statusnet
 * @author   Kyle Hasegawa  @kylehase
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @version  PDhelperPlugin.php,v 0.3 2010/01/05 15:18:45 +0900
 *
 * @see      Event
 */


if (!defined('STATUSNET')) {
    exit(1);
}

class PDhelperPlugin extends Plugin
{

    public $showMarkers;

    /**
     * constructor
     */


    function __construct($show = TRUE)
    {
        $this->showMarkers = $show;
        parent::__construct();
    }

/**
 * These are ALL the documented StatusNet Plugin hooks
 * Most hook functions below simply print the function name where it's hooked during execution
 */


    function onInitializePlugin()
    {
        // A chance to initialize a plugin in a complete environment
    }

    function onCleanupPlugin()
    {
        // A chance to cleanup a plugin at the end of a program
    }

    function onStartPrimaryNav($action)
    {
        $hook = substr(__FUNCTION__, 2);
        $this->_printHook($hook, $action);
    }

    function onEndPrimaryNav($action)
    {
        $hook = substr(__FUNCTION__, 2);
        $this->_printHook($hook, $action);
    }

    function onStartSecondaryNav($action)
    {
        $hook = substr(__FUNCTION__, 2);
        $this->_printHook($hook, $action);
    }

    function onEndSecondaryNav($action)
    {
        $hook = substr(__FUNCTION__, 2);
        $this->_printHook($hook, $action);
    }

    function onStartShowStyles($action)
    {
        $hook = substr(__FUNCTION__, 2);
        $this->_printHeadHook($hook, $action);
    }

    function onEndShowStyles ($action)
    {
        $hook = substr(__FUNCTION__, 2);
        $this->_printHeadHook($hook, $action);
    }

    function onStartShowStatusNetStyles($action)
    {
        $hook = substr(__FUNCTION__, 2);
        $this->_printHeadHook($hook, $action);
    }

    function onEndShowStatusNetStyles($action)
    {
        $hook = substr(__FUNCTION__, 2);
        $this->_printHeadHook($hook, $action);
    }

    function onStartShowUAStyles($action)
    {
        $hook = substr(__FUNCTION__, 2);
        $this->_printHeadHook($hook, $action);
    }

    function onEndShowUAStyles($action)
    {
        $hook = substr(__FUNCTION__, 2);
        $this->_printHeadHook($hook, $action);
        $this->_printPDHelperCSS($action);
    }

    function onStartShowScripts($action)
    {
        $hook = substr(__FUNCTION__, 2);
        $this->_printHeadHook($hook, $action);
    }

    function onEndShowScripts($action)
    {
        $hook = substr(__FUNCTION__, 2);
        $this->_printHeadHook($hook, $action);
        $this->_printActionDump($action);
    }

    function onStartShowJQueryScripts($action)
    {
        $hook = substr(__FUNCTION__, 2);
        $this->_printHeadHook($hook, $action);
    }

    function onEndShowJQueryScripts($action)
    {
        $hook = substr(__FUNCTION__, 2);
        $this->_printHeadHook($hook, $action);
    }

    function onStartShowStatusNetScripts($action)
    {
        $hook = substr(__FUNCTION__, 2);
        $this->_printHeadHook($hook, $action);
    }

    function onEndShowStatusNetScripts($action)
    {
        $hook = substr(__FUNCTION__, 2);
        $this->_printHeadHook($hook, $action);
    }

    function onStartShowSections($action)
    {
        $hook = substr(__FUNCTION__, 2);
        $this->_printHook($hook, $action);
    }

    function onEndShowSections($action)
    {
        $hook = substr(__FUNCTION__, 2);
        $this->_printHook($hook, $action);
    }

    function onStartShowHeader($action)
    {
        $hook = substr(__FUNCTION__, 2);
        $this->_printHeadHook($hook, $action);
    }

    function onEndShowHeader($action)
    {
        $hook = substr(__FUNCTION__, 2);
        $this->_printHeadHook($hook, $action);
    }

    function onStartShowFooter($action)
    {
        $hook = substr(__FUNCTION__, 2);
        $this->_printHook($hook, $action);
    }

    function onEndShowFooter($action)
    {
        $hook = substr(__FUNCTION__, 2);
        $this->_printHook($hook, $action);
    }

    function onStartShowContentBlock($action)
    {
        $hook = substr(__FUNCTION__, 2);
        $this->_printHook($hook, $action);
    }

    function onEndShowContentBlock($action)
    {
        $hook = substr(__FUNCTION__, 2);
        $this->_printHook($hook, $action);
    }

    function onStartNoticeSave($notice)
    {
        // Events can be fired off here when a notice is about to be saved
    }

    function onEndNoticeSave($notice)
    {
        // Events can be fired off here after a notice has been saved
    }

    function onStartShowLocalNavBlock($action)
    {
        $hook = substr(__FUNCTION__, 2);
        $this->_printHook($hook, $action);
    }

    function onEndShowLocalNavBlock($action)
    {
        $hook = substr(__FUNCTION__, 2);
        $this->_printHook($hook, $action);
    }

    function onStartShowHTML($action)
    {
        $hook = substr(__FUNCTION__, 2);
        $this->_printHeadHook($hook, $action);
    }

    function onEndShowHTML($action)
    {
        $hook = substr(__FUNCTION__, 2);
        $this->_printHeadHook($hook, $action);
    }

    function onStartPublicGroupNav($action)
    {
        $hook = substr(__FUNCTION__, 2);
        $this->_printHook($hook, $action);
    }

    function onEndPublicGroupNav($action)
    {
        $hook = substr(__FUNCTION__, 2);
        $this->_printHook($hook, $action);
    }

    function onStartSubGroupNav($action)
    {
        $hook = substr(__FUNCTION__, 2);
        $this->_printHook($hook, $action);
    }

    function onEndSubGroupNav($action)
    {
        $hook = substr(__FUNCTION__, 2);
        $this->_printHook($hook, $action);
    }

    function onRouterInitialized()
    {
        // After the router instance has been initialized
    }

    function onStartLogout($action)
    {
        // Before logging out

        // TODO: examine this object to see if a dump would be useful
        // Page probably reloads after logout so dump data would have to persist
    }

    function onEndLogout($action)
    {
        // After logging out
    }

    function onArgsInitialized($args)
    {
        // After the argument array has been initialized

        // TODO: examine args to see if a dump would be useful
    }

    function onStartAddressData($action)
    {
        $hook = substr(__FUNCTION__, 2);
        $this->_printHeadHook($hook, $action);
    }

    function onEndAddressData($action)
    {
        $hook = substr(__FUNCTION__, 2);
        $this->_printHeadHook($hook, $action);
    }

    function onStartShowHeadElements($action)
    {
        $hook = substr(__FUNCTION__, 2);
        $this->_printHeadHook($hook, $action);
    }

    function onEndShowHeadElements($action)
    {
        $hook = substr(__FUNCTION__, 2);
        $this->_printHeadHook($hook, $action);
    }



/**
 * These functions are not status.net hooks, they are just for this plugin
 */


    /*
     * Show an HTML comment and optionally a small span with the hook info.
     */

    function _printHook($hook, $action)
    {
        // Start and End PublicGroupNav hooks have slightly different action objects. StatusNet Bug?
        if('StartPublicGroupNav' == $hook OR 'EndPublicGroupNav' == $hook)
        {
            $action->action->raw("\n<!-- PDHELPER $hook -->\n");
            if($this->showMarkers)
            {
                // Nav hooks are hooked within <ul> elements so text should be enclosed in <li>
                $action->action->elementStart('li');
                $action->action->elementStart('span', array('id'=>$hook, 'class'=>'pdhelper'));
                $action->action->text(_($hook. ' hook'));
                $action->action->elementEnd('span');
                $action->action->elementEnd('li');
            }
        }
        // All other hooks
        else
        {
            $action->raw("\n<!-- PDHELPER $hook -->\n");
            if($this->showMarkers)
            {
                // Nav hooks are hooked within <ul> elements so they should be <li> elements
                if ('Nav' == substr($hook, -3)) $action->elementStart('li');
                $action->elementStart('span', array('id'=>$hook, 'class'=>'pdhelper'));
                $action->text(_($hook. ' hook'));
                $action->elementEnd('span');
                if ('Nav' == substr($hook, -3)) $action->elementEnd('li');
            }
        }
    }


    /*
     * Shows HTML comments with hook info for hooks that are not visible
     */

    function _printHeadHook($hook, $action)
    {
        $action->raw("\n<!-- PDHELPER $hook -->\n");
    }


    /*
     * Displays divs with information about the current "action"
     */

    function _printActionDump($action)
    {
        if($this->showMarkers)
        {
            // Clickable div with the name of the current action
            $action->elementStart('div', array('id'=>'pdhelper-current-action'));
            if(isset($action->args['action']))
            {
                $action->elementStart('a', array('href'=>'#', 'onclick'=>'return false;'));
                $action->text('Action: '. trim($action->args['action']));
                $action->raw('<br />');
                $action->elementStart('span', array('style'=>'font-size:0.7em;'));
                $action->text('(toggle action object dump)');
                $action->elementEnd('span');
                $action->elementEnd('a');
            }
            elseif(isset($action->code)) $action->text('Error: '. $action->code);

            // Javascript to toggle object dump visibility
            $action->elementStart('script', array('type'=>'text/javascript'));
            $action->raw("\n$('#pdhelper-current-action').toggle(
                    function() {
                        $('#pdhelper-action-dump').show();
                    },
                    function() {
                        $('#pdhelper-action-dump').hide();
                    }
                );\n"
);
            $action->elementEnd('script');

            // Large div with a dump of the action object data
            $action->elementEnd('div');
            $action->elementStart('div', array('id'=>'pdhelper-action-dump'));
            $action->elementStart('pre');
            $action->raw(print_r($action, TRUE));
            $action->elementEnd('pre');
            $action->elementEnd('div');
        }
    }


    /*
     * Prints some CSS for PDHelper output
     */

    function _printPDHelperCSS($action)
    {
        $action->elementStart('style', array('type'=>'text/css'));
        $action->raw("\n.pdhelper {background-color: yellow; font-style: bold;}\n");
        $action->raw("\n#pdhelper-action-dump {background-color: #FFF666; width:500px;".
                      " display:none; z-index:1; position:absolute; top:0; right:0; padding: 5px;}\n");
        $action->raw("\n#pdhelper-current-action {color: #FFF; background-color: #800517; padding:8px; float:left;".
                      " font-size:1.2em; text-decoration:underline; position:absolute; top:0; left:0;}\n");
        $action->raw("\n#pdhelper-current-action a, #pdhelper-current-action a:visited, ".
                      " #pdhelper-current-action a:hover, #pdhelper-current-action a:active {color: #FFF}\n");
        $action->elementEnd('style');

    }
}

All code on this site is free for use at your own risk and provided as-is under the WTFPL license unless otherwise stated. Attribution is appreciated but not required.
Blog content, with the exception of externally quoted material, is licensed under the Creative Commons Attribution 3.0 license