Coranto addons allow you to modify just about everything about the way coranto works, without actually modifying the script files. But this power does come at the expense of simplicity and ease of learning: because addons are integrated so tightly, if you want to write an addon that performs a fairly major task, you're going to have to read parts of coranto's source code. This is inevitable. Don't let this completely scare you, though: it's possible to write simple addons after reading not much more than this document, and the addon architecture itself is quite simple. And if you have any questions about creating addons, send an e-mail.
Addons are, like Coranto, written in Perl. Addon files are in the form "cra_something.pl". Before describing the most basic elements of an addon, here's a code example. This is the smallest valid addon possible:
#! CRADDON 1
#! NAME Guido
my $addon = new Addon('Guido');
1;
This little addon named Guido, presumably contained in cra_guido.pl, is perfectly valid, will show up in the Addon Manager, and will generally get along fine with everyone. Of course, it does absolutely nothing. But that's OK.
Every addon must begin with the CRADDON and NAME lines. (Of course, "Guido" should be replaced with the name of your addon.) The CRADDON line tells Coranto that it's dealing with a valid addon, written for version 1 of the addon interface. The NAME line tells the Addon Manager what to call this addon. There are also some optional lines to give the Addon Manager some more information, as you can see in the example below.
#! CRADDON 1
#! NAME Guido
#! DESCRIPTION SWM seeks SMF for conversation, companionship, and yodelling.
#! VERSION 42
my $addon = new Addon('Guido');
1;
Again, DESCRIPTION and VERSION lines are optional. They simply provide extra information for the Addon Manager to display. The my $addon line creates an Addon object and puts it inside $addon. Once your addon actually does something, you'll be using this object to do several things. The final 1; line is required at the end of every addon (it must always be the last line) to stop Perl from complaining.
Now that you know how to make a well-formed addon, it's time to learn how to make your addon do something other than sit there and look pretty. There are two primary ways of tying your addon into Coranto: hooks and the addon interface. Most addons will use both together, but we'll begin by looking at them individually.
Look at a file like coranto.cgi or crcore.pl. Come on, come on: it's not that painful. Skim through the file until you see a line that looks like
# HOOK: Something
That's an addon hook. A hook allows you to stick your own code in at that point. When coranto reaches a point in its code labelled as a hook, it'll run any code that addons have hooked into that, um, hook. But how does an addon hook itself in? Here's how:
$addon->hook('Hook Name', 'What to Hook In');
See? We are using that $addon object! In the previous example, "Hook Name" is simple. It's the name of the hook, as labelled in the source. For instance, if you see the line
# HOOK: Spaniel
then the name of the hook is Spaniel. "What to Hook In" is a little more complex. It can be one of two things: a string or a reference to a scalar variable. (If you're not sure what a reference to a scalar variable is, consult your friendly local Perl documentation. Or, for simpler addons, just use the string method.) If it's a string, say "Collie", then when the hook in question is reached, the subroutine named Collie (which you presumably defined in your addon) will be executed.
It's time for an example. In this example, we'll use the hook CRHTMLHead which, if you look at coranto.cgi, you can see is located right after Coranto prints the <body> tag as part of the page header.
#! CRADDON 1
#! NAME Guido
my $addon = new Addon('Guido');
$addon->hook('CRHTMLHead', 'Guido_CRHTMLHead');
sub Guido_CRHTMLHead {
print '<h4>Hi. My name is Guido, but my friends call me Jane Austen.</h4>';
}
1;
The above addon actually does something. It may not be useful, but hey, it's something. It will introduce itself at the top of every page. The way it works should hopefully be clear. One note: the name of the subroutine is arbitrary. It can be anything you want it to be. But to avoid conflicts with other addons and to keep things clear, it's highly recommended that you use the form AddonName_HookName as we did above. All subroutines defined in your addon should start with the name of your addon.
We'll go into more detail about hooks later, but now, we'll move to an introduction to the addon interface.
The addon interface is a collection of various methods available via the $addon object. That doesn't mean much, does it? Put differently: you can run commands via the $addon variable we covered briefly earlier. These commands are called the addon interface. All these commands are documented further down this page. We'll use a few of the more commonly-used ones here. We'll also go straight to an example, but the example is fairly heavily commented to explain what's going on.
#! CRADDON 1
#! NAME Guido
my $addon = new Addon('Guido');
# We've seen all the above stuff before.
$addon->addMainFunction('Guido', "Isn't he gorgeous?", 'guido');
# A-ha! The addon interface in action! What the above line does is add a new
# option to the function list on the main page. The option has a title of
# "Guido" and a description of "Isn't he gorgeous?".
# When you click on the option, you'll see that the URL it points to ends
# with "action=guido" -- that is, that option points a script
# function called "guido". Of course, no such function exists...
# yet.
$addon->registerMainFunction('guido', 'Guido_Main');
# The line above tells the script to execute subroutine Guido_Main when
# someone requests a function called guido. Works much like a hook.
sub Guido_Main {
# Of course, now we have to write that subroutine.
# Before we write it, here's what it'll do:
# print a simple HTML page, in the standard Coranto
# style, with a picture and some text.
$addon->pageHeader('Guido Says Hello');
# This prints a standard Coranto page header with
# title Guido.
print '<img src="guido.jpg">';
# This uses standard Perl to print the HTML for an image.
# Alas, we can't provide you with guido.jpg... you'll have to find
# one yourself.
print $addon->descParagraph("Don't you just want to hug him?");
# This prints some text. Now, we could have just used a standard Perl
# print statement, and everything would have worked just fine.
# But the descParagraph method formats things inside a table, using
# Coranto text styling. Addons should try to look as much like
# the remainder of the program as possible -- after all, they're
# forming part of a whole, and that whole should be as, well, whole
# as possible.
$addon->pageFooter();
# Prints the standard footer.
}
# The end.
1;
This addon should work. Try it. This should also give an example of what kinds of things the addon interface can be used for; it's highly recommended that you read the documentation for it further down and see all the various methods available.
This section can be summed up very simply: use my().
But, of course, we have to explain. An addon forms part of a larger script. Not only does it have to coexist with that larger script, but it has to coexist with other, unknown addons. The biggest problem with coexisting is that of global variables. Here's an example.
# THIS IS DUMB CODE: $filename = 'some.file'; # It works. It stores the name of the file in $filename, which will presumably # have something done to it elsewhere in the addon. But what happens if another # addon includes the following? $filename = 'another.file'; # Answer: you're in trouble. Your addon will now open "another.file", # probably resulting in serious problems. So what can you do? Easy: my $filename = 'some.file'; # I AM SMART CODE! # or, with a slightly different syntax: my $filename; $filename = 'some.file';
By declaring your variables with my(), you prevent anyone else from being able to touch them. (If you're not very familiar with the use of my(), here's a tutorial.) Of course, sometimes you may absolutely need to use a global variable. In that case, do the same as suggested earlier for subroutines: make every global variable you use start with the name of your addon, e.g. $Guido_filename.
Back to hooks. There are two topics we haven't covered: priority and the reference method mentioned earlier. First, priority. The full syntax for a hook is:
$addon->hook($HookName, $Code, $Priority);
where $priority is a number between -10 and 10. Priority only comes into play when multiple addons have hooked themselves in at the same hook. Addons which hook themselves in with a priority of 10 will go first; a priority of -10 will make you go last. Only use 10 and -10 if you absolutely, certainly, unquestionably need to go first or last; if you'd just like to go before addons that don't care about their placement, for instance, use a priority of 1. If you don't care about the order in which your addon is run, as is usually the case, just leave out the priority argument, as we did earlier.
Before, we covered putting the name of a subroutine in $Code. You have another option: make $Code a reference to a string (a scalar, to use the exact term) which contains the code that you want to run. Let's go to an example.
#! CRADDON 1
#! NAME Guido
my $addon = new Addon('Guido');
my $Guido_CRHTMLHead_B = q~
print '<h4>I don't understand my friends.</h4>';
~;
my $Guido_CRHTMLHead_A = q~
print '<h4>Hi. My name is Guido, but my friends call me Jane Austen.</h4>';
~;
$addon->hook('CRHTMLHead', \$Guido_CRHTMLHead_B, -5);
$addon->hook('CRHTMLHead', \$Guido_CRHTMLHead_A, 5);
1;
This prints those two phrases, with "My name is..." being the first one. If any other addon hooks into CRHTMLHead, and either doesn't give a priority or gives a priority between -5 and 5, whatever it prints will go between the two phrases our addon prints.
As you can see, the example above uses the reference-to-code method. But why? It's more complex than the subroutine-name method, which could easily have accomplished what we did above. And in this particular case, using the reference method provides no advantages. There are several cases in which the reference-to-code method does have a major advantage, though: it causes your code to run as if it were inserted right where the hook is. The advantage of this is that you can access lexical variables -- that is, variables declared with my() -- which an external subroutine wouldn't be able to access. So: only use the reference method when you need to, but you'll find that it will often come in useful.
That's it for our addon tutorial. Hopefully, you know the basics of how to create an addon. It's important that you also read the next section, though, as use of the addon interface is essential for an addon to integrate itself properly.
Adds a new option to the list of options on the main page (and the navigation bar at the bottom of all pages).
$addon->addMainFunction($title, $description, $function); $title: Name of the option $description: A description, shown beneath the name on the main page $function: The function that is called when the user clicks on the option. See registerMainFunction to control what happens when that function is called.
Controls what happens when a function is called (via an 'action' parameter sent via the URL or a POSTed form).
$addon->registerMainFunction($function, $sub); $function: Name of the function. $sub: The subroutine that is run when that function is called.
Adds a new option to the list of options on the Administration page. Syntax is identical to addMainFunction.
$addon->addAdminFunction($title, $description, $function);
Controls what happens when an administrative function is called (via an 'adminarea' parameter sent via the URL or a POSTed form). Syntax is identical to registerMainFunction. All admin functions, registered via this method, will be available only to Administrator users.
$addon->registerAdminFunction($function, $sub);
Allows an addon to introduce its own code at a particular spot, or hook. This is discussed in depth above.
$addon->hook($hook, $code, $priority);
Prints a standard Coranto page header. All addon pages should use this.
$addon->pageHeader($title, $admin); $title: The title of your page. $admin: If true (non-zero), adds a link back to the main Administration page to the top of the page. Administrative pages should use a value of 1, non-administrative pages should omit this parameter.
Prints a standard Coranto page footer. Required at the end of your page if you earlier used pageHeader.
$addon->pageFooter();
Returns an <a> (link) tag containing session keys and other important information. Use this to generate any script links in your pages.
print $addon->link($params, $tagoptions);
$params: A hash reference containing a key-value pair for every parameter
you want included in the link.
$tagoptions: Any extra HTML to put in the <a> tag, for instance
target="_blank"
EXAMPLE:
print $addon->link({'action' => 'admin', 'adminarea' => 'settings'});
will print something like:
<a href="coranto.cgi?session=hgghjUyuiuyg&action=admin&
adminarea=settings">
Returns a <form> tag (using the POST method) containing session keys and other info in hidden fields. Syntax is identical to link.
print $addon->form($params, $tagoptions);
A replacement for Perl's open() function. Adds security, file locking, and error handling. Use this instead of open() whenever you're opening a file. (It doesn't handle opening piped programs, so you'll still have to use open() for that, but be VERY careful, as opening a program has many security risks.)
my $fh = $addon->open($file);
$file: The name of a file. Works just like open(): precede the filename with
> to open for output, >> to open for append.
$fh: A filehandle, stored in a variable. Use it just like a normal filehandle.
EXAMPLE: (this example copies file.txt to newfile.txt)
my $fh = $addon->open('/usr/home/me/file.txt');
my $fh2 = $addon->open('>/usr/home/me/newfile.txt');
while (<$fh>) {
print $fh2 $_;
}
close($fh);
close($fh2);
Adds a setting to the Change Settings page. Once set, the value of the setting is available via %CConfig.
$addon->addAdvancedSetting($internalname, $title, $desc, $html); $internalname: The key of %CConfig under which the value of the setting will be available. Alphanumeric only. $title: The name of your setting, as displayed on the Change Settings page. $desc: A description of your setting. $html: By default, your setting will have a one-line text box. If you'd rather have a yes/no option box, set this variable to "yn". Or you can set it to your own custom HTML for a form field. (This must be called by your addon every time it loads.) See addAdvancedSettingHeading (next) for an example.
Adds a heading to the Advanced Settings page, used to group settings.
$addon->addAdvancedSettingHeading($heading);
$heading: Text of the heading.
EXAMPLE:
$addon->addAdvancedSettingHeading('MyAddon Settings');
$addon->addAdvancedSetting('MyAddon_Enable', 'Enable MyAddon',
'Turns on MyAddon and causes it to do many wonderful things.',
'yn');
If the user chooses yes, then $CConfig{'MyAddon_Enable'} will equal 1,
otherwise it will equal 0.
If your addon will only work with a certain version (and subsequent versions), use this. It will display an error, tell the user to upgrade in order to use your addon, and finally disable the addon until the user upgrades.
$addon->checkBuild($minimumbuild); $minimumbuild: The lowest build number that your addon will work with.
When something goes wrong that shouldn't go wrong, use this. It displays an error message with lots of diagnostic information (particularly useful to send to someone else for support) and then exits immediately.
$addon->fatalError($message); $message: The error message.
Use this for most standard errors. It prints a nicely-formatted error message and then exits. It differs from fatalError in that it doesn't print all the diagnostic information which, while useful for odd, unexpected errors, can be confusing and even intimidating for simple, straightforward errors like forgetting to fill out a field.
$addon->minorError($message); $message: The error message.
Adds a new news field.
$addon->addNewsField($internalname, $type, $dispname); $internalname: The field's internal name. If the field's internal name is "Spaniel", it will be accessed via $Spaniel. WARNING: Unless the internal name begins with "CustomField_", users will not be able to delete the field themselves. $type: Set to 1, 2, 3 or 4, as follows. 1: Single-line text field. 2: Multi-line text field. 3: Select box. 4: Checkbox $dispname: The full name of the field. This is what it will be called on news submission forms.
Removes a news field.
$addon->removeNewsField($internalname); $internalname: The field's internal name. WARNING: This will delete any information stored in the field. Another warning: while it's possible to delete built-in fields like $Text or $Subject, don't. Bad things will happen.
Adds a new profile type. This is a fairly advanced and difficult thing to do; as well as calling this method, adding a profile type requires hooking yourself in at points like ProfileList_NewType_Status, ProfileList_NewType_Functions, AddProfile_NewType, GetStyleProfiles, and BuildNews_ProfileType.
$addon->addProfileType($profilename); $profilename: The name of the profile. (New profile types don't persist; you have to call this method every time your addon loads.)
Adds a new style type, which then appears as an option when creating new styles.
$addon->addStyleType($stylename); $stylename: The name of the style. (New style types don't persist; you have to call this method every time your addon loads.)
Displays a simple page, suitable for giving the user a brief message (for instance to confirm that something has been done).
$addon->simplePage($title, $message, $admin); $title: The title of the message. $message: The message to display in the body of the page. $admin: If true (non-zero), adds a link back to the main Administration page to the top of the page. Administrative pages should use a value of 1, non-administrative pages should omit this parameter.
Returns the HTML for a heading.
print $addon->heading($heading); $heading: The text of the heading.
Returns a three-section, three-colour table like those used on the Edit Users or Manage Profiles pages. The first row of the table should be used for a name or title, the second for description or status, and the third for actions or functions.
print $addon->itemTable($row1, $row2, $row3); $row1, $row2, and $row3: Text or HTML for the content of the respective rows.
Returns the <table> tag for a table suitable for a listing of fields, like the one on news submission forms.
print $addon->fieldsTable();
Returns the HTML for a row in a table created by fieldsTable.
print $addon->fieldsTableRow($name, $content);
$name: The contents of the left column, generally used for the name
of the field.
$content: The contents of the right column, generally used for the
value of the field or a form field.
EXAMPLE:
print $addon->fieldsTable(),
$addon->fieldsTableRow('Name', 'Guido'),
$addon->fieldsTableRow('Head Circumference', '28 cm'),
'</table>';
Returns the HTML for a center-aligned table, suitable for containing a descriptive paragraph.
print $addon->descParagraph($text); $text: The contents of the table (the paragraph or message).
Returns the HTML for a table suitable for describing a setting, like those used on the Advanced Settings page.
print $addon->settingTable($name, $form, $description); $name: The name to display for the setting. $form: The value or form field for the setting. $description: A description of the setting.
Returns the HTML for a table containing Submit/Reset buttons.
print $addon->submitButton($name); $name: The name of the Submit button. Optional; if not specified, the button will be labelled Submit.