Monthly Archives: March 2006

Silly bug in Visual Studio 2005 editor

If you try to compile the code below you will see that the i in the second loop is not defined in my main function. Position your mouse over the i, click right and choose “Go To Definition” in the context menu. Why does the cursor move to the i in the struct? Btw, if you remove the first for loop this doesn’t happen.

#include "stdafx.h"
struct {
  int i;
} BLAH;

int _tmain(int argc, _TCHAR* argv[]) {
  for (int i = 0; i < 10; ++i) { ; }
  for (i = 0; i < 10; ++i) { ; }
  return 0;
}

More about marshalling

Last month i’ve started programming with the .NET Framework using Visual Basic, C++.NET and C# on a daily basis. The first thing i noticed is that some useful functions that were available in kernel32.dll, user32.dll, etc. have been removed from the API. At pinvoke.net you find a summary of the functions in these DLLs and the PInvoke signatures.The most common approach is to build classes for the DLLs as following:

namespace InterOp {
  public class User32 {
    [DllImport("user32.dll")]
    public static extern IntPtr GetDesktopWindow();
    [DllImport("user32.dll")]
    public static extern IntPtr GetWindowDC(IntPtr hWnd);
    [DllImport("user32.dll")]
    public static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDC);
    [DllImport("user32.dll")]
    public static extern IntPtr GetWindowRect(IntPtr hWnd, RECT rect);
  }

  public  class GDI32 {
    [DllImport("gdi32.dll")]
    public static extern bool DeleteDC(IntPtr hDC);
    [DllImport("gdi32.dll")]
    public static extern bool DeleteObject(IntPtr hObject);
    [DllImport("gdi32.dll")]
    public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);
  }
}

Internationalizing strings with variables

Yesterday i wrote that you can use __($string, $domain) and _e($string, $domain) to internationalize a string with WordPress. I forgot to mention that if you use sprintf you can handle strings with variables too. An example:

echo sprintf(__('There are %d monkeys in the %s'), $domain), $number, $location);

Adding support for localization to a WordPress plugin

WordPress uses GNU gettext, as explained in Translating WordPress and Writing a Plugin, for localization. I decided that my plugins should support l10n too. Here is how i realised it:

I started with defining the WPLANG constant in my wp-config.php:

define ('WPLANG', 'en_EN');

Then i changed the beginning of my localized plugin as following:

<?php
/*
Plugin Name: WP-SpamFilter
Version: 0.4
Plugin URI: http://timvw.madoka.be/?p=533
Description: Mark the new comment as spam if the sender is in the spammers list.
Author: Tim Van Wassenhove <timvw@users.sourceforge.net>
Author URI: http://timvw.be
*/

// If this function does not exist it means that the file is accessed directly.
// Accessing this file directly is not allowed.
if (!function_exists('load_plugin_textdomain')) {
  exit;
}

load_plugin_textdomain('wp_spamfilter', 'wp-content/plugins/wp-spamfilter');

