phpBB

Development Wiki

Using phpBB3's Basic Functions

From phpBB Development Wiki

Abstract

This section describes the most important classes and methods in phpBB3. Database related functions are explained in the DBAL chapter. This chapter will introduce the core functions of phpBB3. Read this carefully, as the use of these functions is an integral part of the MOD DB Requirements.

1.4.1. User input

request_var($var_name, $default, $multibyte = false, $cookie = false)

phpBB3 introduces a new way for reading variables from the request: the request_var function. The function ensures that the read variable is of the right type and applies the appropriate treatment to ensure a secure use. So, instead of using the old php arrays like $HTTP_POST_VARS and $HTTP_GET_VARS, the function request_var should be used. It is permissible to use -- for instance -- $_POST to check whatever or not a var was submitted by a given method, but such parameters should never be assigned directly. Every string returned is already stripslashed (if needed) and htmlspecialchars run on it.


Important
request_var is the only allowed way to read parameters with their values. Using POST/GET to check for the existence of a variable is allowed, like this:
$submit = (isset($_POST['submit'])) ? true false;


Let's have a closer look at this.

function request_var($var_name$default$multibyte false$cookie false)


- $var_name The name of the passed variable, as used in the corresponding HTML form. - $default The default value, should there be no such variable in the request. Also - maybe more importantly - determines the type of the returned variable. - $multibyte Optional. Set this, if the input may contain not-ASCII characters; should be used for almost all strings. - $cookie Optional. Set this, if the variable might be passed via the cookie.

The most important parameter is $default, as it determines the treatment given to the passed variable. If $default is an integer, the result will be cast to int. If it's a string, the result will be run through htmlspecialchars, and so on. Note that arrays always default to the empty array.

// Reads the variable 'int_test'. The variable will be cast to int. If there is no such variable 0 will be returned. 
$int_test request_var('int_test'0);

// Reads the variable 'string_test'. The variable will be run through htmplspecialchars. If there is no such variable, then 'test' will be returned. 
$string_test request_var('string_test''test');

// Reads the variable 'int_array_test'. The entries will be cast to int. If there are no such variables, then an empty array will be returned
$int_array_test request_var('int_array_test', array(0));

// Reads the variable 'int_string_array_test'. The keys will be cast to int, the values run through htmlspecialchars. 
// If there are no such variables, then an empty array will be returned.
$int_string_array_test request_var('int_string_array_test', array(=> ''));
...

1.4.2. Preserving sessions

append_sid($url, $params = false, $is_amp = true, $session_id = false)

phpBB will try to use cookies to track sessions, but as this is not always possible, there's url-rewriting as backup. To allow this system to work, there's one simple requirement: all links in forum pages need to be passed through the function append_sid.

1.4.3. Redirecting users

meta_refresh($time, $url)

To redirect other users to other pages, for instance after a performed action, use the meta_refresh function. The first parameter is the time in seconds until redirection, the second is the url to redirect to. Remember to use append_sid when redirecting to another forum page.

$meta_info append_sid("{$phpbb_root_path}api_example.$phpEx");
meta_refresh(3$meta_info);

1.4.4. The User Class

This class is the main toolbox to check and alter the current user's information. The user class extends the session class. The instance holding the data for the current user is named $user. The very basic use is setting up a user's session at the beginning of a page, which is done like this:

$user->session_begin();
$auth->acl($user->data);
$user->setup();
...

