Coppermine Photo Gallery v1.5.x: Documentation and Manual

Table of Contents

Plugin Writing: Tutorial, API

Intended Audience

Here's a list of who should and who should not (or doesn't need to) read this documentation. The categories overlap in many places, so make sure to read the entire list. Find yourself in the list and then decide whether you should read on.

People who should read this documentation:

People who do not need to read this documentation:

People interested in using plugins but who do not need to modify them or write their own should read the main documentation instead - the plugins section describes everything you need to know.

Back to top

Required Skills & Knowledge

This documentation assumes a few skills & knowledge you need to have before creating or modifying plugins.

Back to top

Recommended Software & Support Forums

You can easily write plugin code with simply a text editor and administrator-access to a Coppermine gallery, but here are a few more tools that are useful in one way or another.

Back to top

"Hello, world" Plugin Tutorial

Here we go. We are going to write a simple plugin and learn how to implement a number of useful features.

My First Plugin

The first thing you need to do is come up with a short name for your plugin. This short name must be unique enough so that it is different from other plugins currently available and be specific enough that it describes what your plugin does, compared to any plugins that are yet to be written. For example, do not name your plugin "thumbnail" because that's way too general. You need to come up with the name now because you need to use this name to create a folder to hold your plugin's files. Since this short name is to be used as your folder name (and later for a prefix for your plugin's functions), use a valid folder name: no spaces, start with a letter, and use only alphanumeric characters and underscores (to separate words). So take a moment and come up with a short name for your plugin. The full name of your plugin can be decided later.

Each and every Coppermine plugin requires its own folder and a minimum of two files. The folder must be located under the Coppermine plugins folder. So if your Coppermine is located in a folder named "cpg" on your domain, and the short name you chose for your plugin is "yellow_banana", the plugin folder would be: - put all plugin files in this folder. The two files every plugin requires are:

The 'sample' plugin that ships with Coppermine shows most of the features that will be described in this tutorial, but please do not copy the 'sample' plugin for this tutorial. Please start from scratch so you know precisely how a plugin is written and developed. You can download all the tutorial plugins using the links in each section. If you do so, each section's plugin will install in a different folder with a name related to the relevant section. Make sure you are using the correct plugin for the section you are reading. For the first tutorial plugin below, it is strongly recommended that you do things manually so you understand the basics. For the subsequent sections, you can then use the downloaded plugins knowing that you can create a plugin from scratch successfully. Keeping this in mind, click here to download the plugin for this section. Once again, it is recommended you only use these files if you have problems. Create the folder and files from scratch so you understand how to do so.

Let's get started!

Create a folder named "hello_world" in the Coppermine plugins folder as shown:

Make sure you enable read and execute permissions on this folder (see the permissions section in the main manual). You can create the folder and files on your personal computer and upload them using FTP to your web server, but keep in mind that you cannot try out your plugin until it is on the web server. For each step in this tutorial where you add new lines, edit the text file on your computer, save it, then upload the new version onto web server, making sure to overwrite the previous file on the web server. (Some FTP programs "append" by default - which won't work with these text files, so make sure you are definitely overwriting the file with the new version.)

Create this file in the "hello_world" folder, with contents as shown (and read & execute permissions):


  Coppermine Plugin - Hello, World
  Copyright (c) 2010 <-InsertNameHere->
  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License version 3
  as published by the Free Software Foundation.

$name='Hello, World';
$description='Plugin API Tutorial - My First Plugin';
$plugin_cpg_version = array('min' => '1.5');

The beginning part is a comment block of course. We suggest you use such a block at the beginning of each of your plugin files. The first text line tells people this is a Coppermine Plugin and lists the name of your plugin. The next text line has a copyright message with your name (real name or username used on the Coppermine forum). The next text block gives the GPL message that Coppermine uses and is recommended for your code as well. This comment block is not strictly required for your plugin to work. That being said, it is important for organization and clarity, so please make sure to add it to each of your plugin files.

The parameters used by the Plugin API are the 4 variables shown. These variables are shown on the plugin manager page to describe your plugin.

Now create your code file in the "hello_world" folder, with contents as shown (and read & execute permissions):


  Coppermine Plugin - Hello, World
  Copyright (c) 2010 <-InsertNameHere->
  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License version 3
  as published by the Free Software Foundation.

if (!defined('IN_COPPERMINE')) die('Not in Coppermine...');

// Add a filter for the gallery header

// Sample function to modify gallery header html
function helloworld_header($html) {
    $html = '<p style="color:red"><b>Hello, world.</b> (tutorial 2.1)</p>'
    return $html;


These 2 files are all you need to install and run your first plugin. Before explaining the code for this plugin, go ahead and install this plugin. The plugin manager will show "Hello, World" as one of the "Plugins Not Installed". Click on the install button to install this plugin. It will immediately show up on the "Installed Plugins" section since there is no configuration for it. You should see bold red text declaring "Hello, world." near the top of the page. For detailed instructions on how to use plugins as an end-user, read the plugins section of the main documentation. If you haven't read this section completely, please do so now.

So how does this plugin work? The first block in codebase.php is the same comment block we used in configuration.php. For the same reasons as noted above, it is recommended that you use this comment block in each and every one of your plugin files.

Moving on, the first line of code is the following:

if (!defined('IN_COPPERMINE')) die('Not in Coppermine...');

The constant 'IN_COPPERMINE' is defined in each of the public scripts that users use directly, i.e. ones that show up in the URL of a webpage. All secondary scripts check for this constant before doing anything else. If it has not been set, then the script stops and exits with a "Not in Coppermine..." message. With this technique, all secondary files are inaccessible to users and cannot be run directly. Besides security, a larger reason is that these scripts are not meant to be run directly; they assume they are being called by another script - a script which ensures that all the standard Coppermine definitions and declarations have been made. Your plugin script is such a secondary script and is not meant to be run directly, hence the check for 'IN_COPPERMINE' and the quick exit if it doesn't exist. You can see this in action by typing the following URL into your web browser: (assuming Coppermine is located in the 'cpg' folder under your domain).

The next line of code (after the comment describing it) tells Coppermine which hook your plugin wants to access:

// Add a filter for the gallery header

With this line of code, you are registering your plugin to use this plugin hook. In this case, we're hooking onto the gallery_header hook which is located in in the load_template() function. This function parses your theme's template.html file, replacing tags like {ADMIN_MENU} and {LANGUAGE_SELECT_LIST} with the corresponding Coppermine HTML to render these content blocks. The two plugin hooks gallery_header and gallery_footer allow HTML to be placed before and after the {GALLERY} tag, respectively. (See the reference section below on these hooks for more detailed information.) On the main page, {GALLERY} is replaced with the "content of the main page" config setting, which usually includes the breadcrumb, category list, album list, and random/lastup blocks. On a page like the plugin manager (where you first see "Hello, world." after installing the plugin), {GALLERY} is replaced with the main part of the page, in this case, the plugin manager tables listing the plugins. The "Hello, World" plugin places the text "Hello, world." just above {GALLERY} in both places. If you modify the line of code above to use gallery_footer instead, you'll see "Hello, world." just below the main part of the page, as expected.

Let's break apart this line of code to understand how you register your plugin to use a particular plugin hook. For those programmers who have used classes before (in PHP or another language with similar constructs), you'll recognize the arrow operator -> as a class operator used to access class properties and methods. In this case, the Coppermine Plugin API provides $thisplugin as a pointer to the instance of your plugin's class. So when your plugin's codebase.php is loaded by the plugin API, you can access the properties and methods of your plugin's class by using $thisplugin. For example, $thisplugin->name refers to your plugin's name (whose value is defined in your plugin's configuration.php). See the section below on plugin class properties & methods for more detailed information.

In this case, we are using the add_filter() function to add the filter hook we want to use to our plugin's list of registered plugin hooks. The user-defined function we will use for each call of the hook is hello_world_header, which is defined in the last section of code in our plugin, as shown:

// Function to modify the gallery header
function hello_world_header($html) {
    $html = '<p style="color:red"><b>Hello, world.</b>  (tutorial 2.1)</p>'
    return $html;

Our function needs one parameter since it is attached to a filter hook (instead of an action hook), and here we name it $html. In this function, we add on the "Hello, world." text, which is tagged to be bold & red for extra effect. We need to ensure that the parameter is added-on to, not replaced, so that it plays well with other plugins who tag on to the same hook. We place the parameter coming in on a separate line to ensure we don't forget to do this. Finally, the function returns the modified parameter. And that does it.

So now you should know how a plugin, albeit a very basic plugin, works. The other sections in this tutorial describe useful features to add to your plugin including installation, configuration, linking in Coppermine, multi-language support, and distributing your plugin as a package. Read on and follow along.

Back to top

Installation, Configuration, and Clean-Up

The tutorial plugin so far merely outputs text via a particular hook. During the installation of the plugin, nothing special is done besides registering the plugin with Coppermine. The only configuration parameters are the identification ones in configuration.php. It's fairly easy to imagine additional installation steps and configuration parameters that would be useful in a real-world plugin. This section teaches you how to add such installation and configuration features to the tutorial plugin.

Back to top

Installation with Simple Configuration

First, we're going to add a very simple installation step, so you can see how the plugin installation can be customized to do whatever you like to do when the plugin is installed. If your plugin needs to create a custom table that it will use during operation, this section will describe where to add such a installation step. This simple installation is simple in that it can only be used for "hands-off" installation, where the user is not involved in customizing the installation. In this section, the user will merely confirm the installation and that's it. The next sections will expand on this to provide more complicated installation steps. This section is important because you need to know exactly how the Coppermine Plugin API calls your installation and configuration functions and that is best described with a very simple installation procedure.

Click here to download the plugin for this section.

Here's the new codebase.php with the new sections highlighted in bold.

Install this new plugin and give it a whirl. You can use the plugin package linked above, or you can edit the plugin code yourself if you want to get your hands dirty. It's certainly recommended for you to play with and modify each tutorial plugin to see how things work, and typing in the new code each time may be a useful exercise. If you have shell access to your web server, you could modify the plugin code directly, in fact you could modify the plugin code while the plugin is installed because the code is loaded on each page load and so the new code would be available immediately. However, it is not recommended to do this with a "live" website since you may break things in modifying the plugin code directly. You should set up a system for working on your plugin, ideally on a test installation, but it could also be done by including an administration check for each operational feature of your plugin so only you the admin would see the effect of the plugin in progress. Or you could set your site offline (in the Coppermine configuration) during this maintenance/upgrade work. The test installation is clearly the most versatile option, as long as you include enough test content there to simulate the real website and test out your plugin thoroughly.

This new configuration is obviously very simple, but it shows the core structure of customizing the installation of a plugin. We have added plugin_install and plugin_configure actions at the beginning. Then we define the functions that will implement these actions. The install action is executed when you click on the "install" button on the plugin manager page. The configure action is executed afterwards, depending upon what return value your install action sends back. The configure action is only executed at installation time and is meant as such to only include configuration that is done on installation. For other configuration parameters of your plugin that you want accessible while the plugin is running, you need to set up a separate function and/or script (a later section will give a "best practices" guide for doing this). To be clear: there is no plugin hook for configuring your plugin while it's running. You set up your own functions for this.

The install action is meant to direct the execution of the configure action and a "best practices" usage would have the configure action do the same for the install action. This may sound confusing, but it makes sense once you carefully step through the procedure. You'll find it very useful in many plugin implementations. The basic theory is this: the Plugin API asks your install function to install the plugin. If your install function needs a configuration step, it returns back a code telling the API that it needs to be configured first. Then your configure function is called. Once things are all set, your configure function should tell your install function this. Your install function then says "I'm ready" to the Plugin API, and the plugin is finally installed.

As a step-by-step guide, let's look at the order of operations for the installation of a plugin. Note: In the following, the Plugin Manager is the script pluginmgr.php, and the Plugin API is the script include/ Both will be capitalized here to make it clear a script (and its set of functions) is being referred to.

Installation Order of Operations:

You can use the install and configure hooks as you see fit. Keep in mind how and when each is called and place your installation steps in one or both of your hooked functions. One practice that is commonly used is the following:

  • Configure: Output a form to allow the user to interact with the installation.
  • Install: Process the configure form data; install the plugin or re-configure as necessary.

If your plugin does not require the user to configure any installation parameters, the configure form is often still used to tell the user that the plugin has been or will be installed, with a simple submit button which then returns the user to the plugin manager list. If you put your installation steps in the install function, then the message should say that the plugin will be installed. In this case, the install function needs to wait for the "Go" or "Continue" button on the configure form before executing the installation steps. If you put your installation steps in the configure function, then the message should say that the plugin has been installed, with a "Ok" or "Plugin Manager" button to return to the plugin manager page. Where you put your installation steps is up to you. Choose either the configure or install function or spread them between both.

Let's return to the current tutorial plugin. It demonstrates a common practice employed in the install and configure actions. It doesn't actually do anything else besides ask the user whether to install the plugin or not. If we check the order of operations listed above, we see that the install action is called first. Our install function first checks to see if the configure function has been called by checking for form POST data. If it doesn't exist, this is the first call to the install function and so the configure function needs to be called. We return the integer value 1 as our error code. (Note that we are calling it an error code because the API plugin installation as not been triggered yet. It doesn't mean that the install has run into a fatal fault. If you prefer, you can call it a program-flow code.)

Our configure function will output a form to ask the user whether to install the plugin or not. As a useful aside, we are going to use the plugin name (as defined by $name in configuration.php), so we need to ask for the global variable $thisplugin, which is a pointer to the plugin's object class. The plugin name is the object property $thisplugin->name. For more information, refer to this section on plugin class properties and methods.

Back to the configure form: we ask the user, "Do you want to install plugin Hello, World?". The four submit buttons are "Yes", "No", "I'm not sure", and "Simulate critical error". Each button will trigger a different return value in the install function. The form itself uses POST to return its data and it should be returned to the plugin manager. The best way to define the form action is as shown:

<form action="{$CPG_PHP_SELF}" method="post">

Now let's see what the buttons do. "Yes" triggers a return value of True which will cause the plugin to be installed. "No" means that the user wants to cancel the installation of the plugin. There is no way through the Plugin API to do so, so you need to do it yourself in your plugin code. Our plugin code shows a good way to cancel the plugin installation: send a header pointing to the Plugin Manager and exit, as shown:

("Location: pluginmgr.php\n\n");  // go back to plugin manager without installing the plugin

The button "I'm not sure" triggers a return value of "2" in the install function, which triggers another display of the configure form. For fun, we add a line regarding having enough time to decide and modify the button to "I'm still not sure". This shows how you can use the integer return value from the install function to direct program flow in the configure function. The return value is passed to the configure function; you can call the parameter whatever you like. We called it here $action_code.

Finally, the button "Simulate critical error" shows you what happens when you return False from the install function. Admittedly, this is not very useful to the poor administrator trying to install your plugin, so it should not be used unless you do not implement another way to inform the user of an installation problem. There may be times when your plugin installation runs into a fatal error. For example, your plugin may add a table for its data but during installation, the table cannot be created. In this case, you should output an informative error message saying the table could not be created, then exit, returning to the plugin manager or using the cpg_die function. The user will appreciate your extra effort to tell him/her what went wrong with the plugin installation.

Now you should understand how the installation process works and the basics of coding your plugin installation. If you are uncertain about these basics, please re-read this section and play with the plugin. Once you have this groundwork set, the next section shows how you can save configuration parameters during your installation.

Back to top

Uninstallation & Clean-Up

Use the plugin cleanup mechanism to perform a custom action when the plugin is being uninstalled.

Add something like
at the top of ./plugins/coffee_maker/codebase.php. The function could simply perform some queries that delete the database changes your plugin install might have caused. The actual uninstall action is being triggered like this:
Here's a more complex example function that will ask the user if the plugin settings should be kept or removed and performs the corresponding action.

Back to top

Linking to Custom Plugin Scripts

You can refer to any php-driven file within your plugin folder using the file URL parameter. The only pre-requisite is that the file you refer to needs to comply to the naming conventions for files within plugins.

Let's assume that you want to refer to a PHP-driven file named foobar.php that resides inside your custom plugin folder named "coffee_maker": To refer to that file, refer to index.php?file=coffee_maker/foobar, i.e. to or (even shorter) to

With this being said, it's obvious that you don't have to move files into the coppermine root folder - you can refer to files that reside in your plugin's sub-folder as if it resided in coppermine's root folder that way.

You (as a coppermine plugin author) mustn't create plugins that require end users to move files around before they can start using your plugin. It's simply not necessary to create plugins that way, as you can savely do as if files resides in coppermine's root folder by using the technique used in this section.

Back to top

Adding a Button to Coppermine

There are two places where you might want to add a menu item to: to the admin menu (usually to offer a link to a plugin's config screen) or to the "regular" coppermine menu.

Adding a Button to the admin menu

function coffee_maker_bar_config_button($admin_menu){
    global $lang_plugin_coffee_maker, $CONFIG, $coffee_maker_icon_array;
    if ($CONFIG['plugin_coffee_maker_config_link'] == 1) {
	    $new_button = '<div class="admin_menu admin_float"><a href="index.php?file=coffee_maker/index&action=configure"';
	    $new_button .= ' title="' . $lang_plugin_coffee_maker['config'] . '">';
	    $new_button .= $coffee_maker_icon_array['config_menu'] . $lang_plugin_coffee_maker['config'] . '</a></div>';
	    $look_for = '<!-- END export -->'; // This is where you determine the place in the admin menu
	    $admin_menu = str_replace($look_for, $look_for . $new_button, $admin_menu);
    return $admin_menu;

Adding a Button to the overall menu

function coffee_maker_bar_sys_button($menu) {
    global $lang_plugin_coffee_maker, $template_sys_menu_spacer, $CONFIG;
    if ($CONFIG['plugin_coffee_maker_how'] == 4) {
		coffee_maker_bar_language(); // Call the function that populates the language variable
        $new_button = array();
        $new_button[0][0] = $lang_plugin_coffee_maker['picinfo_heading'];
        $new_button[0][1] = $lang_plugin_coffee_maker['menu'];
        $new_button[0][2] = 'index.php?file=coffee_maker/index';
        $new_button[0][3] = 'coffee_maker';
        $new_button[0][4] = $template_sys_menu_spacer;
        $new_button[0][5] = 'rel="nofollow"';
        array_splice($menu, count($menu)-1, 0, $new_button);
    return $menu;

Back to top

Adding JavaScript to plugins

How to add JavaScript to plugins is being described in the section "How to include JavaScript files in plugins".

Back to top

Multi-language Support

You can make your plugins capable to be used in multiple languages. Therefore, your mustn't hard-code language strings into your plugin, but use variables instead. As you wouldn't want to introduce a load of individual language variables, you better use an array, preferably following the naming scheme $lang_plugin_foldername. All that is left to do is adding a little switch into your plugin that determines the end user's language and make it use the language file if present.

Let's assume that the folder your plugin resides in is named coffee_maker and the name is "Coffee Maker".

It's advisable to move the definition of languages and other resources that you want to use globaly into a separate file. This way, you can call the function directly if needed (outside the scope of the plugin).

Therefore, at the very start of codebase.php, right after the starting PHP tag, insert
require_once "./plugins/coffee_maker/";

Of course you need to create the file you just refered to.

This is how the content of should look like:
function coffee_maker_language() {
	global $CONFIG, $lang_plugin_coffee_maker;
	require "./plugins/coffee_maker/lang/english.php";
	if ($CONFIG['lang'] != 'english' && file_exists("./plugins/coffee_maker/lang/{$CONFIG['lang']}.php")) {
	    require "./plugins/coffee_maker/lang/{$CONFIG['lang']}.php";

As you can see from the code above, inside your plugin's folder, there should be another folder named lang that contains the language files. It needs to contains english.php at least.

Here's how the content of english.php could look like:
$lang_plugin_coffee_maker['plugin_name'] = 'Coffee Maker';
$lang_plugin_coffee_maker['plugin_description'] = 'This plugin will turn your gallery into a full-featured coffee machine.';
$lang_plugin_coffee_maker['coffee_bean'] = 'coffee bean';
$lang_plugin_coffee_maker['brew'] = 'brew';

Within codebase.php or any other plugin file, you can then use the language strings instead of hard-coding them in. Don't forget to make them global inside functions (see PHP: variable scope).

Back to top

Adding a config section to your plugin

It's advisable to have a config screen available for your plugin - nearly all plugins sooner or later need one.


To accomplish that, create an empty file inside your plugin folder and name that admin.php in analogy to the naming of coppermine's config screen. The config screen can then be accessed with the URL http://yoursite.tld/your_coppermine_folder/?file=coffee_maker/admin, so it's advisable to add a link to the plugin manager that points to your config screen. To accomplish that, add the link to the string $extra_info in configuration.php.

$extra_info .= '<a href="index.php?file=coffee_maker/admin" class="admin_menu">' . $lang_plugin_coffee_maker['plugin_configuration'] . '</a>';
Now let's compose the config screen for your plugin; edit admin.php with a plain text editor and add
    cpg_die(ERROR, $lang_errors['access_denied'], __FILE__, __LINE__);
at the very start to make sure that only the admin can access the plugin's configuration screen and nobody else.
If you're using i18n, i.e. if you are using the multi-lingual feature, don't forget to add your call to the language file into your config screen by adding
require_once "./plugins/coffee_maker/";
Now it's time to add the actual functionality to your config screen. Therefore, there are five steps that we actually need to perform:

Here's a default config page with only one config option that is first split into chunks to comment them:


As suggested above, in the header you just need to make the preparations, e.g. include the language file and make sure that only the gallery admin can access the config page. To accomplish this, use this piece of code:
    cpg_die(ERROR, $lang_errors['access_denied'], __FILE__, __LINE__);
require_once "./plugins/coffee_maker/";


Let's populate the variables that we need later. If you plan to beautify your config options using icons, define them now, using this code:
$plugin_coffee_maker_icon['submit'] = cpg_fetch_icon('ok', 1);
We'll need to specify as well what variables you'll be using inside the form and what type they are:
$sanitization_array = array(
    'plugin_coffee_maker_width' => array('type' => 'int', 'min' => '10', 'max' => '32'),
    'plugin_coffee_maker_height' => array('type' => 'int', 'min' => '0', 'max' => '100'),
    'plugin_coffee_maker_enabled' => array('type' => 'checkbox', 'min' => '0', 'max' => '1'),
This looks more complicated than it actually is: the keys of the array $sanitization_array (i.e. 'plugin_coffee_maker_width') needs to correspond to the name of the form field. It's advisable to respect the naming scheme, i.e. to name your form fields as suggested in this example, using the word "plugin", followed by an underscore, followed by the plugin folder name, followed by another underscore, followed by a short description of the actual purpose of the field. Keep in mind though that there is a limitation in the overall number of characters allowed here: the names we assign here will later be used as well when coming up with the database records. We'll use the config table to store our plugin values, so we need to respect the maximum of 40 characters for the name of the config field. If you need to use abbreviations, choose them wisely to be able to identify your stuff later.
The values for the key 'type' can be 'int', 'checkbox', 'raw' or 'array'. Depending on that value, there are different keys available. The following table should explain what can be done:
type Usage (range) Parameters
int Text input fields where only integers are allowed 'min' -> minimum value
'max' -> maximum value
checkbox Radio buttons and checkboxes with the values of the individual fields set to integers. The difference lies in the way that browsers handle empty checkboxes: if a checkbox is not ticked, there is no data submit with the form, just as if the checkbox wasn't there in the first place. This can cause issues if you explicitely empty a checkbox. The type 'int' can't handle this, but the type 'checkbox' can. 'min' -> minimum value
'max' -> maximum value
raw Use this for text input fields where you expect other content than integers - you can sanitize the input further using the parameter for this field. 'regex_ok' -> regular expression that the submit data needs to match against.
array Use this for an array of possible results that comes from one field, separated with a particular delimiter 'regex_ok' -> regular expression that the submit data needs to match against, similar to the examples given above for the type 'regex'
'delimiter' -> character that is being used to delimit one array element from the other inside the string, e.g. a slash

Sanitize form data & write to database

We only need to sanitize the form data if the form has actually been submit, so we'll wrap the next section between if ($superCage->post->keyExists('submit')) { and the closing curly bracket } that is needed to close the if construct. You will be able to re-use the piece of code in each and every plugin - it has been designed for that purpose. Use this piece of code:

Populate form options

The radio buttons and checkboxes need to be populated with values based on the corresponding values:
if ($CONFIG['plugin_coffee_maker_enabled'] == '1') {
	$option_output['plugin_coffee_maker_enabled'] = 'checked="checked"';
} else {
	$option_output['plugin_coffee_maker_enabled'] = '';

Output the form

Coming up with the actual config form output now should be dead easy, starting with the pageheader, the population of the form token variables, followed by the form tag and the table start. Basically the output starts like this:
list($timestamp, $form_token) = getFormToken();
echo <<< EOT
EOT; starttable('100%', $lang_plugin_coffee_maker['configuration'], 2, 'cpg_zebra');
Now you populate the table, using the heredoc syntax to come up with one table row per config setting:
echo <<< EOT
		<td valign="top">
			<label for="plugin_coffee_maker_enabled" class="clickable_option">{$lang_plugin_coffee_maker['enable_brewing_of_coffee']}</label>
		<td valign="top">
			<input type="checkbox" name="plugin_coffee_maker_enabled" id="plugin_coffee_maker_enabled" class="checkbox" value="1" {$option_output['plugin_coffee_maker_enabled']} />
[... more table rows here ... ]
Finally, you have to Therefore, the last chunk of code looks like this:
echo <<< EOT
		<td class="tablef" colspan="2">
			<input type="hidden" name="form_token" value="{$form_token}" />
			<input type="hidden" name="timestamp" value="{$timestamp}" />
			<button type="submit" class="button" name="submit" value="{$lang_plugin_coffee_maker['submit']}">{$coffee_maker_icon_array['ok']}{$lang_plugin_coffee_maker['submit']}</button>
echo <<< EOT

Back to top

Distributing your plugin

If you have written a plugin that works for you, why not share it with the Coppermine community - others might find your plugin helpfull as well. If you decide to share your plugin, please do as suggested in these steps:

Back to top

Plugin Hooks

For a detailed list of plugin hooks that you can use for your own plugins, please refer to the page plugin hooks. You'll find information there on how to find and use all the hooks available in Coppermine.

Back to top

Global Variables & Constants

There is a list of global variables and constants used througout coppermine that can be accessed as well from within plugins.