Introduction
I'm going to document my step-by-step process to convert a custom EQdkp plugin from a 1.2.0 foundation into a working version for 1.4.0
This example makes use of a plugin called Wishlist, which allows members to enter items they want for their character and then allows an administrator to view all members wanting a particular item, and who might win it based on current point standings, in order to better build the night's raid.
It consists of the following files:
- wishlist_plugin_class.php - The core class file, common to every plugin.
- settings.php - Accessed by the user via the Settings menu and used to input items
- viewmember.inc.php - An include file called by an EQdkp hook that prints the member's wishlist on their viewmember.php page
- admin/index.php - The administration page for viewing all wishlists
- language/english/lang_main.php - Language key definitions for English
Process
1. Remove Table Constants
EQdkp no longer defines constants for SQL table names. Instead, any name prefixed with two underscores will automatically gain the installation's EQdkp prefix.
Delete blocks like this from your <plugin>_plugin_class.php:
global $table_prefix;
if (!defined('WISHLIST_TABLE')) { define('WISHLIST_TABLE', $table_prefix . 'wishlist'); }
if (!defined('WISHLIST_DATA_TABLE')) { define('WISHLIST_DATA_TABLE', $table_prefix . 'wishlist_data'); }Use the magic string instead of the constants:
// Define installation
// -----------------------------------------------------
$sql = "CREATE TABLE IF NOT EXISTS " . WISHLIST_TABLE . " (
becomes
// Define installation
// -----------------------------------------------------
$sql = "CREATE TABLE IF NOT EXISTS __wishlist (
Same thing here:
// Define uninstallation
// -----------------------------------------------------
$this->add_sql(SQL_UNINSTALL, "DROP TABLE IF EXISTS __wishlist");
$this->add_sql(SQL_UNINSTALL, "DROP TABLE IF EXISTS __wishlist_data");
2. Smarter SQL Parameters
This is completely optional but may clean up your code a bit.
You may have seen some code like this in EQdkp 1.3.0:
$insert = array(
'wl_id' => null,
'wl_member' => ucfirst($user->data['username']),
'wl_item' => $_POST['wishlist_item'],
'wl_type' => $_POST['wishlist_type'],
);
$sql = INSERT INTO ' . WISHLIST_TABLE . $db->build_query('INSERT', $insert);
$db->query($sql);The query method now takes an optional second parameter which, if an array, will be passed to build_query() and the results will replace a variable in the query called :params
For example, the above lines could be re-written as one statement:
$db->query("INSERT INTO __wishlist :params", array(
'wl_id' => null,
'wl_member' => ucfirst($user->data['username']),
'wl_item' => $_POST['wishlist_item'],
'wl_type' => $_POST['wishlist_type'],
));The syntax is very similar for UPDATE queries, and the appropriate value is automatically detected and passed to build_query()
3. SQL Injection and Input Filtering
The major focus of EQdkp 1.4.0 was increased security, and that focus should follow through to plugins as well. In the above example, two variables are fed directly into the SQL query. In this case the risk is minor because build_query() automatically makes all variables SQL-safe, but we should input filter them as well for extra measure:
$db->query("INSERT INTO __wishlist :params", array(
'wl_id' => null,
'wl_member' => ucfirst($user->data['username']),
'wl_item' => $in->get('wishlist_item'),
'wl_type' => $in->get('wishlist_type'),
));The $in variable references an instance of the Input class.
Here, settings.php forces the GET variable 'id' to be an integer:
$sql = "DELETE FROM " . WISHLIST_TABLE . "
WHERE ( wl_id = " . intval($_GET['id']) . " )
AND ( wl_member = '" . ucfirst($user->data['username']) . "' )
LIMIT 1";
But we can have Input do this for us by passing it an integer as the second parameter, its default value if 'id' doesn't exist.
$sql = "DELETE FROM __wishlist
WHERE ( wl_id = " . $in->get('id', 0) . " )
AND ( wl_member = '" . ucfirst($user->data['username']) . "' )
LIMIT 1";See the documentation for Input in /includes/input.php for more details on what get() can do.
4. Cross-Site Scripting (XSS) Protection
Never trust what's being displayed to the user. Always sanitize() any variables being printed.
$tpl->assign_block_vars('wishlist_row', array(
'ROW_CLASS' => $eqdkp->switch_row_class(),
'ITEM' => sanitize($row['wl_item']),
'ZONE' => sanitize($row['wl_zone']),
'BOSS' => sanitize($row['wl_boss']),
));5. plugin_path()
To clean up menu generation functions, a plugin_path() function was added.
// Before:
return array($user->lang['wishlist'] => array(
0 => '<a href="plugins/' . $this->get_data('path') . '/settings.php">' . $user->lang['wishlist'] . '</a>'
));
return array(
'wishlist' => array(
0 => 'Wishlist',
1 => array(
'link' => '../plugins/' . $this->get_data('path') . '/admin/index.php',
'text' => $user->lang['wishlist_view'],
'check' => 'u_wishlist_view',
)
)
);
// After:
return array($user->lang['wishlist'] => array(
0 => '<a href="' . plugin_path('wishlist', 'settings.php') .'">' . $user->lang['wishlist'] . '</a>'
));
return array(
'wishlist' => array(
0 => 'Wishlist',
1 => array(
'link' => plugin_path('wishlist', 'admin/index.php'),
'text' => $user->lang['wishlist_view'],
'check' => 'u_wishlist_view',
)
)
);The first argument is your plugin's folder name, and the second argument is the path you'd like to go to relative to that folder.
6. Quirks
In 1.4.0, the username variable was renamed to user_name:
"WHERE ( wl_member = '" . ucfirst($user->data['user_name']) . "' )"
Due to a weird caching quirk with the (horribly aging) template engine, I had to add the following line to my settings.php file to get it to render the plugin's template file called settings.html instead of the EQdkp's default settings.html file:
define('PLUGIN', 'wishlist');