Widget Programming

Introduction
This is intended for reasonably experienced PHP programmers with some experience in vBulletin programming. You should have at least a basic understand of the vB templating system and phrasing. If you don’t, I strongly recommend you do some homework before trying to program a widget. I will use as an example the static html widget.

It's very early to be writing this, I know. I can't say exactly when alpha testing will start on the CMS system, which will be part of the suite. Soon, but I'm not in a position to give a date. I've had several requests for more information, so here it is. I've attached a zip file for two widgets- the static html and the rss. This may not be the final code, but it's close.

I've walked through the statichtml widget, because it's the simplest. If you understand that, there are two more things you need to know. You can call $this->content->getNodeId() to get the unique id of the page you are on. This allows you to do things like make navigation widgets that are page-dependent. Neither of these widgets need that. Also, most widgets should be cached. There is a generic caching algorithm- the first parameter is a value or array of values that form a key. vB_Cache::instance()->write and vB_Cache::instance()->read() do this for you.

At the risk of repeating myself, let me explain the hierarchy.

In the CMS system you define grids. Grids divide the page into columns, with optional header and sidebar settings.

Then you create widgets. Widgets are a combination of a widget type (i.e. static html), plus a configuration. If you want ten different Youtube videos, that's ten instances of a statichtml type widget. If you want five different Google gadgets, that's five more instances of the statichtml type.

Then you make layouts. Layouts put the primary content into a grid, and optionally place one or more widgets in specific places. You apply a layout and style to each section (section is the primary organization system in the CMS, but not the only one). All the pages in that section get that layout.

Let's say you wanted to have a form-type widget where users could enter information about each article. You could create your own database table and use that. Or you could use the widget storage we designed. In that case you would want to use a per-page configuration. We haven't written any of those, yet, but the setConfig and getConfig calls will handle per-page configuration.

I don't want to take credit for something I don't deserve. I didn't write the CMS system, but I've been working in it a lot for the last couple months. I think you'll find it powerful and well designed for extension.

Code Example

Files

You will need at least two files. You will need to define a package and class. The package should be something unique. The class can be anything you like, but it’s generally something descriptive. You will need to create the following file structure.

