Signatures for Forums 5.x-2.3

chx and Morbus reviewed Signatures for Forums 5.x-2.3 which "provides user signatures that will be familiar to users of popular forum software" such as "the administrator can choose the input filter for signatures", conditional signatures that are hidden "if a post is under a particular length", and showing the signature only once per conversation. Liam McDermott, the developer, requested this review.

Standard disclaimers: If you have a suggestion on how to better these reviews, or have eyeballed one of our own mistakes, please let us know by leaving a comment. Our reviews are not necessarily security audits: if we don't mention any problems, it doesn't mean we assert the module is Security Team approved (of which we're both members). Finally, if you'd like us to review your own (contributed) module code, don't hesitate to let Morbus or chx know and we'll add it to our queue.

The Reviewed Files

The Review

  1. There's no README.txt. One would assume that, if a developer is going to duplicate a "popular forum" software's functionality, they should also provide handholding on how to migrate users of that software to Drupal. The lack of a README.txt negatively impacts perceptions: the potential migrator asks "do all Drupal modules lack documentation?" or "am I a second-class citizen because I merely want to do what I've done before?" A module's project page on drupal.org should not be used for this purpose. Create a README.txt in the same text format as core: see bot.module's README.txt for an example.
  2. Is it "Signatures for Forums" (the module's project page; uppercase F) or "Signatures for forums" (inside signature_forum.info; lowercase F) or just "signature" (inside hook_perm())? Our inclination would be "Signatures for Forums", since it's a proper name (as opposed to most other strings used inside a module).
  3. The signature_forum.info uses a package of "User goodies" and this is discouraged: "If your module comes with other modules or is meant to be used exclusively with other modules, enter the name of the package here ... In general, this field should only be used by large multi-module packages, or by modules meant to extend these packages, such as CCK, Views, E-Commerce, Organic Groups ... All other modules should leave this blank."
  4. We'll gladly confess ignorance, but we've not personally used other forums that a) hide a signature if the post's content is small or b) only shows the signature once each thread or conversation. It seems that, for example, phpBB needs a mod to show the signature once per thread; vBulletin also needs a change one way or another. We recommend reconsidering "familiar to users of popular forum software". These two features are mentioned in the module description and also implied by signature_forum.info: "Manages users signatures in the style most forum users will be familiar with." Descriptions and documentation should strive to be free of opinions: we'd revise this to something like "Tweaks signatures in ways inspired by other traditional forum software." It says much the same thing, but doesn't make us feel out of touch. This would also apply to the definition in hook_help().
  5. Coder found no errors. Awesome.
  6. Core uses "Implementation of hook_NAME()." - some definitions are missing the ending period.
  7. One of Drupal Tough Love's primary tenets is "to do what core does" and this applies equally to every aspect of module creation. A large number of Drupal 5 contributions don't support PostgreSQL because their developers "don't have it installed", even though their SQL would run just fine if only there were a proper hook_install(). Drupal 6's SchemaAPI solves this issue (for table definition, at least), but there's still column retardation, where a column representing a user's ID (or any other standardized data) is marked up in a different way than in core. For example, in signature_forum.install, we see:

    <?php
    case 'mysql':
    case
    'mysqli':
    case
    'pgsql':
     
    db_query("CREATE TABLE {users_signature} (
        uid integer,
        signature text default NULL,
        PRIMARY KEY (uid)
        ) /*!40100 DEFAULT CHARACTER SET UTF8 */ "
    );
    ?>

    Not only does this force all three databases into one definition (causing the MySQL UTF8 workaround on the final line to be erroneously applied, but thankfully ignored, by PostgreSQL) but uid integer is (letter-for-letter) a different definition than what core uses. When in doubt, steal liberally from core's own .install files. The above would more correctly be written in Drupal 5 as:

    <?php
    case 'mysql':
    case
    'mysqli':
     
    db_query("CREATE TABLE {users_signature} (
        uid int NOT NULL default '0',
        signature text,
        PRIMARY KEY (uid)
        ) /*!40100 DEFAULT CHARACTER SET UTF8 */ "
    );

      break;

    case
    'pgsql':
     
    db_query("CREATE TABLE {users_signature} (
        uid int NOT NULL default '0',
        signature text,
        PRIMARY KEY (uid)
        )"
    );

      break;
    ?>

    Eagle-eyed viewers will note we left out the default NULL on the definition for signature text. MySQL "BLOB and TEXT columns cannot have DEFAULT values", but it will gladly ignore the attempt. Since there's no plausible default on MySQL, we did not bother providing one for PostgreSQL. Sure, this is more code for little actual gain, but it's also more accurate and standardized. Even the Signature for Forums .install for Drupal 6's Schema API isn't "like core"; compare the definition of a uid column in node.install with this module's 'uid' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0) - there's no description, but there is an unnecessary unsigned.

    Steal liberally from core's definitions and patterns: if you mean the same thing, say the same too.

  8. Drupal core uses constants to give human-readable names to numeric values, and Signature for Forums does the same, even going the extra distance by properly documenting them. Thank you!
  9. This module contains another example of "store our module-specific settings in a single array". We're still not entirely sure where this pattern is coming from (if you know, tell us!). There seems little apparent rationale besides "I'm too lazy to variable_del() each of them in my hook_uninstall().", "I'm gonna forget to update it when I add a new setting.", or the unarguable "I like it!" By forcing your module's settings into an array, you waste a line loading all your settings to check just one value, and also require an extra function to build the defaults (seen also in Printer-Friendly Pages #6, only in slightly different form). We're not seeing how this is such an advantage that inventing something "not like core" is warranted. A healthy amount of code is reinvented in signature_forum_admin_settings_submit() too.

    Take a look at, pseudo-roughly, what Signatures for Forums currently does for its configuration:

    <?php
    function signature_forum_defaults() {
      return array(
        ...
       
    'signature_forum_template' => " [default value] ",
      );
    }

    function
    signature_forum_form($default_values) {
     
    $form['template'] = array(
        ...
       
    '#default_value' => $default_values['signature_forum_template'],
      );

      return
    $form;
    }

    function
    signature_forum_admin_settings_submit($form_id, $form_values) {
      if (
    $form_values['op'] == 'Reset to defaults') {
       
    variable_set('signature_forum_settings', signature_forum_defaults());
        return;
      }

      ...
     
    $settings['signature_forum_template'] = $form_values['template'];
     
    variable_set('signature_forum_settings', $settings);
    }
    ?>

    And, compare it to proper approach for use with system_settings_form():

    <?php
    function signature_forum_form() {
     
    $form['signature_forum_template'] = array(
        ...
       
    '#default_value' => variable_get('signature_forum_template', ' [default value] ');
      );

      return
    system_settings_form($form);
    }
    ?>

    The second code sample is literally all you need when using system_settings_form() and module configuration variables properly. One doesn't have to worry about resetting the default values, creating an extra function to hold them all, or renaming them from the Form API definitions to what they actually get stored as. (For Signatures for Forums, an extra #submit function would also be required to handle the deletion of hardcoded user signatures in existing posts.)

    Inventing new approaches to established Drupal patterns merely walls your module into its own little garden, requiring even more special knowledge to enter. Drupal already requires a lot to learning - help folks out by attempting to keep the established patterns prevalent in your modules. One may argue, and many have, that the core approach needlessly replicates the duplicate value across every use of that variable. And, they'd be right, but the proper way to enact change is to get a patch into core so that everyone benefits, such as the proposed hook_variables() to control and define default values.

  10. Use elseif not else if for your conditionals.
  11. The $section argument of hook_help() doesn't need a default value - one is always passed through by core. Also, the documentation within needs to be rewritten: besides a missing grammatical semi-colon, it suggests that users can use BBCode in their signatures, but doesn't provide any help on how.
  12. user_access() properly restricts administrative configuration in your menu definitions, but the same permission is erroneously checked again within signature_forum_admin_settings(). The approach to that function is slightly odd too: we could see the rationale if signature_forum_form() (the actual form definition) was called multiple times throughout the code, but it isn't, instead serving as just another layer of abstraction. We'd rewrite signature_forum_admin_settings() to something like the below, which removes signature_forum_form() entirely. Combining this with #9 (above) will push your module configuration firmly into the established patterns inherent in core.

    <?php
    function signature_forum_admin_settings() {
     
    $settings = variable_get('signature_forum_settings', signature_forum_defaults());

     
    $form['signature'] = array( ... );

     
    // include the rest of signature_forum_form() here.

     
    return system_settings_form($form);
    }
    ?>

  13. Generally speaking, all form elements should have a #title. If the #title isn't descriptive enough, a #description can be used to further explain what's going on. Fieldsets are used to collect similar fields together (literally, a fieldset is a "set of fields"). signature_forum_form(), however, has a fieldset with no #title and only "one" item (checkboxes for a site's node types). While this approach is OK here because it models the "Display post information on" fieldset in Drupal's theme configuration (admin/build/themes/settings), your current #description should move to the #title.
  14. The module's configuration page has a fieldset entitled "Show signatures once per conversation" - this is action text best reserved for an interactive form element, such as a checkbox or radio button. Rename the fieldset to something more descriptive of the fields within, such as "Per-conversation signatures".
  15. t('<strong>%s</strong> will be replaced with user\'s signature.'). Once upon a time, PHP was a bit slower when using double quotes for its strings, so people tried to avoid it all costs. This eventually became "scripture", disregarding the fact that noone even remembers which PHP version cured the lag; we can't bother to look up whether it was PHP 4.0 or PHP 4.1. Use double quotes: they are not your enemy! Needlessly escaped apostrophes, on the other hand, make code hard to read. signature_forum_help(), signature_forum_menu(), signature_forum_form(), and a few others, all contain escaped apostrophes. Lastly, in this particular example, you need "the user's signature", not just "user's signature". UPDATE: You should also remove the <strong> - not only is the use of HTML in t() questionable, but core doesn't markup placeholders in this way.
  16. Signature for Forums offers a signature template that the admin can customize, placing a %s where they want a user's signature to appear by default. To interpolate the user's signature into this template, the code uses sprintf() in signature_forum_get_signature():

    <?php
    $signature
    = sprintf($settings['signature_forum_template'], trim($signature));
    ?>

    The Drupal way to do this, exemplified in situations like mails sent out to users (admin/user/settings), is with strtr(), which also allows the use of named placeholders (i.e., !signature). Consider replacing your existing %s with !signature and the sprintf() with:

    <?php
    $signature
    = strtr($settings['signature_forum_template'], array('!signature' => trim($signature));
    ?>

  17. We use FALSE and TRUE, not the lowercase variants.
  18. A critical bug exists with the use of check_markup() in signature_forum_get_signature(), which is rigged to err on the safe side by defaulting its $check attribute to TRUE. This means that, for every single visitor, they're being checked for whether they have access to the specified format. For example, if the submitter has access to, say, the <img> tag but the visitor does not, then the signature will be filtered into an empty string. You need to add a third parameter, FALSE, to your check_markup(). filter_form() takes care to show only the filter formats the submitter has access to, so that's not a problem.
  19. Replace the longer code in signature_forum_nodeapi() with $node->content['body']['#value'] .= signature_forum_get_signature($node);. The matching logic in signature_forum_comment() is correct.
  20. Some of your SQL uses column=%d. The proper style is a space on both sides of the equal sign.

Comments

Another excellent review

Another excellent review guys! Can't wait for the next one :)

I am wondering, whouldnt

I am wondering, whouldnt this:
t('<strong>%s</strong> will be replaced with user\'s signature.') be better as something like this:

t('%thing will be replaced with user\'s signature.', $value)?

Personally i find the idea of using any html tag in t() functions inherently wrong, since it assumes html output, which should be a task of theming (this is why its really cool to have theme_placeholder).

I assume from point 16 this approach of embedding the %s is because of getting the settings is just returning one value, but this is one more argument why its not an elegant solution.

I agree. I'll update the post

I agree. I'll update the post with this suggestion.

Morbus, chx, I can't begin to

Morbus, chx,

I can't begin to tell how valuable this is. It provides a very technical view on how things should be done, and the benefit is huge. You give quite a lot of pointers into how to do things properly, the "Drupal way", and I find this very educating. I have little value in having ending period in comments, or in PHP semantics, but I understand why these are important. May I suggest to categorize the points into few categories, to ease on readability? Sections may be something like "Drupal way", "Coding standards", "Generic PHP", "Other"...

Thanks again for an excellent review..

For item 9 - there's at least

For item 9 - there's at least one person going around requesting people to put all system variables into a single tree.

Thanks for the review, gents.

It was merely an opinion, I

It was merely an opinion, I was not "going around requesting people to...", simply an opinion based on the large number of entries created by pathauto. The points covered in item 9 are certainly valid and I would to agree with everything stated.

I would also like you to keep

I would also like you to keep in mind that I am only 20 years old, learning, and doing my best to help the community, just like anyone else so I would appreciate it if you could embrace that fact and provide me constructive feedback rather than going around accusing me of certain things, or dissing me like you have in the past.

Unsigned UID? Mostly:

Unsigned UID?

Mostly: Wonderfully thorough! I aspire to have a module merely worthy of review.

One question on number 7, though:

Shouldn't a User ID (uid) field be defined as unsigned?

And a follow-up question. For PostgreSQL, is the "most core" way of doing this to borrow from system.module and define an SQL domain like this?

<?php
     
/* create unsigned types, modeled on system.install */
     
db_query("CREATE DOMAIN int_unsigned integer CHECK (VALUE >= 0)");

    
/* example of core use from system.install: */
     
db_query("CREATE TABLE {users_roles} (
        uid int_unsigned NOT NULL default '0',
        rid int_unsigned NOT NULL default '0',
        PRIMARY KEY (uid, rid)
      )"
);
?>

I've never understood why

I've never understood why it's the "Drupal Way" to capitalize some SQL reserved words, but not others.

Shouldn't this:

<?php
  db_query
("CREATE TABLE {users_signature} (
    uid int NOT NULL default '0',
    signature text,
    PRIMARY KEY (uid)
    )"
);
?>

