tips for phpbb3 modders

Discussion of general topics related to the new version and its place in the world. Don't discuss new features, report bugs, ask for support, et cetera. Don't use this to spam for other boards or attack those boards!
Forum rules
Discussion of general topics related to the new release and its place in the world. Don't discuss new features, report bugs, ask for support, et cetera. Don't use this to spam for other boards or attack those boards!
asinshesq
Registered User
Posts: 156
Joined: Fri May 14, 2004 10:32 pm
Location: NYC

tips for phpbb3 modders

Post by asinshesq »

I figured it would be a good idea to start a thread where people can post tips for modders about how to simplify modding in phpbb3. I'll start with the really easy stuff (since that's all I know so far), but if others can add things I envision this could be pretty useful.

1. Spend time reading the coding guidelines here: http://area51.phpbb.com/docs/coding-guidelines.html . Those guidelines not only have a few tips (some of which I'll repeat here) but also tell you the proper code layout (things like the number of spaces per tab, indentation of sql statements, etc.).

2. Spend time reading this wiki entry here: http://olympuswiki.naderman.de/Main_Page . That's pretty undeveloped so far but I suspect over time it will grow.

3. Try to keep the changes you make to core phpbb3 files to a minimum. For example, instead of editing phpbb3's language files, consider adding a new file in language/en/mods that would look like this:

Code: Select all

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

$lang = array_merge($lang, array(
    'SAMPLE_MOD_PHRASE'		=> 'This is a sample of text in a modlanguage file',
    'ANOTHER_SAMPLE'		=> 'Here's another sample of some text in a mod language file',
));
?>
Call that file something like your mod name (e.g. [name_of_mod].php). To use that language file in any core phpbb3 page, simply include this somewhere after the session management line:

Code: Select all

$user->add_lang('mods/[name_of_mod]').
Note that you should not inlcude the '.php' part in the name of the mod when you call it using add_lang. Note also that if you are calling more than one language file, you can use this formulation:

Code: Select all

