Version: 2.0
Release: Feb 1, 2006
Compatibility: QC v1.0+
Languages: en
Author: Wizzud
Contact: rb@wizzud.com

Plugin Manager

Plugin module for Quick.Cart by OpenSolution.org

Synopsis

This is a complete re-vamp of the original version 1.0 plugin.
For appropriately constructed plugins* this Plugin Manager offers ...
  • Enable/disable plugins
  • Online plugin configuration
  • Version checking
  • Auto-include
  • Theme selection
  • Shop maintenance modes
  • Reduction of code editting
* In order to receive the full benefits of the Plugin Manager, other plugins need to be constructed to use the facilities on offer. This generally requires the inclusion of a couple of files, such as config.php and setup.php, in the plugin folder, and possibly a language file. Without these 'control' files the manager can only enable/disable other plugins, and then only if the appropriate code is inserted in the PHP files in the QC/plugins/ folder. See Plugin Developers section.

Compatibility

Quick.Cart versions prior to v1.0 : Not compatible.
Quick.Cart versions 1.0 upward : Compatible at the time of writing.
Other Plugins : With plugins of which I am aware, there are no known issues.

Prerequisites

None, other than meeting the Quick.Cart Compatibility requirements.

Upgrade

There is no upgrade path, since this is first release available for the compatible Quick.Cart versions.

Install

Where possible, the release folder is laid out in the correct folder structure to aid installation.
  1. Copy the entire pluginManager folder from the release/plugins/ folder into your Quick.Cart plugins/ folder.
  2. Copy the contents of the release/templates/admin/ folder to your Quick.Cart templates/admin/ folder (2 new files: pluginManager.tpl and pluginManager.css).
  3. Copy the contents of the release/templates/ folder to your Quick.Cart templates/ folder (1 new folder: themes; and 1 new file: pluginManager.tpl - NOT the admin folder!).
  4. Edit file plugins/actions_client.php, copy 1 line below and paste to plugins/actions_client.php file, above any other installed plugins!:
    CODE
    require DIR_PLUGINS.'pluginManager/actions_client.php';
  5. Edit file plugins/actions_admin.php, copy 1 line below and paste to plugins/actions_admin.php file, above any other installed plugins!:
    CODE
    require DIR_PLUGINS.'pluginManager/actions_admin.php';
  6. Edit file plugins/plugins.php, copy 1 line below and paste to plugins/plugins.php file, above any other installed plugins!:
    CODE
    require DIR_PLUGINS.'pluginManager/pluginManager.php';
  7. Copy the contents of the release/config/ folder to your Quick.Cart config/ folder (1 new file: plugins.php) and give it the same file permissions as general.php in that directory
  8. Add a link in the admin menu bar to access the plugin manager. Please remember to make a copy of the template file before you change it!
    Edit templates/admin/page.tpl and ...
    Replace the 1 line below
    CODE
    <a class="menuButton" href="?p=otherConfig" >$lang[configuration]</a>
    with
    CODE
    <a class="menuButton" href="?p=otherConfig"
       onmouseover="return buttonClick(event, 'configuration'); buttonMouseover(event, 'configuration');"
       >$lang[configuration]</a>

    Then look a couple of lines down from there, for a line containing just "</div>",and immediately below this line paste the following 3 lines
    CODE
    <div id="configuration" class="menu" onmouseover="menuMouseover( event );">
       <a class="menuItem" href="?p=pluginsEdit"><span class="menuItemText">$lang[plugins]</span></a>
    </div>
  9. The Plugin Manager should now be accessible from the Admin menu as a 'plugins' option below the 'configuration' option.
    Select the 'plugins' option and hit 'Save' (whether you have made any changes or not!).

User Guide