Let's see what kind of information can be accessed via the user Object. This has the objective of showing what's there, without explaining every last bit or any claim to completeness.

  • lang The array holding all defined language entries for the user. You can add more language files with the add_lang method or by using the lang parameter of setup.
  • page Information about the current location. Always take this information with a grain of salt, as it might not be totally correct. Also, using these might pose a security risk, so be careful.
    • page_name The current page's name.
    • page_dir The path from phpbb_root_path.
    • query_string
    • script_path
    • root_script_path
    • page The current page with query string.
  • data The actual data and settings of the user.
    • user_id
    • user_type
    • group_id The user's default group.
    • user_ip
    • user_regdate
    • username
    • username_clean The all lower-case normalized version of the username for comparisons.''
    • user_email
    • user_email_hash
    • user_birthday
    • user_lastvisit
    • user_lastmark
    • user_lastpost_time
    • user_lastpage
    • user_last_confirm_key security relevant
    • user_last_search
    • user_warnings
    • user_last_warning
    • user_login_attempts
    • user_inactive_reason
    • user_inactive_time
    • user_posts
    • user_lang
    • user_timezone
    • user_dateformat
    • user_style
    • user_rank
    • user_colour
    • user_message_rules
    • user_full_folder
    • user_emailtime
    • user_topic_show_days, user_topic_sortby_type, user_topic_sortby_dir, user_post_show_days, user_post_sortby_type, user_post_sortby_dir Preferences for reading
    • user_notify
    • user_notify_pm
    • user_notify_type
    • user_allow_pm
    • user_allow_viewonline
    • user_allow_viewemail
    • user_allow_massemail
    • user_options
    • user_avatar
    • user_avatar_type
    • user_avatar_width
    • user_avatar_height
    • user_sig
    • user_sig_bbcode_uid and user_sig_bbcode_bitfield Needed to render the signature.
    • user_from
    • user_icq
    • user_aim
    • user_yim
    • user_msnm
    • user_jabber
    • user_website
    • user_occ
    • user_interests
    • user_actkey
    • user_newpasswd
    • is_registered
    • is_bot
    • session_admin
    • session_page
  • browser The user agent string.
  • host
  • session_id
  • ip
  • time_now
  • update_session_page

And now to a few selected methods:

  • setup function setup($lang_set = false, $style = false) The setup method is used for the initial preparation of the user object. Both its parameters are optional. lang_set can be used to add additional language files and expects either the name of a language file or an array of such names. Style can hold a style ID number.
  • add_lang function add_lang($lang_set) Add_lang is used to load additional language files dynamically. It expects either a filename or an array of filenames. Never use the second and the third parameter.

See the Source Code documentation for session.php to learn further details about the user class.

1.4.5. Validating input

validate_data($data, $val_ary)

functions_user offers a powerful set of functions to validate input, mostly designed for user profiles. It expects an array holding the values and another one holding the test parameters and returns an array of error messages - which will be empty if everything is fine

Here a simple example for checking the password change of an user. Note that the array keys in both arrays match and that several entries can be defined for a single key by using an array of checks. Also note that the error strings might be undefined if you use nonstandard key names.

$data = array(
    
'user_password'     => request_var('user_password'''true),
    
'password_confirm'  => request_var('password_confirm'''true),
);

$check_ary = array(
    
'user_password'     => array(
        array(
'string'true$config['min_pass_chars'], $config['max_pass_chars']),
            array(
'password')),
    
'password_confirm'  => array('string'true$config['min_pass_chars'], $config['max_pass_chars']),
);

$error validate_data($data$check_ary);

For instance, array('string', true, $config['min_pass_chars'], $config['max_pass_chars']), means: use the check for strings, use "true" as the first parameter, $config['min_pass_chars'] for the second and $config['max_pass_chars'] as the third. validate_data knows the following input formats:

  • string Basic checks for strings
    • optional boolean. If true, empty will be accepted. Defaults to false.
    • max int. Minimal length.
    • max int. Maximal length
  • num Basic checks for numbers.
    • optional boolean. If true, empty will be accepted. Defaults to false.
    • max number. Minimal value. Defaults to 0.
    • max number. Maximal value. Defaults to 1E99
  • match Checks two values for equality
    • optional boolean. If true, empty will be accepted. Defaults to false.
    • match The other value
  • username Usernames. Uses the current ban data, settings, and taken usernames.
    • allowed_username One username which will be accepted regardless of the configuration. Defaults to the current user's username.
  • email email addresses. Uses the current ban data, settings and used emails.
    • allowed_email One address which will be accepted regardless of the configuration. Defaults to the current user's email.
  • match Checks value against a regular expression
    • optional boolean. If true, empty will be accepted. Defaults to false.
    • match The Perl-compatible regular expression


Important
You might need to apply more than one check on a submitted value.


1.4.6. Checking authorisation

acl_get($opt, $f = 0)

To use the auth system, you will first have to set the current user's permissions. This is done with the acl method of the $auth object. $auth->acl($user->data);. This will only work after the user's session was initialised.

Permissions are grouped into different types. These are a_ for admin permissions, u_ for user permissions and f_ for forum permissions. Admin permissions and User permissions are generally global, meaning that they do apply under all circumstances. Moderator permissions can be local or global, forum permissions are always local. Local in this regard means that they do only apply for certain fora, and not the board as a whole - moderator permissions are a typical example.

To check a permission for the current user, use the acl_get method of the $auth object. It takes the name of the permission as argument, and returns either true or false. For local permissions, the forum ID has to be supplied as second argument. If you want to check more than one permission at once, use the acl_gets method.


