User Moderation Sandbox Project for Drupal - Community Management of New Users to Avoid Spammers and Trolls

User Moderation Sandbox Project for Drupal - Community Management of New Users to Avoid Spammers and Trolls

In my 'spare time,' I manage and moderate a fairly large community of people who are traveling (by car, truck, motorhome, or motorcycle) North, Central, and South America. Powered by Drupal, of course, the site works as a wiki in addition to having active forums. We strive to make the website as open as possible. Unfortunately, we were having a hard time finding a good balance between protecting the forums, comments, and wiki-type pages from advertisers and related spammers, and locking down the site so user registration took too long. Eventually we settled on manual approval of new user accounts. As administrators, we could make a highly educated guess about whether or a not a new user's account was legit based on a field we added to the user registration form. The field asked new users to 'Tell us about your trip' - people who wrote 'I'm driving a Toyota Tacoma from Seattle to Argentina' were clearly legit. New users who wrote 'I think it's great' were real people trying to sneak their way in so they could start spamming. The problem is that when I go on vacation or forget to check the website, the new user registrations pile up. Most people have an expectation that after creating a new account on a website they will be able to use that site pretty quickly. We didn't want to lose users because of a delay in their registration. 

Isn't Spam already solved for Drupal? (Not really)

Drupal has an exhaustive list of all of the anti-spam modules that are already available, but none of them looked like they were going to solve our problem. These modules are grouped into several categories:

  • CAPTCHA / Honeypot - Our website already had Mollom working to catch spambots 
  • Content Focused Modules - While maintaining fairly open user registration, we wanted to catch spam users before they started to general spam content
  • User Focused Modules - Email validation and other similar modules were failing because very persistent trolls and spammers would actually spend the time to validate their email addresses and then spam away. Also, IP-based modules weren't the greatest since we are a website that does attract a very global audience. 

It's possible that some combination of these modules would have solved the problem we were having with advertisers and spammers trolling our forums and comments. However, I thought I would use this as a learning process for me to finally get around to developing my own custom Drupal module from scratch. I envisioned a system where trusted users can upvote or downvote new users who haven't yet been approved. If a new user receives enough votes from the community, their status would be moved from blocked to active, and they could proceed with the forums, comments, and wiki-type edits. This would take the pressure of site administrators to go through the list of users awaiting approval. 

desired solution to our spam problem

First I laid out the general scenario and how I wanted to solve this problem:

A. User Registration:

  1. A new user registers for an account and they are set to ‘blocked’.
  2. The system adds the new user to the list of users that have to be approved (what we will refer to as the approval queue).