In wp-includes/wp-l10n.php you see that the result of this call is that the locale and the path are used to determine the mo-file to be loaded. Because this function uses ABSPATH it’s impossible to use dirname(__FILE__) as path parameter :(

The _e($string, $domain) function echos a localized string and the __($string, $domain) function returns a localized string that you can use in function calls etc… Below you can see an example of the original version and the updated version with localization support:

<h2>some string</h2>
<?php echo('some other string'); ?>
<h2><?php _e('some string', 'wp_spamfilter'); ?></h2>
<?php echo(__('some other string', 'wp_spamfilter')); ?>

Then i used xgettext to extract all the strings that should be localized into wp_spamfilter-en_EN.po:

xgettext –keyword=__ –keyword=_e –default-domain=wordpress –language=php *.php –output=wp_spamfilter-en_EN.po

After that i editted the po file and i compiled a mo file with it using msgfmt:

msgfmt wp_spamfilter-en_EN.po -o wp_spamfilter-en_EN.mo

Once i had made sure that wp_spamfilter-en_EN.mo was readable by my webserver i was ready.

Plugin template for WordPress

Today i’ve been cleaning up my code. All my plugins live in a directory %plugin_name% under the wp-content/plugins directory. Here is the code for %plugin_name%/%plugin_name%.php:

<?php
/*
Plugin Name: %plugin_name%
Version: %plugin_version%
Plugin URI: %plugin_uri%
Description: %plugin_description%
Author: %plugin_author_name% <%plugin_author_mail%>
Author URI: %plugin_author_uri%
*/

// Direct access is not allowed.
if (!function_exists('load_plugin_textdomain')) {
  exit;
} else {
  require('%plugin_name%.php');
  $plugin_name = substr(basename(__FILE__), 0, -4);

  // You can add optional parameters to the constructor, eg: the wpdb instance.
  $%plugin_class% = new %plugin_class%($plugin_name);
}
?>

Now it’s time to implement the %plugin_class%. Here is the template for %plugin_name%/%plugin_class%.php:

<?php

class %plugin_class% {
  var $_plugin_name;

  var $_localization_domain;
  var $_localization_path;

  function %plugin_class%($plugin_name = '%plugin_name%') {
    $this->_plugin_name = $plugin_name;

    $this->_localization_domain = $plugin_name;
    $this->_localization_path = 'wp-content/plugins/' . $plugin_name;

    // You can add additional hooks and filters here.
    add_action('activate_' . $plugin_name . '/' . $plugin_name . '.php', array(&$this, 'OnActivation'));
    add_action('admin_menu', array(&$this, 'OnAdminMenu'));
}

  function OnAdminMenu() {
    load_plugin_textdomain($this->_localization_domain, $this->_localization_path);
    add_options_page(__('%plugin_name% Options', $this->_localization_domain), __('%plugin_name%', $this->_localization_domain), 'manage_options',$this->_plugin_name . '/' . $this->_plugin_name . '-options.php', array(&$this, 'OnDisplayOptions'));
  }

  function OnDisplayOptions() {
    $%plugin_class% = "";
    require( dirname(__FILE__) . '/' . $this->_plugin_name . '-options.php');
  }

  function OnActivation() {
    // This code is executed when the plugin is activated.
    // I prepend all my option names with $this->_plugin_name.
    add_option($this->_plugin_name . '_somevar', 'foo');
  }
}
?>

The code above requires that you create a %plugin_name%/%plugin_name%-options.php file to administrate the options. Here is the template for that file:

<?php
// Direct access is not allowed.
if (!isset($%plugin_class%)) {
  exit;
}

load_plugin_textdomain($%plugin_class%->_localization_domain, $%plugin_class%->_localization_path);

// Handle post action.
if ($_POST['stage'] == 'process') {
  // All the names of form variables start with %plugin_class%_plugin_name.
  if (isset($_POST[$%plugin_class%->_plugin_name . '_somevar'])) {
    // Do processing here.
  }
}

?>
<form method="post" action="">
  <input type="hidden" name="stage" value="process"/>
  <input type="text" name="<?php echo $%plugin_class%->_plugin_name . '_somevar'; ?>"/>
  <input type="submit" value="<?php _e('Submit', $%plugin_class%->_localization_domain); ?>"/>
</form>

The only thing that we still have to do is generate a po file with xgettext, translate it and compile a %plugin_name%/%plugin_name%-en_EN.mo file.

Adding an action when your plugin is activated

The WordPress documentation says that you have to call add_action(‘activate_pluginurl’, ‘somefunction’) to trigger somefunction when your plugin is activated. Unfortunately i couldn’t find with what pluginurl should be replaced. After a bit of experimenting i’ve found that in wp-admin/plugins.php the following is called when a plugin is activated:

do_action('activate_' . trim( $_GET['plugin'] ));

So it appears that you simply have to use the path of your plugin relative to /wp-content/plugins. Eg: you have a plugin in /wp-content/plugins/wp-spamfilter/wp-spamfilter.php then you have to call add_action as following:

add_action('activate_wp-spamfilter/wp-spamfilter.php', 'somefunction');

More marshalling…

This snippet uses GetPrivateProfileString that is available in kernel32.dll. Apparently microsoft has decided to remove this useful function from the dotnet api.

[DllImport("kernel32", SetLastError=true)]
extern int GetPrivateProfileString(
  String ^pSection,
  String ^pKey,
  String ^pDefault,
  StringBuilder ^pValue,
  int pBufferLen,
  String ^pFile
);

StringBuilder ^buf = gcnew StringBuilder(256);
GetPrivateProfileString(
  "logsection",
  "file",
  "default",
  buf,
  buf->Capacity,
  "example.ini"
);

std::string _log_file = new string(
  (char*) Marshal::StringToHGlobalAnsi(logf).ToPointer()
);