Tip
Permissions can be negated by prefixing them with '!'.


// checking one global permissions.
if (!$auth->acl_get('a_groups'))
// checking two local permissions.
if (!$auth->acl_gets('f_list''f_read'$forum_id))

1.4.7. Inserting Posts and Private Messages

generate_text_for_storage(&$text, &$uid, &$bitfield, &$flags, $allow_bbcode = false, $allow_urls = false, $allow_smilies = false)

submit_post($mode, $subject, $username, $topic_type, &$poll, &$data, $update_message = true)

function submit_pm($mode, $subject, &$data, $put_in_outbox = true)

Let's start with the basic feat of preparing the data to be inserted into a post or PM - for that matter any data that should be able to use BBCODES. phpBB3 offers a convenient function to prepare text for such tasks: generate_text_for_storage(&$text, &$uid, &$bitfield, &$flags, $allow_bbcode = false, $allow_urls = false, $allow_smilies = false). Use this function for both the subject and the message you want to post/pm, after running them through utf8_normalize_nfc.

// note that multibyte support is enabled here 
$my_text utf8_normalize_nfc(request_var('text'''true));
                
// variables to hold the parameters for submit_post
$uid $bitfield $options ''
                 
generate_text_for_storage($my_text$uid$bitfield$optionstruetruetrue);
Tip
There is also a function generate_text_for_display, which will create HTML from prepared messages.

We'll ignore polls and attachments in this section, as those are simply beyond the scope of this text. phpBB3 offers powerful methods to insert posts and PMs, however their use is not entirely trivial. We'll limit this introduction to the very basic features.

Let's start with inserting posts. This is the signature of the submit_post function, used to add new posts: submit_post($mode, $subject, $username, $topic_type, &$poll, &$data, $update_message = true). Use the value 'post' for the $mode parameter; subject and username (for guest posters) are straightforward, just insert the desired subject and username. $topic_type refers to the topic types (sticky etc., as described in constants.php). We won't cover polls; for the being just use 'false' for $poll. The interesting and important parameter is $data. $data is an array, containing the actual entries for the post . Most of these are very simple, but a few entries need to be prepared with the aforementioned generate_text_for_storage method. Use the following entries, there are more, but these will be enough to insert a message:

  • forum_id int. The forum ID. Can be empty for global announcements, otherwise use the forum where the post should be posted in.
  • icon_id The icon to display.
  • enable_bbcode boolean.
  • enable_urls boolean.
  • enable_sig boolean.
  • message The message text proper, has to be prepared with generate_text_for_storage
  • message_md5 Use md5() after running generate_text_for_storage over the message to obtain this valuemd5($my_message)
  • bbcode_bitfield Use the bitfield supplied by generate_text_for_storage
  • bbcode_uid string. Use the value supplied by generate_text_for_storage
  • post_edit_locked int
  • enable_sig boolean
  • topic_time_limit
  • post_edit_locked int, corresponds to the ITEM_LOCKED/ITEM_UNLOCKED constants
  • topic_title String
  • notify_set boolean
  • notify boolean
  • post_time int. Just use '0' for now
  • forum_name String. Only used for notify mails
  • enable_indexing boolean.
Tip
The submit_post function will always use the current $user object as poster.

A little example to show this in action Example 1.6, “Example API submit_post()”:

Example 1.6. Example API submit_post()

// note that multibyte support is enabled here 
$my_subject utf8_normalize_nfc(request_var('my_subject'''true));
$my_text    utf8_normalize_nfc(request_var('my_text'''true));

// variables to hold the parameters for submit_post
$poll $uid $bitfield $options ''

generate_text_for_storage($my_subject$uid$bitfield$optionsfalsefalsefalse);
generate_text_for_storage($my_text$uid$bitfield$optionstruetruetrue);

$data = array( 
    
'forum_id'      => 2,
    
'icon_id'       => false,

    
'enable_bbcode'     => true,
    
'enable_smilies'    => true,
    
'enable_urls'       => true,
    
'enable_sig'        => true,

    
'message'       => $my_text,
    
'message_md5'   => md5($my_text),
                
    
'bbcode_bitfield'   => $bitfield,
    
'bbcode_uid'        => $uid,

    
'post_edit_locked'  => 0,
    
'topic_title'       => $my_subject,
    
'notify_set'        => false,
    
'notify'            => false,
    
'post_time'         => 0,
    
'forum_name'        => '',
    
'enable_indexing'   => true,
);