Be written like:

<?php
  db_query
("CREATE TABLE {users_signature} (
    uid INT NOT NULL DEFAULT '0',
    signature TEXT,
    PRIMARY KEY (uid)
    )"
);
?>

mysqldump doesn't capitalize

mysqldump doesn't capitalize them either, for what it's worth:

<?php
CREATE TABLE
`access` (
  `
aid` int(11) NOT NULL auto_increment,
  `
mask` varchar(255) NOT NULL default '',
  `
type` varchar(255) NOT NULL default '',
...
?>

Anyways, is roughly irrelevant with Drupal 6's Schema API.

DTL: centuries of Drupal

DTL: centuries of Drupal knowledge, condensed into yummy bite sized chunks.
Another gem, guys. Many thanks.

I agree 100% that backquoting

I agree 100% that backquoting single and/or double quotes in order to be able to use one or the other is undesirable. However, it is false to say that there is no speed penalty in using double-quotes, and that the difference was fixed long ago.

It was never a bug. Single quotes do not do substitutions for things such as newline (\n). They have less work to do than double quotes. This is easily demonstrated with a benchmark. I just ran such a benchmark using PHP 5.2.5 and single quotes were approximately twice as fast.

I do not argue that such a speed difference makes using backquoting acceptable, however. Both are fast enough to use, unless one is talking about 10,000 iterations or something. ;-)