/packages (already there>
/
/item
/content
/
/content
/
Note that we use all lower case file and path names. The static HTML widget has package = vBCms, class=Static. The files are
/packages/vbcms/item/content/static.php
/packages/vbcms/ content/static.php

Database
You will need to create two database entries to use your widget. In the TABLE_PREFIXpackage table you need to insert a record with package= . In the TABLE_PREFIXwidgettype table you need to create an entry with class= , packageid = .

Item/Content file
This file is simple. It’s just for the convenience of the CMS router. You declare a class, and define the package and class. If you have a default configuration setting you should put it here. Here is the file for Static HTML (I’ve removed the comments and licensing.)

if (!defined('VB_ENTRY')) die('Access denied.');

class vBCms_Item_Widget_Static extends vBCms_Item_Widget
{
protected $package = 'vBCms';
protected $class = 'Static';
protected $config = array('html' => 'Enter HTML here.');
}


Content file

This is the file that does the work. You need, at a minimum, to create two functions: getConfigView and getPageView. The first is called to configure your widget, and the second is called to render the view. With most widgets you’ll want to do some caching. For the static html widget it’s one query to get the data, and I couldn’t see executing a query to see if I needed to run one simple query.

Below is the file.



This php file should never be called directly, only from a properly initialized page. This call ensures that.


if (!defined('VB_ENTRY')) die('Access denied.');
licensing

/*======================================================================*\
|| #################################################################### ||
|| # vBulletin [#]version[#] - Licence Number [#]license[#]
|| # ---------------------------------------------------------------- # ||
|| # Copyright ©2000-[#]year[#] Jelsoft Enterprises Ltd. All Rights Reserved. ||
|| # This file may not be redistributed in whole or significant part. # ||
|| # ---------------- VBULLETIN IS NOT FREE SOFTWARE ---------------- # ||
|| # http://www.vbulletin.com | http://www.vbulletin.com/license.html # ||
|| #################################################################### ||
\*======================================================================*/

/**
* Static HTML Widget Controller
*
* @package vBulletin
* @author vBulletin Development Team
* @version $Revision: 31096 $
* @since $Date: 2009-06-03 08:42:31 -0700 (Wed, 03 Jun 2009) $
* @copyright Jelsoft Enterprises Ltd.
*/
Declare the class
class vBCms_Widget_Static extends vBCms_Widget
{
/*Properties====================================================================*/

/**
* A package identifier.
* This is used to resolve any related class names.
* It is also used by client code to resolve the class name of this widget.
*
* @var string
*/
Declare the package and class. You need to do this so the data managers and other components know how to handle inputs
protected $package = 'vBCms';

/**
* A class identifier.
* This is used to resolve any related class names.
* It is also used by client code to resolve the class name of this widget.
*
* @var string
*/
protected $class = 'Static';


/*Render========================================================================*/

/**
* Returns the config view for the widget.
*
* @return vBCms_View_Widget - The view result
*/


Here's the first essential function. This creates the configuration interface and saves data. There is a template called vbcms_widget_static_config.

public function getConfigView()
{
Ensure we have been fully loaded. The classes use lazy loading

$this->assertWidget();

Verify and store the form variables we want.
'do' => vB_Input::TYPE_STR,
'html' => vB_Input::TYPE_STR,,
'template_name' => vB_Input::TYPE_STR
));


Get the template view. This rendering of templates is new for the CMS system. You can use the old methods, but this will be a bit faster.

$view = new vB_View_AJAXHTML('cms_widget_config');
Set the title

$view->title = new vB_Phrase('vbcms', 'configuring_widget_x', $this->widget->getTitle());
load the configuration

$config = $this->widget->getConfig();


See if we have data to save. verifyPostId, together with the addPostId function, adds a security layer



if (('config' == vB::$vbulletin->GPC['do']) AND $this->verifyPostId())
{
get the data manager and set the values

$config['html'] = str_replace('%u0025', '%' , vB::$vbulletin->GPC['html']);
if (vB::$vbulletin->GPC_exists['template_name'])
{
$config['template_name'] = vB::$vbulletin->GPC['template_name'];
}

$widgetdm = $this->widget->getDM();
$widgetdm->set('config', $config);


We don't really need this call in this widget. We have the ability to define widgets which have a per-page configuration. For this widget we have a single configuration site-wide. If you want a different statichtml widget you make a new widget instance. We left it here in case we want to extend it later.

if ($this->content)
{
$widgetdm->setConfigNode($this->content->getNodeId());
}
Save it
$widgetdm->save();
Check for errors, and display a message if we need to.
if (!$widgetdm->hasErrors())
{
if ($this->content)
{
$segments = array('node' => $this->content->getNodeURLSegment(),
'action' => vB_Router::getUserAction('vBCms_Controller_Content', 'EditPage'));
$view->setUrl(vB_View_AJAXHTML::URL_FINISHED, vBCms_Route_Content::getURL($segments));
}

Show we're finished
$view->setStatus(vB_View_AJAXHTML::STATUS_FINISHED, new vB_Phrase('vbcms', 'configuration_saved'));

}
else
{
if (vB::$vbulletin->debug)
{
$view->addErrors($widgetdm->getErrors());
}

// only send a message
$view->setStatus(vB_View_AJAXHTML::STATUS_MESSAGE, new vB_Phrase('vbcms', 'configuration_failed'));
}
}
else
{
Now let's create the configuration interface. First create the view

// add the config content
$configview = $this->createView('config');


if (!isset($config['template_name']) or ('' == $config['template_name']) )
{
$config['template_name'] = 'vbcms_widget_static_page';
}

Add the html code
$configview->html = $config['html'] ? htmlspecialchars_uni($config['html']) : '';

// item id to ensure form is submitted to us
Set the form variables for security
$this->addPostId($configview);

Load the configuration and status

// send the view
$view->setStatus(vB_View_AJAXHTML::STATUS_VIEW, new vB_Phrase('vbcms', 'configuring_widget'));
}
and return the view
return $view;
}




And the second function. This creates the widget.


/**
* Fetches the standard page view for a widget.
*
* @return vBCms_View_Widget - The resolved view, or array of views
*/
public function getPageView()
{
ensure the widget is properly loaded

$this->assertWidget();

get the configuration- in this case it's the html code and the template

$config = $this->widget->getConfig();


if (!isset($config['template_name']) or ('' == $config['template_name']) )
{
$config['template_name'] = 'vbcms_widget_static_page';
}

instantiate the view

// Create view
$view = new vBCms_View_Widget($config['template_name']);


set the class, title, and description

$view->class = $this->widget->getClass();
$view->title = $this->widget->getTitle();
$view->description = $this->widget->getDescription();

load it into the view
$view->static_html = $config['html'];
and return the view
return $view;
}
/*======================================================================*\
|| ####################################################################
|| # Downloaded: [#]zipbuilddate[#]
|| # SVN: $Revision: 31096 $
|| ####################################################################
\*======================================================================*/