B. Administrative Tasks:

  1. Administrator can view the approval queue and either upvote or downvote the users. 
  2. Administrator can set the number of votes (X) required to either approve the new user or leave them blocked as spammers. 
  3. Administrator can select the field displayed on the user registration form that they want to display to people when they are voting to help them decide if the user is legit or a spammer (in our case, 'Tell us about your trip). 
  4. Administrator can vote new users up or down.  If the admin votes ‘real person’ the system records 1 up-vote. If the admin votes ‘spammer’, the system records 1 down-vote.
  5. If a new user receives X up-votes, their account is moved from blocked to active. If a user receives X down-votes, the user is removed from the list of users that have to be approved. Either way, after receive X votes the new user is removed from the approval queue. 

C. User Approval:

  1. When a trusted user logs into the system they are displayed information about a new user awaiting approval (displays ‘tell us about yourself’ field for the user awaiting approval).
  2. The logged in user can either indicate that they think the new user awaiting approval is a real person or a spammer. If they say ‘real person’ the system records 1 up-vote. If the trusted user votes ‘spammer’ the system records 1 down-vote.
  3. If a new user receives X up-votes, their account is moved from blocked to active. If a user receives X down-votes, the user is removed from the list of users that have to be approved. Either way, after receive X votes the new user is removed from the approval queue. 

Ways to approach this

There were a bunch of different approaches I considered when thinking through how to achieve this setup. I looked at the Entityqueue module, but it seemed like it would be simple to make a table of users to store the 'approval queue' I imagined, rather than work with the Entity API and EntityQueue contributed module. I also thought I could use some combination or Rules, Actions, and Triggers, but again once I started fooling with these, it seemed like I was going to write a lot of custom code to hook into those. I thought it would be simpler and more lightweight to use Drupal's core hooks. I also considered using the Voting API, but this seemed a little heavy for what I hoped to keep a fairly simple solution. Of course there are a million possible ways to skin this cat, but since I was also looking to use this as a learning experience, I decided to build from the ground up. 

How I built a Community User moderation module

The sandbox user moderation project is available on Drupal.org if you want to try it out yourself, but I'll walk you through the hooks and concepts I used to build my custom user moderation module. 

Step A-1: Set new users to 'blocked' on registration

Easy, handled already through Drupal core User settings (admin/config/people/accounts) and Login Toboggan (admin/config/system/logintoboggan). Visitors can register for accounts but administrator approval is required.

Step A-2: Add new user to approval queue on registration

In the user_moderation.install file, I used hook_schema() to create a simple {user_moderation} table that stores user IDs, upvotes, and downvotes. Using hook_user_insert() the new user is added to the {user_moderation} table when they register for an account. 

Step B-1: Administrator can view the approval queue

Hook_menu() to create the administrative link with a page callback to build a page that has the table listing all users in the approval queue, their user ID, their 'tell me about yourself entry' and how many upvotes and downvotes they have received. Much of this code was inspired by the Examples Tablesort Example module. 

drupal user moderation custom module

Steps B-2 and B-3: Administrator can set number of votes required and field to display to voters

Hook_menu() to create the administrative link with a page callback to drupal_get_form() to build the settings form. I used Drupal's handy variable_get() and variable_set() (and of course variable_del() in the user_moderation.install file) to set the number of votes (the cutoff value) required to either approve a user or remove them from the approval queue (and leave them in the 'blocked' state). I then used field_info_instances() to get an array of custom user registration fields to select from on the administrative screen. A quick validation function just checks to make sure the administrator has entered an integer greater than 0 for the number of votes field. 

drupal custom module configuration page

Step B-4 and B-5: Administrator can vote new users up or down

This may not be totally needed, but I thought for testing purposes it would be easiest to build the voting functionality into the administrator's table. Once I got the voting working, it would then be easy to extend this into a block for trusted users to vote. 

Within the administrative user moderation table, I created links that were 'user-moderation/%/%', where the first argument is the user ID, and the second argument is either 'upvote' or 'downvote'. I then used a hook_menu() for the path 'user-moderation/%/%' and passed those page arguments into page callback that was my custom voting function, user_moderation_vote(). I retrieved the votes variable set by the administrator, and then the logic is as follows:

If the user receives a downvote, add 1 to the downvotes column in the {user_moderation} table, or remove the user from the {user_moderation} table if they've received more than the cutoff number of votes. So first check to see how many votes they have, and if they've hit the cutoff, remove them from the approval queue and you're done. If they haven't hit the cutoff, give them another downvote. The logic works the same for upvotes. If the user receives an upvote, add one to the upvotes column in the {user_moderation} table or remove the user from the approval queue if they've received more than the cutoff number of votes. The only difference here is that after checking to see if the user has hit the cutoff number of upvotes, in addition to removing them from the approval queue, use the user_save() function to move their account from 'blocked to 'active'.

Step C-1: Trusted users are displayed information about blocked users awaiting approval

I created a block using hook_block_info() and hook_block_view() to display the 'tell us about your trip' field (selected in the configuration options for this custom module), as well as links to upvote or downvote the user. These block functions contain a callback that gets the first user in the approval queue, and displays only that user's information. 

drupal user moderation custom module voting block

Some Additional UX Improvements

To make this as unobtrusive as possible, I used AJAX for the voting in the block, and JQuery to hide the block after the user has voted, so they aren't always being asked to vote as they traverse the site. (The Examples module AJAX example was instrumental for this). I also set a cookie when the user votes so the trusted user will only be displayed this block again after they have closed their browser. 

I'm going to try this module out on Drive the Americas this week once I've done some final proofreading - hopefully; this will take pressure off me, as the main site administrator, when it comes to approving new users. We'll see! 

You can download view the sandbox User Moderation project at https://www.drupal.org/sandbox/kristin.brinner/2595185.