I read the post you indicated

I read the post you indicated and I think that it is flawed in 2 counts:
1. The time includes the parsing of the PHP file.. Fine on one side, but if you're trying to see how much an ant weights, you don't normally add an elefant to the scale to check the weight of the elefant+1 ant, or the elefant alone and come up with difference in measurements as the ant's weight.
2. It only executed the echo 1 time. The original benchmark (and I believe Chris Johnson's) tried to magnify the difference and to come up with a median measurement by repeating the use of the string a large number of times. If single quotes are twice as fast, I would think that their use should be recommended. Especially if one thinks that in a normal Drupal page, the parsing and execution of all loaded modules may require the parsing of double-quoted strings 1000+ times.

Joao Ventura

PS: Regarding #9 and the print module's #6, I am planning to stop doing it in the print module for one simple reason: I now want to group stuff in a collapsible fieldset and I don't want to have that fieldset be the same as the variables I am using. Solution: do what you recommended there and just use a different var for each item, instead of an array.

@morbus: I say the rebuttal

@morbus: I say the rebuttal you cite is garbage.

The specific topic is the evaluation of literal strings (single quoted) + concatenation and variable strings (double quoted) with inline variables. The example you site introduces php compile time which can be offset with an opcode cache. The comments regarding flex are somewhat true, but variable strings stored in the lexer have to be evaluated at run time while literal strings do not to determine their actual value. We are not talking about benchmarking the php lexer and compiler's performance but the running php code... ;)

However, according to this

However, according to this (http://www.phpbench.com/) there's basically no difference and their tests make the call 1000 times. Remember to refresh the page a few times as explained in the header of the page.

So, you're right.. The source you indicated may have presented a faulty analysis, but the end conclusion that the use of single or double quotes is equivalent is correct.

João

It was never a bug. But as

It was never a bug. But as far as I can remember -- this was very long ago -- once upon a time, the difference was significant enough that people cared. While single quotes are still faster than double quote -- yes. Matters -- not at all. Add an index or cache the results of a query to make some change that is visible because this won't be.

What about this? t('%thing

What about this?
t('%thing will be replaced with the user’s signature.', $value)

Great reviews btw guys, I

Great reviews btw guys, I know some people have bemoaned the lack of quality in modules, but DTL not only improves modules, it improves developers as well.

Just stumbled upon this site,

Just stumbled upon this site, and it seems like an excellent resource for current and aspiring module developers. Thanks for putting it together!

I have a question related to the store our module-specific settings in a single array issue (#9). I am currently working on a module for which I'd like to follow your recommendation and use the standard drupal method for constructing a module settings form. This would result (I think) in a separate entry in the variables table for each of my form fields.

Separate variables would be fine with me, except I'm running into the 48 character limit imposed on the variable name field (I'm working with 5.x). The need for a longer variable name is a longer ;-) story... Storing a small number of my settings form fields as an array seems to be a reasonable workaround in my particular situation.

Is there a way to use Drupal's automatic handling of admin setting form submissions, and get a few of the form settings stored together in an array? Or does the need for storing an array in the variables table mean I have to write my own submit function?

Thanks again!

I would tend to suggest,

I would tend to suggest, instead, that you shorten your variable names ;). Note, also, in Drupal 6, the variable name field was increased to 128 characters. If you absolutely must do so (but I don't find your argument compelling), you can use #tree. We argue against doing such a thing, however, in the first Drupal Tough Love review and, after some time, the original developer agrees.

Thank you _very_ much for

Thank you _very_ much for your reply! That little tidbit of information about using #tree was tremendously helpful, though I take your point that in general it is better practice to create separate variables (and shorten variable names if necessary).

I didn't want to get the discussion too far off topic and describe my need for accommodating long variable names in great detail. It's quite likely that a more experienced module developer could find a more elegant solution than the #tree workaround. But in this case, storing just a few of the settings (not all of them!) in an array did the trick.

In the unlikely event my underlying issue is of any interest, here is a brief description: I need to store a setting for every available nodereference field instance. It seemed that concatenating the field name and content type name was the most straightforward way to ensure unique variable names that could be easily referenced/identified later. The combined length of those strings can easily exceed the 48 character limit imposed by Drupal 5.x I wouldn't be at all surprised if I'm overlooking a simple solution ;-)

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.
  • You may post code using <code>...</code> (generic) or <?php ... ?> (highlighted PHP) tags.

More information about formatting options