$user->add_lang(array('mods/mod1', 'mods/mod2', 'mods/mod3'));
4. Similarly, you may want to consider adding a page called includes/functions_[name_of_mod].php for all the functions your mod uses and another page called includes/constants_[name_of_mod].php for all of the new constants your mod uses. Then, whenever you want to use those functions or constants in a core phpbb3 page, you can say at one place (probably the exact same place where you call the mod's langauge file as described above), something like this:

Code: Select all

include($phpbb_root_path . 'includes/functions_[name_of_mod].' . $phpEx);
include($phpbb_root_path . 'includes/constants_[name_of_mod].' . $phpEx);
If you are careful about putting most of the code for your mod in separate functions, you can then keep the changes to the core phpbb3 files to an absolute minimum.

5. Be very careful not to use formulations like if ($variable) if you are not sure that $variable has been set, because phpbb3 will run at a higher error reporting level than phpbb2. Instead, use if (isset($variable)) or if (!empty($variable)).

6. To find out if a variable has been passed in the address or in a hidden field in a template, use this formulation rather than the old $http_post_vars and $http_get_vars formulation:

Code: Select all

$submit = (isset($_POST['submit']) || isset($_GET['submit'])) ? true : false;
(obviously if you know that the variable would only be coming from a hidden field you would only use $_POST and if you know the variable would only coming through the address you would only use $_GET).

7. To actually use a variable (rather than simply finding out if it is passed), use the new function request_var. The function request_var takes three main arguments; the first argument is the name of the variable you are checking for, the second argument gives the default if nothing is there (and also by example tells request_var the 'type' of variable, e.g. integer, string, array of integers, array of strings, etc.) and the third argument specifies whether the input characters are multibyte. Here's how to use request_var in some simple situations (most of which are borrowed from the coding guidelines referred to above):

Code: Select all

// if start is an integer (defaults to 0):
$start = request_var('start', 0);

// if name is a string (defaults to ''):
$name = request_var('name', '', true);

// if mark is an array where the keys are integers (each element defaults to 0)
$mark_array = request_var('mark', array(0));

// if action is an array where the keys are strings (each element defaults to 0)
$action_ary = request_var('action', array('' => 0));
Note that request_var assures that the value of a variable that is supposed to be an integer is forced to be an integer and that the value of a variable that is supposed to be a string is run through htmlspecialchars. But see (9) below for how to deal with injection risks presented by strings.

8. If you use request_var to get text that will be stored in the database, run the result through a function called utf8_normalize_nfc. For example, you will find this in posting.php:

Code: Select all

$message = utf8_normalize_nfc(request_var('message', '', true));
(I can't say that I have taken the time to understand what this does, but I gather it has something to do with making the database entries really utf8 compliant.)

9. Although request_var forces user input that is supposed to be an integer to be an integer and forces any user input that is supposed to be a string to be run through htmlspecialchars(), it does not deal with the injection risk posed by single quotes. To deal with those, any strings inputed from user forms that will be used in any database query should be escaped like this:

Code: Select all

$db->sql_escape(utf8_normalize_nfc(request_var('message', '', true)));
In order to avoid confusion about whether you have or have not cleaned a variable (and in order to avoid escaping the same text twice and ending up with too many backslshes in the final result), it's a good idea to build the escape code into your sql queries rather than cleaning the text as it comes in. Here's an example from the coding guidlines:

Code: Select all

$sql = 'SELECT *
	FROM ' . SOME_TABLE . "
	WHERE username = '" . $db->sql_escape($username) . "'";
Of course, instead of fetching 'username' and storing it in $username before you get to the $sql definition you could have fetched the variable right inside the $sql, like this:

Code: Select all

$sql = 'SELECT *
	FROM ' . SOME_TABLE . "
	WHERE username = '" . $db->sql_escape(utf8_normalize_nfc(request_var('username', '', true))) . "'";
Note that sql_build_array automatically escapes single quotes, so instead of using $db->sql_escape you could use a formulation like this:

Code: Select all

$sql_ary = array(
    'message'    => utf8_normalize_nfc(request_var('message', '', true)),
    'subject'    => utf8_normalize_nfc(request_var('subject', '', true)),
);
$sql = 'INSERT INTO ' . YOUR_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary);
Spend some time with the coding guidelines for more information about differences in the way we should now be interfacting with the database. For example, the coding guidelines describe the use of:
$db->sql_escape():
$db->sql_query_limit():
$db->sql_build_array():
$db->sql_in_set():
$db->sql_build_query():

A word about $db->sql_in_set(): the coding guidelnes say: "The $db->sql_in_set() function should be used for building IN () and NOT IN () constructs" and gives as an example:

Code: Select all

$sql = 'SELECT *
	FROM ' . FORUMS_TABLE . '
	WHERE ' . $db->sql_in_set('forum_id', $forum_ids);
$db->sql_query($sql);
Two thngs to keep in mind:

- Even though mysql actually needs a comma separated list for its IN feature, the second parameter of sql_in_set ($forum_ids in the above example) needs to be an array (the function then changes the array to a comma separated list usable by mysql program). That is actually pretty useful, but if you are converting old code that uses IN you will need to keep in mind that you should now use an array that you feed into that function.

- The way to build NOT IN is to add a third parameter 'true'. If you look at the comments for the function in dbal.php you will see that they have it backwards (the way the function is written you should use 'true' if you want NOT IN but the function comment says the opposite).

10. Instead of message_die, use trigger_error. Here are three examples borrowed from the coding guidlines:

Code: Select all

trigger_error('NO_FORUM');
trigger_error($user->lang['NO_FORUM']);
trigger_error('NO_APPROPRIATE_MODE', E_USER_ERROR);
I haven't tried all of these, but I gather that the first and second one will both check the langauage files and print out the entry for 'NO_FORUM'.

11. The templates now have an .html extension rather than a .tpl extension. They use conditional statements like 'if' (fabulous!). And you no longer need to assign any language file entries to the templates via the relevant php page; instead, if you have an entry like {L_XYZ} in a template, the parser will automatically look at the language files that were called in the relevant php page and if there is an 'XYZ' entry then that entry gets automatically picked up in the template (very cool). You can also include files within templates (which can be other templates or even php files). Take a look at the coding guidelines for more details on these subjects and for other noteworthy things about the new template system.

That's all for now...but please feel free to correct anything I have said that is wrong and to supplement it so that we can start to create a real resource for modding authors.
Last edited by asinshesq on Tue Feb 27, 2007 2:49 pm, edited 6 times in total.
Alan
User avatar
EXreaction
Registered User
Posts: 1555
Joined: Sat Sep 10, 2005 2:15 am

Re: tips for phpbb3 modders

Post by EXreaction »

Very nice.

An easy way to add a permission(gotten from http://olympuswiki.naderman.de/Permissions ):

To add a new permission you must do 3 things. For the example I will use 'u_blog' as the new permission I want to add.

1. Add the field to the database. The query to do that would look like the following for the example(if you use the default table prefix):
INSERT INTO phpbb_acl_options (auth_option, is_global, is_local, founder_only) VALUES ('u_blog', 1, 0, 0);

2. Add the field to the acp permissions language file or make your own permissions file. Since I don't recommend modifying the core code I will just tell you how to make a new permissions file.


Create a file named permissions_(anything you want here).php in language/xx/acp/

The file would look something like this to add the u_blog permission with the language attribute as 'Can Blog' under a new category named new with the lang attribute for that being 'New Permissions Section'.

Code: Select all

<?php 

/**
* 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

// Adding new category
$lang['permission_cat']['new'] = 'New Permissions Section';

// Adding the permissions
$lang = array_merge($lang, array(
    'acl_u_blog'    => array('lang' => 'Can Blog', 'cat' => 'new'),
));

?>
In the array merge you can not have anything other than 2 underscores( _ ). At least that I know of. So if you want something like u_blog_edit you can't, it must be u_blogedit because(as far as I have tested) acl_ must be in front of it.

3. Clear the board's cache.

Then you can use the following code to check if the user can blog(in this instance we just have it trigger the not authorized error, but you can have it do whatever you want.):

Code: Select all

if (!$auth->acl_get('u_blog'))
{
     trigger_error('NOT_AUTHORIZED');
}
You also may want to add in code to have it bring you to the login page if the user is logged out...
Above the trigger_error you would put:

Code: Select all

    if (!$user->data['is_registered'])
    {
        login_box('', $user->lang['LOGIN_EXPLAIN_EDIT_BLOG']);
    }
Of course you would need to add LOGIN_EXPLAIN_EDIT_BLOG to the language file you are using to output the page as well.
User avatar
poyntesm
Registered User
Posts: 176
Joined: Fri May 13, 2005 4:08 pm
Location: Dublin, Ireland
Contact:

Re: tips for phpbb3 modders

Post by poyntesm »

Adding directly permissions to the DB is not a good idea. You have cache to worry about.

There is a function to do it for you and handle the cache...

Code: Select all

<?php 

    //Setup $auth_admin class so we can add permission options
      include($phpbb_root_path . '/includes/acp/auth.' . $phpEx);
      $auth_admin = new auth_admin();

      //Lets Add The Required New Permissions
      $blog_permissions = array(
         'local'      => array(),
         'global'   => array(
            'u_blog_browse',
            'u_blog_search',
            'u_blog_add',
            'u_blog_delete',
            'u_blog_edit',
            'm_blog',
            'a_blog')
      );
      $auth_admin->acl_add_option($blog_permissions);
IMO the wiki is the place to add stuff to rather than a post here...I must try post there myself soon..
asinshesq
Registered User
Posts: 156
Joined: Fri May 14, 2004 10:32 pm
Location: NYC

Re: tips for phpbb3 modders

Post by asinshesq »

poyntesm wrote: IMO the wiki is the place to add stuff to rather than a post here...I must try post there myself soon..

That would be great, but I am skeptical that people will contribute to that as readily as they may contribute tidbits to this thread. Hopefully people will contirubte to both.
poyntesm wrote: Adding directly permissions to the DB is not a good idea. You have cache to worry about. There is a function to do it for you and handle the cache...
By the way, the wiki seems to say the same thing that EXreaction says about adding permissions: it suggests that you add the fields to the db by hand and then clear the cache separately.

The wiki also talks about permission files in language/en/mods getting included automatically. Does that somehow work for regular language files too?
Alan
User avatar
poyntesm
Registered User
Posts: 176
Joined: Fri May 13, 2005 4:08 pm
Location: Dublin, Ireland
Contact:

Re: tips for phpbb3 modders

Post by poyntesm »

No permissions are the only ones included automatically.

Trust me I had numerous PM's with Acyd over permissions a while ago, use the functions provided...if they update the code your code gets updated automatically. It always makes sense to use a standard function if it does what is needed.

Not saying the other way does not work...but passing an array to a function is IMO much easier + uou can do multiple permissions in one hit very easily.
asinshesq
Registered User
Posts: 156
Joined: Fri May 14, 2004 10:32 pm
Location: NYC

Re: tips for phpbb3 modders

Post by asinshesq »

poyntesm wrote: Trust me I had numerous PM's with Acyd over permissions a while ago, use the functions provided...if they update the code your code gets updated automatically. It always makes sense to use a standard function if it does what is needed. Not saying the other way does not work...but passing an array to a function is IMO much easier + uou can do multiple permissions in one hit very easily.
Then it probably makes sense for you to correct the wiki :mrgreen:
Alan
User avatar
EXreaction
Registered User
Posts: 1555
Joined: Sat Sep 10, 2005 2:15 am

Re: tips for phpbb3 modders

Post by EXreaction »

Nice tip, that would work great in the package. Though it is easier for me on my local server to do them manually than run a script(since I have it all open anyways). :P
LEW21
Registered User
Posts: 128
Joined: Fri Nov 04, 2005 9:27 pm

Re: tips for phpbb3 modders

Post by LEW21 »

OK, article in the wiki corrected. You can see it here.
phpBB3.pl - user-friendly Polish phpBB3 support
asinshesq
Registered User
Posts: 156
Joined: Fri May 14, 2004 10:32 pm
Location: NYC

Re: tips for phpbb3 modders

Post by asinshesq »

LEW21 wrote: OK, article in the wiki corrected. You can see it here.

Thanks!

By the way, was I right in thinking that
utf8_normalize_nfc(request_var('message', '', true));
cleans a string so that it is ready for insertion into the db? Looking at request_var in functions.php I see it uses htmlspecialchars with ent compat which leaves single quotes alone and no corresponding str_replace to replace single quotes with something else...so what deals with that injection risk? Is there something in utf8_normalize_nfc that deals with that?
Alan
User avatar
Handyman
Registered User
Posts: 522
Joined: Thu Feb 03, 2005 5:09 am
Location: Where no man has gone before!
Contact:

Re: tips for phpbb3 modders

Post by Handyman »

asinshesq wrote:
LEW21 wrote: OK, article in the wiki corrected. You can see it here.

Thanks!

By the way, was I right in thinking that
utf8_normalize_nfc(request_var('message', '', true));
cleans a string so that it is ready for insertion into the db? Looking at request_var in functions.php I see it uses htmlspecialchars with ent compat which leaves single quotes alone and no corresponding str_replace to replace single quotes with something else...so what deals with that injection risk? Is there something in utf8_normalize_nfc that deals with that?

as far as I know, that doesn't deal with the injection risks… if you just use that, you would want to use something like

Code: Select all

<?php $db->sql_escape(utf8_normalize_nfc(request_var('message', '', true)));
Though it's better to put it in a sql_build_array insert like this

Code: Select all

<?php 
$sql_ary = array(
    'message'    => utf8_normalize_nfc(request_var('message', '', true)),
);
$sql = 'INSERT INTO ' . YOUR_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary);
$db->sql_query($sql);
using the sql_build_array cleans it up so you don't have to use the sql_escape… and you don't have the unsightly \' stuff going on.
My phpBB3 Mods || My Mod Queue
Search Engine Friendly (SEO) URLs || Profile link on Avatar and/or Username || AJAX Chat
Display Posts Anywhere || CashMod || AJAX Quick Edit || AJAX Quick Reply

Image
Post Reply