submit_post('post'$my_subject''POST_NORMAL$poll$data);

The function for PMs behaves very much the same way. This is the signature of the submit_pm function, used to add new posts: function submit_pm($mode, $subject, &$data, $put_in_outbox = true). Use the value 'post' for the $mode parameter; subject is just that. As with posts, data is the important piece. Use the following entries:

  • address_list array. This value holds the recipients. It's a nested array of the following structure array ('u' => array(2 => 'to' 3 => ' bcc'), ('g' => array(2 => 'to' 3 => ' bcc'))) Meaning, the first level array has two subarrays: 'u' for users and 'g' for groups. Each of these hold arrays mapping the recipients user_id to the type of recipient he is ('to' or 'bcc').
  • from_user_id int. The sender's user ID
  • from_user_name String. The sender's username.
  • icon_id The icon to use.
  • from_user_ip
  • enable_bbcode boolean.
  • enable_smilies boolean.
  • enable_urls boolean
  • enable_sig boolean
  • message The message text proper. Remember to run this through
  • bbcode_bitfield Use message_parser to obtain this value
  • bbcode_uid Use message_parser to obtain this value
// note that multibyte support is enabled here 
$my_subject utf8_normalize_nfc(request_var('my_subject'''true));
$my_text    utf8_normalize_nfc(request_var('my_text'''true));

// variables to hold the parameters for submit_pm
$poll $uid $bitfield $options ''
generate_text_for_storage($my_subject$uid$bitfield$optionsfalsefalsefalse);
generate_text_for_storage($my_text$uid$bitfield$optionstruetruetrue);

$data = array( 
    
'address_list'      => array ('u' => array(=> 'to')),
    
'from_user_id'      => 2,
    
'from_username'     => 'test',
    
'icon_id'           => 0,
    
'from_user_ip'      => $user->data['user_ip'],
     
    
'enable_bbcode'     => true,
    
'enable_smilies'    => true,
    
'enable_urls'       => true,
    
'enable_sig'        => true,

    
'message'           => $my_text,
    
'bbcode_bitfield'   => $bitfield,
    
'bbcode_uid'        => $uid,
);

submit_pm('post'$my_subject$datafalse);


Tip
To use submit_post you have to include functions_posting, to use submit_pm functions_privmsgs.

1.4.8. Building URLs, hidden fields and other HTML

build_hidden_fields($field_ary, $specialchar = false, $stripslashes = false)

build_url()

phpBB3 offers a few functions to take care of the dirty deed of untemplated HTML. The important entries in the category are build_hidden_fields and build_url. Neither is doing anything unexpected, but here we go: build_hidden_fields($field_ary, $specialchar = false, $stripslashes = false). The function will use the entries of the first parameter (array, name value) to build a string of hidden HTML input fields. That string is returned as result. If the second parameter is set to true, the function will use htmlspecialchars on the keys and the values in the first parameter; if the third is true it will use stripslashes. See Example 1.7, “Example confirm box”

build_url is even simpler: it will generate a valid url to the current page in the forum and return it as string. You can pass an array of GET parameter names which should be stripped from the url as argument.

1.4.9. Errors

Strictly speaking, this is not part of phpBB's code, but as it is a major change for coders used to the message_die function from phpBB2, we'll cover it anyway. The use is very simple: just supply the string you want displayed as error message. The lang system will be applied on the argument, but you should use the user object in most cases, like this: trigger_error($user->lang['SOME_LANG_KEY']);

