Introduction

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.

The Basics

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 yodeling.
 #! 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.

↑ Contents

Getting Things Done

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.

Hooks

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.

↑ Contents

Addon Interface (Short, Fast, Wimpy Introduction)

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.

↑ Contents

Interlude: Variable Scoping

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.

More Fun With Hooks

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.

↑ Contents

Fin

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.

Addon Interface Reference

Adding Functions

addMainFunction

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.

registerMainFunction

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.

addAdminFunction

Adds a new option to the list of options on the Administration page. Syntax is identical to addMainFunction.

 $addon->addAdminFunction($title, $description, $function);

registerAdminFunction

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);

hook

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);

Basic Layout & HTML

pageHeader

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.

↑ Contents

pageFooter

Prints a standard Coranto page footer. Required at the end of your page if you earlier used pageHeader.

 $addon->pageFooter();

link

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">

form

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);

Data & Settings

open

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);

↑ Contents

addAdvancedSetting

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.

addAdvancedSettingHeading

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.

Error Handling

checkBuild

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.

↑ Contents

fatalError

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.

minorError

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.

Miscellaneous

addNewsField

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.

removeNewsField

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.

↑ Contents

addProfileType

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.)

addStyleType

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.)

HTML & Display

simplePage

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.

heading

Returns the HTML for a heading.

 print $addon->heading($heading);
 $heading: The text of the heading.

itemTable

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.

↑ Contents

fieldsTable

Returns the <table> tag for a table suitable for a listing of fields, like the one on news submission forms.

 print $addon->fieldsTable();

fieldsTableRow

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>';

descParagraph

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).

settingTable

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.

submitButton

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 labeled Submit.

↑ Contents




Page last modified on February 02, 2009, at 07:46 AM