In the Admin control panel select the 'plugins' option:
  • A list of the installed plugins is presented, with radio buttons indicating whether they are enabled or not.
  • For plugins set up with configuration options (in the plugin's own config.php file), a configuration button link will also be present to the right of the radio buttons.
  • At the far top right of the table of plugins, inline with the 'Plugin Manager' header, is a configuration button for the Plugin Manager itself.
  • Move the cursor over any plugin name and you will see any Setup information that has been made available (from the plugin's own setup.php file).
  • If an 'AboutThisPlugin.html' file exists in the plugin's folder, then a clickable image will be displayed between the plugin name and the radio buttons, which will open the HTML file in a new window.
  • Any error or warning messages (such as Version Mismatch) will be displayed to the far right of the plugin's row.
  • Always Save any changes!
Click on the 'pluginManager Configuration' button (top right of table):
  • The 'Plugins Data File' is an information-only field.
  • The 'Plugins to Ignore' field contains a list of plugins over which the Plugin Manager has no control. It is pre-configured with 'edit' (the wysiwyg editors supplied with Quick.Cart), but the names of other plugins can be added if necessary.
  • 'Version Checking' is a simple on/off field, and is primarily intended as a safeguard when installing new versions of plugins. If enabled it will automatically disable a plugin if the version information in the plugin's Setup file does not match what has been saved in the Plugins Data File. If such an event occurs you will see an error message against the offending plugin in the plugins list, and be presented with a 'Reload Setup' button which will load and save the information from the plugin's Setup file. You should check the plugins configuration (if there is any) before re-enabling it.
    If 'Version Checking' is disabled you will still be presented with error messages and reload buttons, but the offending plugin will not be automatically disabled.
  • Hover the cursor over any field label and you will see information about the variable and its format.
  • Hover the cursor over the Help link at the top right of the page and you will see general help information.
  • Always Save any changes!
Not all plugins will have configurable parameters, and the configuration button will only be presented where the plugin has been specifically set up for control by the Plugin Manager. Legacy plugins, and plugins not compatible with the Plugin Manager, may still require editting of the /config/general.php file. Always check the instructions when installing new plugins!
Plugins that have not been set up to run under the Plugin Manager (ie. no setup.php file) can still be enabled/disabled (if appropriate) from the Plugin Manager, with control over the plugin being effected by using the pluginIsEnabled() function in the plugin control files. For example, if the plugin's installation instructions say to paste the following line into /plugins/plugins.php ...
CODE
require DIR_PLUGINS.'nameOfPlugin/nameOfPlugin.php';
... then you simply replace it with
CODE
if(pluginIsEnabled('nameOfPlugin')){ require DIR_PLUGINS.'nameOfPlugin/nameOfPlugin.php'; }
The same principle applies to the two other plugin control files - /plugins/actions_admin.php and /plugins/actions_client.php.

Themes

The Plugin Manager brings back the concept of Themes, whereby different sets of template files an be switched between as and when desired. The themes are held under a Themes Folder, below the Templates folder, and have the same format as the 'default' Quick.Cart templates - ie. the shop templates at the top level, admin templates in an 'admin' sub-folder, and images in an 'images' sub-folder.
For example, if you created a 'green' theme which required template file changes as well as CSS changes, then, using a themes folder called 'themes' ...
  • the shop template files and CSS file(s) would be in the /templates/themes/green/ folder
  • the images (for both shop and admin) would be held in the /templates/themes/green/images/ folder
  • the admin templates and CSS file(s) would be held in the /template/themes/green/admin/ folder.
The dropdown list in the Plugin Manager's configuration page allows selection of any installed and valid themes. Note that a theme must contain at least one CSS file in order to be considered valid. Please also note that it is not necessary to have an admin section for every theme; if an admin template file cannot be found in the current theme/admin folder then the one from the 'default' admin folder will be used instead.
The default Quick.Cart theme will always be available under the heading 'Default', and the selection of a stylesheet from the main Configuration page will reflect those stylesheets found in the currently selected theme.
One word of caution if you are contemplating using or providing themes: the configuration variable $config['dir_tpl'] is used in the default template files to link stylesheets and provide paths to images, but this will not provide the required path when used in a theme template. You should use $config['dir_theme'] instead, since this variable will always hold the path to the current theme folder (including 'the 'default'). As with 'dir_tpl', you should provide the sub-folder path extensions for admin/ and images/ when needed.

Maintenance Mode / Shop Status

The Plugin Manager offers the ability to set a maintenance mode for your shop while you are upgrading it or rebuilding your product base. the 'Shop Status' dropdown on the plugin's own configuration page offers 3 settings:
  • Online - which is the default - means everything will function normally
  • Catalogue Only - will allow visitors to view all the products and content as usual, but they will not be able to place any items in the Basket for purchase
  • Offline - prevents visitors from viewing (and thereby puchasing) any products; the category menus will still be visible, as will any content pages
Administration functionality is totally unaffected by any setting of this flag.

Language

By default this plugin is only supplied with an English language file - /plugins/pluginManager/lang-en.php.
If you wish to provide translations for your own language, copy lang-en.php to lang-[2-char-code].php (within the same folder), where [2-char-code] is the recognised code for your language. For example, a Polish language file would be /plugins/pluginManager/lang-pl.php.
The Plugin Manager will automatically load the English file first, and then look for the language file in the Shop's configured language, so that the English text is always there as a backup.

Plugin Developers...

To be controlled by the Plugin Manager, a plugin must have its own folder in the plugins folder (even if it doesn't need one!).
The Plugin Manager looks for config.php in the plugin's folder (ie. /plugins/thisPluginName/config.php) and provides the ability to modify the settings held in that file. How the modification is presented and handled can be 'tweaked' by providing instructions for the Plugin Manager, either from the config.php file itself, and/or from a lang file in the same plugin folder, such as /plugins/thisPluginName/lang-en.php (for language-related settings only).
The Plugin Manager also uses a setup file (ie. /plugins/thisPluginName/setup.php), from which it retrieves version and loading instructions.

Config File

Settings in the plugin's config.php, using $PMCI array variables, eg $PMCI['paypalAccount'] for a config variable defined as $config['paypalAccount']:
typeSpecifies the form mechanism for modifying the data
'input[(n)]'Default (except for booleans or format 'e'). If supplied, the (n) dictates the size of the input field
'radio(value=display [,value=display...])'where value can be a number or string (eg. '2', 'two', 'true') and is the captured value, and display is the visible label.
Don't quote the value or display settings!
'checkbox[(value=display [,value=display...])]'where value can be a number or string (eg. '2', 'two', 'true') and is the captured value, and display is the visible label.
For a simple on/off variable such as a boolean, you do not need to specify the (value=display), ie. just use 'checkbox' and it will show a single checkbox with no label, inferring the 'name' as its descriptor - this is actually the default for boolean variables!
Don't quote the value or display settings!
'select(value=display' [,value=display...])'where value can be a number or string (eg. '2', 'two', 'true') and is the captured value, and display is the visible option in the dropdown. Don't quote the value or display settings!
'textarea[(rows,columns)]'where rows and columns (including the containing brackets) are optional - the default is 4 rows by 40 columns.
Mandatory for format 'e'!
formatThis is the type of variable stored, and can be worked out in most cases by the application
's' = string,
'b' = boolean (ie true/false),
'n' = number (integer or float)
Using these notations any quoting required is handled by the application (ie. string variables do not have to be quoted on input).
An expandable array of strings can be expressed as 's*'; a fixed array of 3 strings can be expressed as 's3';
a mixed array 1 string, 1 number, 2 booleans, and 1 last string should be expressed as 's,n,b,b,s' (or 's,n,b2,s');
A special type of 'e' is available for 'exact' representation, and input must include all quotes and array declarations exactly as if typing the declaration into the PHP file itself.
For example, an array of arrays cannot be handled by the application and so would have to be set to a format of 'e' (it will default to this if not set explicitly).
Note that the 'type' for an 'e' format must be textarea - if you do not specify it, it will be overridden and will use the default rows and columns
checkThese are the parameters passed into the checkForm javascript routine if validation is required on the field.
Do not supply the field name (first parameter usually passed to the routine) since the application will provide that.
Subsequent parameters must be supplied, either as a string (for single parameters) or an array (of one or more parameters), eg 'check'=>'simple', or 'check'=>array('simple','Surname is a required field'), or 'check'=>array('email').
The checkForm function offers validation of type simple, txt, float, int, email and regexp.
For the more advanced field validation see the Regular Expressions section.
nameThis is the visiable label for the field
altThis is the alt and title string applied to the input field
textThis is the Hint that can be displayed alongside the actual input field, if you wish to provide detailed instructions on what values are or are not allowed
disableThis causes the application to only DISPLAY the referenced variable on the configuration page, so no values can be changed - useful for special variables that you do not want anyone to be able to modify through the Admin interface.
Example. $PMCI['special_var']['disable'] = true;
Unless you specifically set up this variable, and with a value of true, it will always default to false, ie. all fields are modifiable unless specified otherwise.
hideIn case of problems, or if you have a variable that you really don't want Admin to even see the value of, then you can set this to true and the config variable will not be displayed or affected at all.
Example. $PMCI['hidden_var']['hide'] = true;
Unless you specifically set up this variable, and with a value of true, it will always default to false, ie. all fields are visible unless specified otherwise.
Example: Given config variables of ...
CODE
$config['paypal_live'] = true;
$config['paypal_account'] = 'yourname@yoursite.com';
$config['paypal_url']['test'] = 'https://www.sandbox.paypal.com/xclick/';
$config['paypal_url']['live'] = 'https://www.paypal.com/xclick/';
$config['paypal_currency'] = 'EUR';
then the $PMCI array variables could be set to ...
CODE
// use 2 radio buttons for boolean variable ...
$PMCI['paypal_live']['type'] = 'radio(true=' . LANG_YES_SHORT . ',false=' . LANG_NO_SHORT . ')';
$PMCI['paypal_account'] = array('type'=>'input', 'name'=>'PayPal Account', 'check'=>array('email'), 'alt'=>'PayPal Account');
$PMCI['paypal_url']['test'] = array('name'=>'Test URL', 'alt'=>'Test URL');
$PMCI['paypal_url']['live'] = array('name'=>'Live URL', 'alt'=>'Live URL', 'check'=>'simple');
// radio buttons to select currency ...
$PMCI['paypal_currency'] = array('type'=>'radio(USD=USD,EUR=EUR,GBP=GBP,CAD=CAD,JPY=JPY)');
... or ...
CODE
// use dropdown select for boolean variable ...
$PMCI['paypal_live']['type'] = 'select(true=Live,false=Test)';
$PMCI['paypal_account']['name'] = $PMCI['paypal_account']['alt'] = 'PayPal Account';
$PMCI['paypal_account']['check'] = 'email';
$PMCI['paypal_url']['test']['name'] = $PMCI['paypal_url']['test']['alt'] = 'Test URL';
$PMCI['paypal_url']['live']['name'] = $PMCI['paypal_url']['live']['alt'] = 'Live URL';
$PMCI['paypal_url']['live']['check'] = 'simple';
// Modification of the Live URL disabled, but its still a mandatory field! ...
$PMCI['paypal_url']['live']['disable'] = true;
// dropdown select ...
$PMCI['paypal_currency']['type'] = 'select(USD=US Dollar,EUR=Euro,GBP=Sterling,CAD=Canadian Dollar,JPY=Yen)');

The Plugin Manager can cope with 2 levels of $config variable, ie. $config['one']=.. and $config['one']['two']=.., but no more. Any further levels would have to be of the form $config['one'] = array(array(..), array(..), ..);.
Note that if you have defined something like $config['paypal']['account'] then the instruction variable (in the config file) becomes $PMCI['paypal']['account'].., BUT the language file equivalents become $lang['PMCI']['paypal__account'].. (ie. you have to join the 2 levels with a double underbar); see below.

Language File

The language files looked for are
  1. the english file, lang-en.php (ie. /plugins/thisPluginName/lang-en.php), and
  2. the file for the site-defined language (ex. /plugins/thisPluginName/lang-pl.php)
with the site-defined language overriding the english language file (the english language file is there as a default backup).
name, alt and text can be provided in the plugins language file (eg lang-en.php) by setting $lang['PMCI'] array variables eg. for a config variable $config['paypal_account'] the language file could contain definitions of
CODE
$lang['PMCI']['paypal_account']['name'] = 'PayPal Account';
$lang['PMCI']['paypal_account']['alt'] = 'PayPal Account';
$lang['PMCI']['paypal_account']['text'] = 'Enter your PayPal Account identifier (an email address)';
and for a config variable $config['paypal']['account'] the language file could contain definitions of
CODE
$lang['PMCI']['paypal__account']['name'] = 'PayPal Account';
$lang['PMCI']['paypal__account']['alt'] = 'PayPal Account';
$lang['PMCI']['paypal__account']['text'] = 'Enter your PayPal Account identifier (an email address)';
Note the double underbars in the second example.
Note also, that values provided in the config file (using the $PMCI array variables) will override the language file settings ($lang['PMCI']['..']).

Setup File

The Setup file holds the version number of the plugin, the files to auto-load, any prerequisite information, and a language pre-load setting. The provision of a correctly configured Setup file means that the 'require' entries in any of the three PHP plugin control files (/plugins/plugins.php, /plugins/actions_admin.php and /plugins/actions_client.php) do not have to be manually editted in by the installer - the Plugin Manager will do it automatically if the plugin is enabled.
The 'triggerDisabled' entry only has any meaning for Admin-type facilities where, for example, the main navigation menu has been editted to add options to access new functionality (eg. the productAttributes plugin). Since it is possible to disable the plugin, but the menu option will still be clickable, entering the $p page setting for the link into the 'triggerDisabled' array will cause Plugin Manager to intercept the call and redirect it to its main list page with an appropriate error message.
For example, the setup.php file (/plugins/pluginmanager/setup.php) for version 2.0 of the Plugin Manager looks like this ...
CODE
<?php
/*
* setup.php for pluginManager
*/
$setup = array( 'version' => '2.0'
  , 'plugins.php' => array()
  , 'actions_client.php' => array()
  , 'actions_admin.php' => array()
  , 'prerequisites' => array()
  , 'preloadLang' => 0
  , 'triggerDisabled' => array()
  );
?>
while a more complex one might look like ...
CODE
<?php
/*
* setup.php for productAttributes
*/
$setup = array( 'version' => '2.0'
  , 'plugins.php' => array('productAttributes.php')
  , 'actions_client.php' => array('actions_client.php')
  , 'actions_admin.php' => array('actions_admin.php')
  , 'prerequisites' => array('pluginManager v2.0+')
  , 'preloadLang' => 2
  , 'triggerDisabled' => array('attributesList','attributesForm')
  );
?>
The $setup array elements are:
  • version : text field indicating the current version of the plugin
  • plugins.php : array of file names within the plugin's folder that have to be included into /plugins/plugins.php
  • actions_client.php : array of file names within the plugin's folder that have to be included into /plugins/actions_client.php
  • actions_admin.php : array of file names within the plugin's folder that have to be included into /plugins/actions_admin.php
  • prerequisites : text field indicating any other plugins (and version information) that this plugin is dependent upon in order to run correctly
  • preloadLang : an integer indicating whether or not to pre-load the plugin's language file(s). Allowed values are 0='no', 1='only if plugin is enabled', and 2='always'. The reason for this setting is that some plugins have to be 'hard-coded' into template files to a certain extent, such as templateEdit, which requires the menu option to be placed in the admin menu template, and if the plugin is then disabled the layout of the shop would distort or break if the language file was not still included. Also, some plugins may require the language file to be included before they themselves are called into action and can include the language file for themselves. Therefore this setting allows the Plugin Manager to load the language file(s) on the plugin's behalf.
  • triggerDisabled : array of $p page names (Admin side only) that, if the plugin is disabled, will be redirected to Plugin Manager's main page.

Regular Expressions Checking

The checkForm javascript routine (as provided with this plugin, not the standard Quick.Cart version!) has the ability to perform regular expression checking on a field, as long as you provide the correct expression to validate against! The instructions here apply to usage within the context of the Plugin Manager configuration instruction set (they may differ slightly when writing the expression directly into a template definition file).
  • Don't include the start and end delimiters - they are not needed.
  • If you need a dollar sign in the expression (for 'ends with', for example) you need to use another variable to get around the template parsing, so I've provided one defined as $lang['PMdollar'].
  • Because you are parsing through PHP and Javascript, if you need to escape anything with backslashes you also have to escape those backslashes with more backslashes (twice!) to get the requisite escaping all the way through to the javascript regular expression.
For example: To check for a valid 2 decimal place price, the regular expression might normally resemble '/^[0-9]+\.[0-9]{2}$/'. Basically this breaks down to "one or more leading digits, followed by a period, and terminating in 2 digits". To get the checkForm routine to perform this validation on a field in the Plugin Manager Configuration facility you need to define 'check' as follows:
CODE
$PMCI['newPriceField']['check'] = array('regexp','^[0-9]+\\\\.[0-9]{2}$lang[PMdollar]','','Specific error message for the field');
(If you don't have a specific error message you want to display then omit the last 2 parameters.)
Note that the backslash for the period has itself been escaped, and then both those have again been escaped, the reason being that PHP will parse the 4 backslashes down to 2, and Javascript will then parse the 2 down to 1, which is the one needed to escape the period in the final regular expression.
Also note that the dollar sign (indicating 'ends with') will be provided by the template parser substituting the value of $lang['PMdollar'] for $lang[PMDollar].
An alternative for the above, using \d instead of [0-9] (and without a specific error message) would be
CODE
$PMCI['newPriceField']['check'] = array('regexp','^\\\\d+\\\\.\\\\d{2}$lang[PMdollar]');
A question could be raised as to why the Plugin Manager doesn't do the dollar-substitution itself - and I did consider it. However, not doing it means that the regular expression can be made to use variables if you wish it to, instead of being a fixed expression. Also, it may be that the expression needs to test specifically for '$' within the field value. If the Plugin Manager were to do, say, 'ends-with' substitution of $, but not mid-expression substitution, it just leads to confusion as to what will or won't be substituted automatically. Therefore, I have left it entirely to you!

Extended TPL Parser

The Plugin Manager includes a class extension to the standard TPL Parser, which enables intercepts to be placed on $tpl method calls. A more comprehensive explanation, with examples, is available at the top of the include file /plugins/pluginManager/extendTplParser.class.php but what it basically provides is:
  • All four calls to dump/return files/blocks from template files (eg. $tpl->tbHtml([fileName], [blockName]) are received by the class extension
  • The extension checks to see if an intercept has been set up for that [FileName] and/or [blockName] and will substitute file and/or block as instructed
  • The extension provides for conditional substitution - based on a boolean returned returned form a specified function - and 'run-before' functions which can be used to set up or modify global variables used by the template about to be called
  • The intercept can specify another tpl call to append to the output of the activating call
  • Four extra calls have been provided which bypass the intercept functionality (eg. $tpl->xtbHtml([fileName], [blockName]) - note the 'x' - so that, for example, a plugin can initiate a $tpl method call without the possibility of it being intercepted by some other plugin's intercept
The above probably sounds like gobbledy-gook but it makes plugin development considerably easier, and vastly simplifies installation procedures. Please take a look at the include file, and investigate other plugins to see how they utilise the extended class.

DOM Trawler

I have included a JavaScript function for run-time searching of the HTML DOM tree. I originally constructed it as a means of avoiding having to replicate template files, purely in order to add a reference to, say, a global variable.
The file is /pluginManager/trawler.js and the only function it contains is trawlDOM(). The function returns either a node object, or boolean false if it can't find a node matching the search criteria specified.
PARAMETERS
/**
* Recursively searches for a <TAG> node (note the case!), optionally with attribute <att>=<attVal> and to a specified level of child nodes
* @return object node or false if not found
* @param object starting node
* @param string tag to search for (uppercase!)
* @param string optional attribute name to search for (lowercase!)
* @param string optional attribute value
* @param integer number of child levels to search through (0 = just this level, default is all)
* @param integer current level (recursive use only)
*/
Some examples of possible usage might be ...
CODE
if(document.getElementById('addcart')){
  var ac=document.getElementById('addcart');
  /* starting from first child of id=addcart (and searching all sub-levels), find INPUT type=submit */
  var ns=trawlDOM(ac.firstChild,'INPUT','type','submit');
  if(ns){
    /* if found, create and insert new DIV with id=attrSelect ...*/
    var nw=ns.parentNode.insertBefore(document.createElement('DIV'),ns);
    nw.setAttribute('id','attrSelect');   // set the DIV id
    nw.innerHTML='.......';   // set the DIV content
    ......
CODE
if(document.getElementById('addcart')){
  var ac=document.getElementById('addcart');
  /* starting from id=addcart (and keeping to that level only), find the first class=clear DIV ... */
  var ns=trawlDOM(ac,'DIV','class','clear',0);
  /* ... then the next DIV following that ... */
  if(ns && ns.nextSibling){ ns=trawlDOM(ns.nextSibling,'DIV',false,false,0); }
  if(ns){
    /* if found, create another new div (id=attrPrint) below class=clear DIV ... */
    var nw=ns.parentNode.insertBefore(document.createElement('DIV'),ns);
    nw.setAttribute('id','attrPrint');   // set the DIV id
    nw.innerHTML='.......';   // set the DIV content
    ......
~~ The End ~~