For privilege breaches, you can add the constant "E_USER_WARNING" like (Note: that's not a valid language entry) this trigger_error($user->lang['NOT_MANAGE_FOUNDER'] . adm_back_link($this->u_action), E_USER_WARNING);

1.4.10. Log In

login_box($redirect = , $l_explain = , $l_success = , $admin = false, $s_display = true)

Login prompts are easily generated with the login_box function. The first parameter - $redirect - indicates the page where the user should be redirected to after a successful login; the second - $l_explain - contains the text which should be shown as explanation and the third - $l_success - should contain the text to display in the case of a successful login.


Tip
Leaving $redirect empty will cause redirection to the original page; in fact the function can be called without any parameters.


A small example to illustrate the use:

if ($user->data['user_id'] == ANONYMOUS || $user->data['is_bot'])
{
    
login_box(''$user->lang['SOME_LANG_KEY']);
}

1.4.11. Confirm Box

confirm_box($check, $title = , $hidden = , $html_body = 'confirm_body.html', $u_action = )

The confirm boxes are phpBB3's system to secure critical actions against CSRF attacks and unintentional triggering. The system is quite simple: the function can be called in check mode, where it will check the presence of a one-time confirmation string passed as request parameter or in display mode, where it will display the confirm box.

Let's take a quick glance at the function and its parameters. confirm_box($check, $title = , $hidden = , $html_body = 'confirm_body.html', $u_action = ). Generally, you will need to supply the first three parameters, while the remaining two are usually fine by default - see the code documentation for details. $check correspondends to the aforementioned mode: true will run the function in check mode, false in display. The second parameter should be a language key used to explain to the user what the confirm box is about. The third parameter, $hidden, should hold html for hidden fields containing all (user-)submitted values needed to get the current script to exactly the state where it is when calling the function. Let's see this in action Example 1.7, “Example confirm box”/;

Example 1.7. Example confirm box

if ($submit)
{
    
// check mode
    
if (confirm_box(true))
    {
        
submit($my_message);
    }
    else
    {
        
$s_hidden_fields build_hidden_fields(array(
            
'submit'    => true,
            
'my_mesage' => $my_message,
            )
        );

        
//display mode
        
confirm_box(false'SAMPLE_LANG_KEY'$s_hidden_fields);
    }

1.4.12. Page Header and Footer

page_header()

page_footer()

make_jumpbox($action)

Page headers and footers are easily created by calling the respective functions page_footer() and page_header(). If the templates include the header/footer template files, then this will ensure the display of the forum footers and headers.

1.4.13. Bringing it all together: A Sample Page

The PHP file:

<?php
/** 
*
* @package phpBB3
* @version $Id: v3_api.xml 52 2007-12-09 19:45:45Z jelly_doughnut $
* @copyright (c) 2007 phpBB Group 
* @license http://opensource.org/licenses/gpl-license.php GNU Public License 
*
*/


// The default phpBB inclusion protection - required
define('IN_PHPBB'true);
$phpbb_root_path './';
$phpEx substr(strrchr(__FILE__'.'), 1);


include(
$phpbb_root_path 'common.' $phpEx);
include(
$phpbb_root_path 'includes/functions_privmsgs.' $phpEx);


// Start session management
$user->session_begin();
$auth->acl($user->data);
// our lang file is in the mods directory
$user->setup(array('memberlist''mods/api_example'));


$page_title =  $user->lang['SEND_PM_SELF'];

// Read user input
$my_subject     request_var('my_subject'''true);
$my_message     request_var('my_message'''true);

$my_subject     utf8_normalize_nfc($my_subject);
$my_message     utf8_normalize_nfc($my_message);

$submit = (isset($_POST['submit'])) ? true false;

// check permissions
if (!$auth->acl_gets('u_sendpm''u_readpm'))
{
    if (
$user->data['is_registered'])
    {
        
trigger_error('EXAMPLE_SEND_PM_DENIED');
    }

    
login_box(''$user->lang['EXAMPLE_SEND_PM_LOGIN']);
}


if (
$submit)
{
    if (
confirm_box(true))
    {
        
// variables to hold the parameters for submit_pm
        
$poll $uid $bitfield $options ''
        
generate_text_for_storage($my_subject$uid$bitfield$optionsfalsefalsefalse);
        
generate_text_for_storage($my_text$uid$bitfield$optionstruetruetrue);

        
$data = array( 
            
'address_list'      => array ('u' => array($user->data['user_id'] => 'to')),
            
'from_user_id'      => $user->data['user_id'],
            
'from_username'     => $user->data['username'],
            
'icon_id'           => 0,
            
'from_user_ip'      => $user->data['user_ip'],
             
            
'enable_bbcode'     => true,
            
'enable_smilies'    => true,
            
'enable_urls'       => true,
            
'enable_sig'        => true,
                    
            
'message'           => $my_message,
            
'bbcode_bitfield'   => $bitfield,
            
'bbcode_uid'        => $uid,
        );
        
submit_pm('post'$my_subject$datatrue);
        
        
$meta_info append_sid("{$phpbb_root_path}api_example.$phpEx");
        
$message $user->lang['EXAMPLE_PM_SENT'] . '<br /><br />' sprintf($user->lang['RETURN_SEND_PM'], '<a href="' $meta_info '">''</a>');
        
meta_refresh(3$meta_info);
        
trigger_error($message);
    }
    else
    {
        
$s_hidden_fields build_hidden_fields(array(
            
'submit'        => true,
            
'my_message'    => $my_message,
            
'my_subject'    => $my_subject,
            )
        );
        
        
// display mode
        
confirm_box(false$user->lang['EXAMPLE_API_REALLY'], $s_hidden_fields);
    }
}

// Output the page
page_header($page_title);

$template->assign_vars(array(
    
'S_MY_MESSAGE'  => $my_message,
    
'S_MY_SUBJECT'  => $my_subject,
    
'S_SEND_ACTION' => build_url('confirm_key'),
    )
);

$template->set_filenames(array(
    
'body' => 'api_example.html')
);

make_jumpbox(append_sid("{$phpbb_root_path}viewforum.$phpEx"));

page_footer();

?>


The template file:

<!-- INCLUDE overall_header.html -->

<
div id="pagecontent">

    <
form name="send" action="{S_SEND_ACTION}" method="post">
    
        <
table class="tablebg" width="100%" cellspacing="1">
            <
tr>
                <
th>{L_SEND_TITLE}</th>
            </
tr>
            <
tr>
            <
td class="row3" align="center"><span class="genmed">{L_SEND_EXPLAIN}</span></td>
            </
tr>
            <
tr>
                <
th>{L_SEND_SUBJECT}</th>
            </
tr>
            <
tr>
                <
td class="row1" align="center"><input name="my_subject" value="{S_MY_SUBJECT}" size="76" /></td>
            </
tr>
            <
tr>
                <
th>{L_SEND_MESSAGE}</th>
            </
tr>
            <
tr>
                <
td class="row2" align="center"><textarea name="my_message" rows="10" cols="76">{S_MY_MESSAGE}</textarea></td>
            </
tr>
            
            <
tr>
                <
td class="cat" align="center"><input class="btnmain" type="submit" name="submit" value="{L_SUBMIT}" />
                &
nbsp;&nbsp;<input class="btnlite" type="reset" value="{L_RESET}" /></td>
            </
tr>
        </
table>
    </
form>
    
</
div>
<
br clear="all" />

<!-- INCLUDE 
breadcrumbs.html -->

<
br clear="all" />

<
div align="{S_CONTENT_FLOW_END}"><!-- INCLUDE jumpbox.html --></div>

<!-- INCLUDE 
overall_footer.html -->


The language. Be careful to use UTF8 encoding without BOM. That format is sometimes called UTF8-cookie.


Important
If the file language/en/mods/info_{module_name}.php (for example language/en/mods/info_ucp_karma.php) exists, it will automatically be included.


<?php
/** 
*
* example [English]
*
* @package language
* @version $Id: v3_api.xml 52 2007-12-09 19:45:45Z jelly_doughnut $
* @copyright (c) 2007 phpBB Group 
* @license http://opensource.org/licenses/gpl-license.php GNU Public License 
*
*/

/**
* DO NOT CHANGE
*/
if (empty($lang) || !is_array($lang))
{
    
$lang = array();
}

// DEVELOPERS PLEASE NOTE
//
// All language files should use UTF-8 as their encoding and the files must not contain a BOM.
//
// Placeholders can now contain order information, e.g. instead of
// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows
// translators to re-order the output of data while ensuring it remains correct
//
// You do not need this where single placeholders are used, e.g. 'Message %d' is fine
// equally where a string contains only two placeholders which are used to wrap text
// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine

$lang array_merge($lang, array(
    
'SEND_TITLE'                => 'Send a PM to yourself',
    
'SEND_EXPLAIN'              => 'Just a little example. You can use it to send PMs to yourself. Fun!',
    
'SEND_SUBJECT'              => 'Subject',
    
'SEND_MESSAGE'              => 'Message',
    
'EXAMPLE_API_REALLY'        => 'Do you really want to send a PM to yourself?',
    
'EXAMPLE_PM_SENT'           => 'You have sent a PM to yourself. You have no life.',
    
'RETURN_SEND_PM'            => 'That was fun. Let\'s do it %sagain%s.',

 
    
'EXAMPLE_SEND_PM_LOGIN'         => 'Login to send a PM to yourself',
    
'EXAMPLE_SEND_PM_LOGIN_SUCC'    => 'Send a PM',
    
'EXAMPLE_SEND_PM_DENIED'        => 'You are not allowed to send yourself PMs',
    
'SEND_PM_SELF'                  => 'Send a PM to yourself',
));

?>