FreeCap CAPTCHA Integration


See also:
Ticket #347

URCaptchaModule: A modular CAPTCHA plugin that requires no modifications to Wikka core code. Based upon YodaHome's implementation and the UR framework available since 1.1.6.4.


I've recently had several attacks with bots registering dozens of users and then vandalizing my wiki where possible. While I could have just hidden the Registration I thought the obvious answer to this would be to add a CAPTCHA to the registration process. This is what I came up with.

Actually I looked for nice CAPTCHA scripts and found FreeCap which seemed quite strong to me and had some nice features. It is also released under the GPL. So I integrated it with my Wikka.

Prerequisites / Installation


First you need to download the FreeCap package from the website. (Note that this link may not lead to the latest package). Then all the files went into a "freecap" folder below the "actions" tree. Of course you might put it somewhere else but be sure that it can be properly accessed and you changed the paths correctly where necessary (see note at the bottom).

I only changed the usersettings.php (this is a modified version from 1.1.6.3), look for "added for FreeCap" in the comments. For convenience this is the whole file. (Scroll down for 1.1.7-compatible version.)

actions/usersettings.php (version 1.1.6.3 only)
<?php
/**
 * Display a form to register, login and change user settings.
 *
 * @package     Actions
 * @version     $Id$
 * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
 * @filesource
 *
 * @author      {@link http://wikkawiki.org/MinusF MinusF} (code cleanup and validation)
 * @author      {@link http://wikkawiki.org/DarTar Dario Taraborelli} (further cleanup, i18n, replaced JS dialogs with server-generated messages)
 *
 * @uses        Wakka::htmlspecialchars_ent()
 *
 * @todo        use different actions for registration / login / user settings;
 * @todo        add documentation links or short explanations for each option;
 * @todo        use error handler for displaying messages and highlighting
 *              invalid input fields;
 * @todo        remove useless redirections;
 * @todo        [accessibility] make logout independent of JavaScript
 */


// defaults
if (!defined('PASSWORD_MIN_LENGTH')) define('PASSWORD_MIN_LENGTH', "5");
if (!defined('VALID_EMAIL_PATTERN')) define('VALID_EMAIL_PATTERN', "/^.+?\@.+?\..+$/"); //TODO: Use central regex library
if (!defined('REVISION_DISPLAY_LIMIT_MIN')) define('REVISION_DISPLAY_LIMIT_MIN', "0"); // 0 means no limit, 1 is the minimum number of revisions
if (!defined('REVISION_DISPLAY_LIMIT_MAX')) define('REVISION_DISPLAY_LIMIT_MAX', "20"); // keep this value within a reasonable limit to avoid an unnecessary long lists
if (!defined('RECENTCHANGES_DISPLAY_LIMIT_MIN')) define('RECENTCHANGES_DISPLAY_LIMIT_MIN', "0"); // 0 means no limit, 1 is the minimum number of changes
if (!defined('RECENTCHANGES_DISPLAY_LIMIT_MAX')) define('RECENTCHANGES_DISPLAY_LIMIT_MAX', "50"); // keep this value within a reasonable limit to avoid an unnecessary long list
if (!defined('INPUT_ERROR_STYLE')) define('INPUT_ERROR_STYLE', 'class="highlight"');

// i18n strings
if (!defined('USER_SETTINGS_HEADING')) define('USER_SETTINGS_HEADING', "User settings");
if (!defined('USER_LOGGED_OUT')) define('USER_LOGGED_OUT', "You have successfully logged out.");
if (!defined('USER_SETTINGS_STORED')) define('USER_SETTINGS_STORED', "User settings stored!");
if (!defined('ERROR_NO_BLANK')) define('ERROR_NO_BLANK', "Sorry, blanks are not permitted in the password.");
if (!defined('ERROR_PASSWORD_TOO_SHORT')) define('ERROR_PASSWORD_TOO_SHORT', "Sorry, the password must contain at least %s characters.");
if (!defined('PASSWORD_CHANGED')) define('PASSWORD_CHANGED', "Password successfully changed!");
if (!defined('ERROR_OLD_PASSWORD_WRONG')) define('ERROR_OLD_PASSWORD_WRONG', "The old password you entered is wrong.");
if (!defined('USER_EMAIL_LABEL')) define('USER_EMAIL_LABEL', "Your email address:");
if (!defined('DOUBLECLICK_LABEL')) define('DOUBLECLICK_LABEL', "Doubleclick Editing:");
if (!defined('SHOW_COMMENTS_LABEL')) define('SHOW_COMMENTS_LABEL', "Show comments by default:");
if (!defined('RECENTCHANGES_DISPLAY_LIMIT_LABEL')) define('RECENTCHANGES_DISPLAY_LIMIT_LABEL', "RecentChanges display limit:");
if (!defined('PAGEREVISION_LIST_LIMIT_LABEL')) define('PAGEREVISION_LIST_LIMIT_LABEL', "Page revisions list limit:");
if (!defined('UPDATE_SETTINGS_INPUT')) define('UPDATE_SETTINGS_INPUT', "Update Settings");
if (!defined('CHANGE_PASSWORD_HEADING')) define('CHANGE_PASSWORD_HEADING', "Change your password:");
if (!defined('CURRENT_PASSWORD_LABEL')) define('CURRENT_PASSWORD_LABEL', "Your current password:");
if (!defined('PASSWORD_REMINDER_LABEL')) define('PASSWORD_REMINDER_LABEL', "Password reminder:");
if (!defined('NEW_PASSWORD_LABEL')) define('NEW_PASSWORD_LABEL', "Your new password:");
if (!defined('NEW_PASSWORD_CONFIRM_LABEL')) define('NEW_PASSWORD_CONFIRM_LABEL', "Confirm new password:");
if (!defined('CHANGE_BUTTON_LABEL')) define('CHANGE_BUTTON_LABEL', "Change password");
if (!defined('REGISTER_BUTTON_LABEL')) define('REGISTER_BUTTON_LABEL', "Register");
if (!defined('QUICK_LINKS_HEADING')) define('QUICK_LINKS_HEADING', "Quick links");
if (!defined('QUICK_LINKS')) define('QUICK_LINKS', "See a list of pages you own (MyPages) and pages you've edited (MyChanges).");
if (!defined('ERROR_WRONG_PASSWORD')) define('ERROR_WRONG_PASSWORD', "Sorry, you entered the wrong password.");
if (!defined('ERROR_WRONG_HASH')) define('ERROR_WRONG_HASH', "Sorry, you entered a wrong password reminder.");
if (!defined('ERROR_EMPTY_USERNAME')) define('ERROR_EMPTY_USERNAME', "Please fill in your user name.");
if (!defined('ERROR_NON_EXISTENT_USERNAME')) define('ERROR_NON_EXISTENT_USERNAME', "Sorry, this user name doesn't exist.");
if (!defined('ERROR_RESERVED_PAGENAME')) define('ERROR_RESERVED_PAGENAME', "Sorry, this name is reserved for a page. Please choose a different name.");
if (!defined('ERROR_WIKINAME')) define('ERROR_WIKINAME', "Username must be formatted as a ##\"\"WikiName\"\"##, e.g. ##\"\"JohnDoe\"\"##.");
if (!defined('ERROR_EMPTY_PASSWORD')) define('ERROR_EMPTY_PASSWORD', "Please fill in a password.");
if (!defined('ERROR_EMPTY_PASSWORD_OR_HASH')) define('ERROR_EMPTY_PASSWORD_OR_HASH', "Please fill your password or hash.");
if (!defined('ERROR_EMPTY_CONFIRMATION_PASSWORD')) define('ERROR_EMPTY_CONFIRMATION_PASSWORD', "Please confirm your password in order to register a new account.");
if (!defined('ERROR_EMPTY_NEW_CONFIRMATION_PASSWORD')) define('ERROR_EMPTY_NEW_CONFIRMATION_PASSWORD', "Please confirm your new password in order to update your account.");
if (!defined('ERROR_EMPTY_NEW_PASSWORD')) define('ERROR_EMPTY_NEW_PASSWORD', "You must also fill in a new password.");
if (!defined('ERROR_PASSWORD_MATCH')) define('ERROR_PASSWORD_MATCH', "Passwords don't match.");
if (!defined('ERROR_EMAIL_ADDRESS_REQUIRED')) define('ERROR_EMAIL_ADDRESS_REQUIRED', "Please specify an email address.");
if (!defined('ERROR_INVALID_EMAIL_ADDRESS')) define('ERROR_INVALID_EMAIL_ADDRESS', "That doesn't quite look like an email address.");
if (!defined('ERROR_INVALID_REVISION_DISPLAY_LIMIT')) define('ERROR_INVALID_REVISION_DISPLAY_LIMIT', "The number of page revisions should not exceed %d.");
if (!defined('ERROR_INVALID_RECENTCHANGES_DISPLAY_LIMIT')) define('ERROR_INVALID_RECENTCHANGES_DISPLAY_LIMIT', "The number of recently changed pages should not exceed %d.");
if (!defined('REGISTRATION_SUCCEEDED')) define('REGISTRATION_SUCCEEDED', "You have successfully registered!");
if (!defined('REGISTERED_USER_LOGIN_LABEL')) define('REGISTERED_USER_LOGIN_LABEL', "If you're already a registered user, log in here!");
if (!defined('REGISTER_HEADING')) define('REGISTER_HEADING', "===Login/Register===");
if (!defined('WIKINAME_LABEL')) define('WIKINAME_LABEL', "Your <abbr title=\"A WikiName is formed by two or more capitalized words without space, e.g. JohnDoe\">WikiName</abbr>:");
if (!defined('PASSWORD_LABEL')) define('PASSWORD_LABEL', "Password (%s+ chars):");
if (!defined('LOGIN_BUTTON_LABEL')) define('LOGIN_BUTTON_LABEL', "Login");
if (!defined('LOGOUT_BUTTON_LABEL')) define('LOGOUT_BUTTON_LABEL', "Logout");
if (!defined('NEW_USER_REGISTER_LABEL')) define('NEW_USER_REGISTER_LABEL', "Stuff you only need to fill in when you're logging in for the first time (and thus signing up as a new user on this site).");
if (!defined('CONFIRM_PASSWORD_LABEL')) define('CONFIRM_PASSWORD_LABEL', "Confirm password:");
if (!defined('RETRIEVE_PASSWORD_HEADING')) define('RETRIEVE_PASSWORD_HEADING', "===Forgot your password?===");
if (!defined('RETRIEVE_PASSWORD_MESSAGE')) define('RETRIEVE_PASSWORD_MESSAGE', "If you need a password reminder, click [[PasswordForgotten | here]]. --- You can login here using your password reminder.");
if (!defined('TEMP_PASSWORD_LABEL')) define('TEMP_PASSWORD_LABEL', "Password reminder:");
// added for FreeCap CAPTCHA
if (!defined('CAPTCHA_LABEL')) define('CAPTCHA_LABEL', "letters in the above picture:");

//initialize variables
$params = '';
$url = '';
$email = '';
$doubleclickedit = '';
$show_comments = '';
$revisioncount = '';
$changescount = '';
$password = '';
$oldpass = '';
$password_confirm = '';
$pw_selected = '';
$hash_selected = '';
$username_highlight = '';
$username_temp_highlight = '';
$password_temp_highlight = '';
$email_highlight = '';
$password_highlight = '';
$password_new_highlight = '';
$password_confirm_highlight = '';
$revisioncount_highlight = '';
$changescount_highlight = '';

//create URL
$url = $this->config['base_url'].$this->tag;

// append URL params depending on rewrite_mode
$params = ($this->config['rewrite_mode'] == 1) ? '?' : '&';

// BEGIN *** Logout ***
// is user trying to log out?
#if (isset($_REQUEST['action']) && ($_REQUEST['action'] == 'logout'))   // JavaScript button with GET
if (isset($_POST['logout']) && $_POST['logout'] == LOGOUT_BUTTON_LABEL)     // replaced with normal form button #353, #312
{
    $this->LogoutUser();
    $params .= 'out=true';
    $this->Redirect($url.$params);
}
// END *** Logout ***

// BEGIN *** Usersettings ***
// user is still logged in
else if ($user = $this->GetUser())
{
    // is user trying to update user settings?
    if (isset($_POST['action']) && ($_POST['action'] == 'update'))
    {
        // get POST parameters
        $email = $this->GetSafeVar('email', 'post');
        $doubleclickedit = $this->GetSafeVar('doubleclickedit', 'post');
        $show_comments = $this->GetSafeVar('show_comments', 'post');
        $revisioncount = (int) $this->GetSafeVar('revisioncount', 'post');
        $changescount = (int) $this->GetSafeVar('changescount', 'post');

        // validate form input
        switch (TRUE)
        {
            case (strlen($email) == 0): //email is empty
                $error = ERROR_EMAIL_ADDRESS_REQUIRED;
                $email_highlight = INPUT_ERROR_STYLE;
                break;
            case (!preg_match(VALID_EMAIL_PATTERN, $email)): //invalid email
                $error = ERROR_INVALID_EMAIL_ADDRESS;
                $email_highlight = INPUT_ERROR_STYLE;
                break;
            case (($revisioncount < REVISION_DISPLAY_LIMIT_MIN) || ($revisioncount > REVISION_DISPLAY_LIMIT_MAX)): //invalid revision display limit
                $error = sprintf(ERROR_INVALID_REVISION_DISPLAY_LIMIT, REVISION_DISPLAY_LIMIT_MAX);
                $revisioncount_highlight = INPUT_ERROR_STYLE;
                break;
            case (($changescount < RECENTCHANGES_DISPLAY_LIMIT_MIN) || ($changescount > RECENTCHANGES_DISPLAY_LIMIT_MAX)): //invalid recentchanges display limit
                $error = sprintf(ERROR_INVALID_RECENTCHANGES_DISPLAY_LIMIT, RECENTCHANGES_DISPLAY_LIMIT_MAX);
                $changescount_highlight = INPUT_ERROR_STYLE;
                break;
            default: // input is valid
                $this->Query('UPDATE '.$this->config['table_prefix'].'users SET '.
                    "email = '".mysql_real_escape_string($email)."', ".
                    "doubleclickedit = '".mysql_real_escape_string($doubleclickedit)."', ".
                    "show_comments = '".mysql_real_escape_string($show_comments)."', ".
                    "revisioncount = '".mysql_real_escape_string($revisioncount)."', ".
                    "changescount = '".mysql_real_escape_string($changescount)."' ".
                    "WHERE name = '".$user['name']."' LIMIT 1");
                $this->SetUser($this->LoadUser($user["name"]));
           
                // forward
                $params .= 'stored=true';
                $this->Redirect($url.$params);
        }
    }
    //user just logged in
    else
    {
        // get stored settings
        $email = $user['email'];
        $doubleclickedit = $user['doubleclickedit'];
        $show_comments = $user['show_comments'];
        $revisioncount = $user['revisioncount'];
        $changescount = $user['changescount'];
    }

    // display user settings form
    echo '<h3>'.USER_SETTINGS_HEADING.'</h3>';
    echo $this->FormOpen();
?>
    <input type="hidden" name="action" value="update" />
    <table class="usersettings">
        <tr>
            <td>&nbsp;</td>
            <td>Hello, <?php echo $this->Link($user['name']) ?>!</td>
        </tr>
<?php

    // create confirmation message if needed
    switch(TRUE)
    {
        case (isset($_GET['registered']) && $_GET['registered'] == 'true'):
            $success = REGISTRATION_SUCCEEDED;
            break;
        case (isset($_GET['stored']) && $_GET['stored'] == 'true'):
            $success = USER_SETTINGS_STORED;
            break;
        case (isset($_GET['newpassword']) && $_GET['newpassword'] == 'true'):
            $success = PASSWORD_CHANGED;
    }

    // display error or confirmation message
    switch(TRUE)
    {
        case (isset($error)):
            echo '<tr><td></td><td><em class="error">'.$this->Format($error).'</em></td></tr>'."\n";
            break;
        case (isset($success)):
            echo '<tr><td></td><td><em class="success">'.$this->Format($success).'</em></td></tr>'."\n";       
            break;
        default:
    }
?>
        <tr>
            <td align="right"><?php echo USER_EMAIL_LABEL ?></td>
            <td><input <?php echo $email_highlight; ?> name="email" value="<?php echo $this->htmlspecialchars_ent($email) ?>" size="40" /></td>
        </tr>
        <tr>
            <td align="right"><?php echo DOUBLECLICK_LABEL ?></td>
            <td><input type="hidden" name="doubleclickedit" value="N" /><input type="checkbox" name="doubleclickedit" value="Y" <?php echo $doubleclickedit == 'Y' ? 'checked="checked"' : '' ?> /></td>
        </tr>
        <tr>
            <td align="right"><?php echo SHOW_COMMENTS_LABEL ?></td>
            <td><input type="hidden" name="show_comments" value="N" /><input type="checkbox" name="show_comments" value="Y" <?php echo $show_comments == 'Y' ? 'checked="checked"' : '' ?> /></td>
        </tr>
        <tr>
            <td align="right"><?php echo PAGEREVISION_LIST_LIMIT_LABEL ?></td>
            <td><input <?php echo $revisioncount_highlight; ?> name="revisioncount" value="<?php echo $this->htmlspecialchars_ent($revisioncount) ?>" size="40" /></td>
        </tr>
        <tr>
            <td align="right"><?php echo RECENTCHANGES_DISPLAY_LIMIT_LABEL ?></td>
            <td><input <?php echo $changescount_highlight; ?> name="changescount" value="<?php echo $this->htmlspecialchars_ent($changescount) ?>" size="40" /></td>
        </tr>
        <tr>
            <td>&nbsp;</td>
            <td><input type="submit" value="<?php echo UPDATE_SETTINGS_INPUT ?>" /><!-- <input type="button" value="<?php echo LOGOUT_BUTTON_LABEL; ?>" onclick="document.location='<?php echo $this->href('', '', 'action=logout'); ?>'" /></td>-->
                <input id="logout" name="logout" type="submit" value="<?php echo LOGOUT_BUTTON_LABEL; ?>" /><!--#353,#312-->
            </td>
        </tr>
    </table>
<?php  
    echo $this->FormClose(); //close user settings form

    if (isset($_POST['action']) && ($_POST['action'] == 'changepass'))
    {
        // check password
        $oldpass = $_POST['oldpass']; //can be current password or hash sent as password reminder
        $password = $_POST['password'];
        $password_confirm = $_POST['password_confirm'];
        $update_option = $this->GetSafeVar('update_option', 'post');
       
        switch (TRUE)
        {
            case (strlen($oldpass) == 0):
                $passerror = ERROR_EMPTY_PASSWORD_OR_HASH;
                $password_highlight = INPUT_ERROR_STYLE;
                break;
            case (($update_option == 'pw') && md5($oldpass) != $user['password']): //wrong password
                $passerror = ERROR_WRONG_PASSWORD;
                $pw_selected = 'selected="selected"';
                $password_highlight = INPUT_ERROR_STYLE;           
                break;
            case (($update_option == 'hash') && $oldpass != $user['password']): //wrong hash
                $passerror = ERROR_WRONG_HASH;
                $hash_selected = 'selected="selected"';
                $password_highlight = INPUT_ERROR_STYLE;           
                break;
            case (strlen($password) == 0):
                $passerror = ERROR_EMPTY_NEW_PASSWORD;
                $password_highlight = INPUT_ERROR_STYLE;           
                $password_new_highlight = INPUT_ERROR_STYLE;
                break;
            case (preg_match("/ /", $password)):
                $passerror = ERROR_NO_BLANK;
                $password_highlight = INPUT_ERROR_STYLE;           
                $password_new_highlight = INPUT_ERROR_STYLE;
                break;
            case (strlen($password) < PASSWORD_MIN_LENGTH):
                $passerror = sprintf(ERROR_PASSWORD_TOO_SHORT, PASSWORD_MIN_LENGTH);
                $password_highlight = INPUT_ERROR_STYLE;           
                $password_new_highlight = INPUT_ERROR_STYLE;
                break;
            case (strlen($password_confirm) == 0):
                $passerror = ERROR_EMPTY_NEW_CONFIRMATION_PASSWORD;
                $password_highlight = INPUT_ERROR_STYLE;           
                $password_new_highlight = INPUT_ERROR_STYLE;
                $password_confirm_highlight = INPUT_ERROR_STYLE;
                break;
            case ($password_confirm != $password):
                $passerror = ERROR_PASSWORD_MATCH;
                $password_highlight = INPUT_ERROR_STYLE;
                $password_new_highlight = INPUT_ERROR_STYLE;           
                $password_confirm_highlight = INPUT_ERROR_STYLE;
                break;
            default:
                $this->Query('UPDATE '.$this->config['table_prefix'].'users set '."password = md5('".mysql_real_escape_string($password)."') "."WHERE name = '".$user['name']."'");
                $user['password'] = md5($password);
                $this->SetUser($user);
                $params .= 'newpassword=true';
                $this->Redirect($url.$params);
        }
    }

    //display password update form
    echo '<hr />'."\n";
    echo $this->FormOpen();
?>
    <input type="hidden" name="action" value="changepass" />
    <h5><?php echo CHANGE_PASSWORD_HEADING ?></h5>
    <table class="usersettings">
<?php
        if (isset($passerror))
        {
            print('<tr><td></td><td><em class="error">'.$this->Format($passerror).'</em></td></tr>'."\n");
        }
?>
        <tr>
            <td align="right">
                <select name="update_option">
                    <option value="pw" <?php echo $pw_selected; ?>><?php echo CURRENT_PASSWORD_LABEL; ?></option>
                    <option value="hash" <?php echo $hash_selected; ?>><?php echo PASSWORD_REMINDER_LABEL; ?></option>
            </select></td>
            <td><input <?php echo $password_highlight; ?> type="password" name="oldpass" size="40" /></td>
        </tr>
        <tr>
            <td align="right"><?php echo NEW_PASSWORD_LABEL ?></td>
            <td><input  <?php echo $password_new_highlight; ?> type="password" name="password" size="40" /></td>
        </tr>
        <tr>
            <td align="right"><?php echo NEW_PASSWORD_CONFIRM_LABEL ?></td>
            <td><input  <?php echo $password_confirm_highlight; ?> type="password" name="password_confirm" size="40" /></td>
        </tr>
        <tr>
            <td></td>
            <td><input type="submit" value="<?php echo CHANGE_BUTTON_LABEL ?>" size="40" /></td>
        </tr>
    </table>
<?php
    echo '<hr />'."\n";
    echo '<h5>'.QUICK_LINKS_HEADING.'</h5>'."\n";
    echo $this->Format(QUICK_LINKS);
    print($this->FormClose());
}
// user is not logged in
else
{
    // print confirmation message on successful logout
    if (isset($_GET['out']) && ($_GET['out'] == 'true'))
    {
        $success = USER_LOGGED_OUT;
    }

    // is user trying to log in or register?
    if (isset($_POST['action']) && ($_POST['action'] == 'login'))
    {
        // if user name already exists, check password
        if (isset($_POST['name']) && $existingUser = $this->LoadUser($_POST['name']))
        {
            // check password
            switch(TRUE){
                case (strlen($_POST['password']) == 0):
                    $error = ERROR_EMPTY_PASSWORD;
                    $password_highlight = INPUT_ERROR_STYLE;
                    break;
                case (md5($_POST['password']) != $existingUser['password']):
                    $error = ERROR_WRONG_PASSWORD;
                    $password_highlight = INPUT_ERROR_STYLE;
                    break;
                default:
                    $this->SetUser($existingUser);
                    $this->Redirect($url, '');
            }
        }
        // BEGIN *** Register ***
        else // otherwise, proceed to registration
        { //added for freecap CAPTCHA
                if(!empty($_SESSION['freecap_word_hash']) && !empty($_POST['word']))
            {
            // all freeCap words are lowercase.
            // font #4 looks uppercase, but trust me, it's not...
            if($_SESSION['hash_func'](strtolower($_POST['word']))==$_SESSION['freecap_word_hash'])
                {
                // reset freeCap session vars
                // cannot stress enough how important it is to do this
                // defeats re-use of known image with spoofed session id
                $_SESSION['freecap_attempts'] = 0;
                $_SESSION['freecap_word_hash'] = false;

                // now go somewhere else
                // header("Location: somewhere.php");
            $name = trim($_POST['name']);
            $email = trim($this->GetSafeVar('email', 'post'));
            $password = $_POST['password'];
            $confpassword = $_POST['confpassword'];
           
            // validate input
            switch(TRUE)
            {
                case (strlen($name) == 0):
                    $error = ERROR_EMPTY_USERNAME;
                    $username_highlight = INPUT_ERROR_STYLE;
                    break;
                case (!$this->IsWikiName($name)):
                    $error = ERROR_WIKINAME;
                    $username_highlight = INPUT_ERROR_STYLE;
                    break;
                case ($this->ExistsPage($name)):
                    $error = ERROR_RESERVED_PAGENAME;
                    $username_highlight = INPUT_ERROR_STYLE;
                    break;
                case (strlen($password) == 0):
                    $error = ERROR_EMPTY_PASSWORD;
                    $password_highlight = INPUT_ERROR_STYLE;
                    break;
                case (preg_match("/ /", $password)):
                    $error = ERROR_NO_BLANK;
                    $password_highlight = INPUT_ERROR_STYLE;
                    break;
                case (strlen($password) < PASSWORD_MIN_LENGTH):
                    $error = sprintf(ERROR_PASSWORD_TOO_SHORT, PASSWORD_MIN_LENGTH);
                    $password_highlight = INPUT_ERROR_STYLE;
                    break;
                case (strlen($confpassword) == 0):
                    $error = ERROR_EMPTY_CONFIRMATION_PASSWORD;
                    $password_highlight = INPUT_ERROR_STYLE;
                    $password_confirm_highlight = INPUT_ERROR_STYLE;
                    break;
                case ($confpassword != $password):
                    $error = ERROR_PASSWORD_MATCH;
                    $password_highlight = INPUT_ERROR_STYLE;
                    $password_confirm_highlight = INPUT_ERROR_STYLE;
                    break;
                case (strlen($email) == 0):
                    $error = ERROR_EMAIL_ADDRESS_REQUIRED;
                    $email_highlight = INPUT_ERROR_STYLE;
                    $password_highlight = INPUT_ERROR_STYLE;
                    $password_confirm_highlight = INPUT_ERROR_STYLE;
                    break;
                case (!preg_match(VALID_EMAIL_PATTERN, $email)):
                    $error = ERROR_INVALID_EMAIL_ADDRESS;
                    $email_highlight = INPUT_ERROR_STYLE;
                    $password_highlight = INPUT_ERROR_STYLE;
                    $password_confirm_highlight = INPUT_ERROR_STYLE;
                    break;
                default: //valid input, create user
                    $this->Query("INSERT INTO ".$this->config['table_prefix']."users SET ".
                        "signuptime = now(), ".
                        "name = '".mysql_real_escape_string($name)."', ".
                        "email = '".mysql_real_escape_string($email)."', ".
                        "password = md5('".mysql_real_escape_string($_POST['password'])."')");

                    // log in
                    $this->SetUser($this->LoadUser($name));
                    $params .= 'registered=true';
                    $this->Redirect($url.$params);
            }
                            } else {
                $captcha="You entered the wrong letters, please try again.";
                }
            } else {
            $captcha="You entered none of the letters. Please do so.";
            }
       
        }
        // END *** Register ***
    }

    // BEGIN *** Usersettings ***
    elseif  (isset($_POST['action']) && ($_POST['action'] == 'updatepass'))
    {
            $name = trim($_POST['yourname']);
        if (strlen($name) == 0) // empty username  
        {
            $newerror = ERROR_EMPTY_USERNAME;
            $username_temp_highlight = INPUT_ERROR_STYLE;
        }
        elseif (!$this->IsWikiName($name)) // check if name is WikiName style  
        {
            $newerror = ERROR_WIKINAME;
            $username_temp_highlight = INPUT_ERROR_STYLE;
        }
        elseif (!($this->LoadUser($_POST['yourname']))) //check if user exists
        {
            $newerror = ERROR_NON_EXISTENT_USERNAME;
            $username_temp_highlight = INPUT_ERROR_STYLE;
        }
        elseif ($existingUser = $this->LoadUser($_POST['yourname']))  // if user name already exists, check password
        {
            // updatepassword
            if ($existingUser['password'] == $_POST['temppassword'])
            {
                $this->SetUser($existingUser, $_POST['remember']);
                $this->Redirect($url);
            }
            else
            {
                $newerror = ERROR_WRONG_PASSWORD;
                $password_temp_highlight = INPUT_ERROR_STYLE;
            }
        }
    }
    // END *** Usersettings ***

    // BEGIN ***  Login/Register ***
    print($this->FormOpen());
?>
<script language="javascript">
<!--
function new_freecap()
{
    // loads new freeCap image
    if(document.getElementById)
    {
        // extract image name from image source (i.e. cut off ?randomness)
        thesrc = document.getElementById("freecap").src;
        thesrc = thesrc.substring(0,thesrc.lastIndexOf(".")+4);
        // add ?(random) to prevent browser/isp caching
        document.getElementById("freecap").src = thesrc+"?"+Math.round(Math.random()*100000);
    } else {
        alert("Sorry, cannot autoreload freeCap image\nSubmit the form and a new freeCap will be loaded");
    }
}
//-->
</script>
    <input type="hidden" name="action" value="login" />
    <table class="usersettings">
    <tr>
        <td colspan="2"><?php echo $this->Format(REGISTER_HEADING) ?></td>
        <td>&nbsp;</td>
    </tr>
    <tr>
        <td>&nbsp;</td>
        <td><?php echo $this->Format(REGISTERED_USER_LOGIN_LABEL); ?></td>
    </tr>
<?php
    switch (true)
    {
        case (isset($error)):
            echo '<tr><td></td><td><em class="error">'.$this->Format($error).'</em></td></tr>'."\n";
            break;
        case (isset($success)):
            echo '<tr><td></td><td><em class="success">'.$this->Format($success).'</em></td></tr>'."\n";
            break;
// added for FreeCap CAPTCHA
        case (isset($captcha)):
            echo '<tr><td></td><td><em class="error">'.$this->Format($captcha).'</em></td></tr>'."\n";
            break;
       
    }
?>
    <tr>
        <td align="right"><?php echo WIKINAME_LABEL ?></td>
        <td><input <?php echo $username_highlight; ?> name="name" size="40" value="<?php echo $this->GetSafeVar('name', 'post'); ?>" /></td>
    </tr>
    <tr>
        <td align="right"><?php echo sprintf(PASSWORD_LABEL, PASSWORD_MIN_LENGTH) ?></td>
        <td><input <?php echo $password_highlight; ?> type="password" name="password" size="40" /></td>
    </tr>
    <tr>
        <td>&nbsp;</td>
        <td><input type="submit" value="<?php echo LOGIN_BUTTON_LABEL ?>" size="40" /></td>
    </tr>
    <tr>
        <td>&nbsp;</td>
        <td width="500"><?php echo $this->Format(NEW_USER_REGISTER_LABEL); ?></td>
    </tr>
    <tr>
        <td align="right"><?php echo CONFIRM_PASSWORD_LABEL ?></td>
        <td><input  <?php echo $password_confirm_highlight; ?> type="password" name="confpassword" size="40" /></td>
    </tr>
    <tr>
        <td align="right"><?php echo USER_EMAIL_LABEL ?></td>
        <td><input <?php echo $email_highlight; ?> name="email" size="40" value="<?php echo $email; ?>" /></td>
    </tr>
    <?php //added for Freecap CAPTCHA
                    setcookie("sessname", session_name());
            ?>
    <tr>
        <td colspan="2"><img src="actions/freecap/freecap.php" id="freecap"></td>
    </tr>
    <tr>
        <td colspan="2">If you can't read the word, <a href="#" onClick="this.blur();new_freecap();return false;">click here</a></td>
    </tr>
    <tr>
        <td align="right"><?php echo CAPTCHA_LABEL ?></td>
        <td><input type="text" name="word" /></td>
    </tr>
    <?php //added for Freecap CAPTCHA ?>
    <tr>
        <td>&nbsp;</td>
        <td><input type="submit" value="<?php echo REGISTER_BUTTON_LABEL ?>" size="40" /></td>
    </tr>
    </table>
<?php
    print($this->FormClose());
    // END *** Login/Register ***

    // BEGIN *** Login Temp Password ***
    print($this->FormOpen());
?>
    <input type="hidden" name="action" value="updatepass" />
    <table class="usersettings">
    <tr>
        <td colspan="2"><br /><hr /><?php echo $this->Format(RETRIEVE_PASSWORD_HEADING) ?></td><td></td>
    </tr>
    <tr>
        <td align="left"></td>
        <td><?php echo $this->Format(RETRIEVE_PASSWORD_MESSAGE) ?></td>
    </tr>
<?php  
    if (isset($newerror))
    {
        print('<tr><td></td><td><em class="error">'.$this->Format($newerror).'</em></td></tr>'."\n");
    }
?>
    <tr>
        <td align="right"><?php echo WIKINAME_LABEL ?></td>
        <td><input <?php echo $username_temp_highlight; ?> name="yourname" value="<?php echo $this->GetSafeVar('yourname', 'post'); ?>" size="40" /></td>
    </tr>
    <tr>
        <td align="right"><?php echo TEMP_PASSWORD_LABEL ?></td>
        <td><input <?php echo $password_temp_highlight; ?> name="temppassword" size="40" /></td>
    </tr>
    <tr>
        <td>&nbsp;</td>
        <td><input type="submit" value="<?php echo LOGIN_BUTTON_LABEL ?>" size="40" /></td>
    </tr>
   </table>
<?php
    print($this->FormClose());
    // END *** Login Temp Password ***
}
?>


actions/usersettings/usersettings.php (version 1.1.7 [trunk] only)
<?php
/**
 * Display a form to register, login and change user settings.
 * Enchanced to include captchas, based upon work by YodaHome
 * (http://wikkawiki.org/FreeCap).  See that page for implementation
 * details.
 *
 * Note: To enable captchas, set allow_user-registration in
 * wikak.config.php to '3'.
 *
 * @package     Actions
 * @version     $Id: usersettings.php,v 1.1 2007/07/14 05:32:14 brian Exp brian $
 * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
 * @filesource
 * @since       1.1.7
 *
 * @author      {@link http://wikkawiki.org/MinusF MinusF} (code cleanup and validation)
 * @author      {@link http://wikkawiki.org/DarTar Dario Taraborelli} (further cleanup, i18n, replaced JS dialogs with server-generated messages)
 * @author      {@link http://wikkawiki.org/NilsLindenberg Nils Lindenberg} (possibility to restrict registration)
 *
 * @uses        Wakka::htmlspecialchars_ent()
 *
 * @todo        use different actions for registration / login / user settings;
 * @todo        add documentation links or short explanations for each option;
 * @todo        use error handler for displaying messages and highlighting
 *              invalid input fields;
 * @todo        remove useless redirections;
 * @todo        [accessibility] make logout independent of JavaScript
 * @todo    replace $_REQUEST with either $_GET or $_POST (or both if really
 *          necessary) - #312  
 * Captcha TODOs:
 * @todo    Parameterize allow_user_registration in wikka.config.php
 *          to use defines rather than integers
 * @todo    Wrap in div blocks and style via CSS
 * @todo    Add option to bypass for manual registration
 *          (accessibility)
 */


// defaults
if (!defined('PASSWORD_MIN_LENGTH')) define('PASSWORD_MIN_LENGTH', "5");
if (!defined('VALID_EMAIL_PATTERN')) define('VALID_EMAIL_PATTERN', "/^.+?\@.+?\..+$/"); //TODO: Use central regex library
if (!defined('REVISION_DISPLAY_LIMIT_MIN')) define('REVISION_DISPLAY_LIMIT_MIN', "0"); // 0 means no limit, 1 is the minimum number of revisions
if (!defined('REVISION_DISPLAY_LIMIT_MAX')) define('REVISION_DISPLAY_LIMIT_MAX', "20"); // keep this value within a reasonable limit to avoid an unnecessary long lists
if (!defined('RECENTCHANGES_DISPLAY_LIMIT_MIN')) define('RECENTCHANGES_DISPLAY_LIMIT_MIN', "0"); // 0 means no limit, 1 is the minimum number of changes
if (!defined('RECENTCHANGES_DISPLAY_LIMIT_MAX')) define('RECENTCHANGES_DISPLAY_LIMIT_MAX', "50"); // keep this value within a reasonable limit to avoid an unnecessary long list
if (!defined('INPUT_ERROR_STYLE')) define('INPUT_ERROR_STYLE', 'class="highlight"');
// added for FreeCap CAPTCHA
if (!defined('CAPTCHA_LABEL')) define('CAPTCHA_LABEL', "Type the letters in the picture:");
if (!defined('CAPTCHA_NO_READ')) define('CAPTCHA_NO_READ', "If you cannot read the word,");
if (!defined('CAPTCHA_NO_READ_CLICK_HERE')) define('CAPTCHA_NO_READ_CLICK_HERE', "click here");

//initialize variables
$params = '';
$url = '';
$email = '';
$doubleclickedit = '';
$show_comments = '';
$default_comment_display = '';
$revisioncount = '';
$changescount = '';
$password = '';
$oldpass = '';
$password_confirm = '';
$pw_selected = '';
$hash_selected = '';
$username_highlight = '';
$username_temp_highlight = '';
$password_temp_highlight = '';
$email_highlight = '';
$password_highlight = '';
$password_new_highlight = '';
$password_confirm_highlight = '';
$revisioncount_highlight = '';
$changescount_highlight = '';
$invitation_code_highlight = '';

$wikiname_expanded = '<abbr title="'.WIKINAME_LONG.'">'.WIKINAME_SHORT.'</abbr>';

//create URL
$url = $this->Href();

//Remember referring page if internal.
// - Getting correct regex to find the tag of referring page
preg_match('/^(.*)ReferrerMarker/', $this->Href('', 'ReferrerMarker'), $match);
$regex_referrer = '/^'.preg_quote($match[1], '/')."([^\\/\\?&]*)/";
if (isset($_SERVER['HTTP_REFERER']) && preg_match($regex_referrer, $_SERVER['HTTP_REFERER'], $match))
{
    if (strcasecmp($this->tag, $match[1]))
    {
        $_SESSION['go_back'] = $_SERVER['HTTP_REFERER'];
        //We save the tag of the referring page, this tag is to be shown in label <Go back to ...>. We must use a session here because if the user
        //Refresh the page by hitting <Enter> on the address bar, the value would be lost.
        $_SESSION['go_back_tag'] = $match[1];
    }
}

// append URL params depending on rewrite_mode
$params = ($this->config['rewrite_mode'] == 1) ? '?' : '&';

// BEGIN *** Logout ***
// is user trying to log out?
if (isset($_POST['logout']) && $_POST['logout'] == LOGOUT_BUTTON)       // replaced with normal form button #353, #312
{
    $this->LogoutUser();
}
// END *** Logout ***

// BEGIN *** Usersettings ***
// user is still logged in
if ($user = $this->GetUser())
{
    // is user trying to update user settings?
    if (isset($_POST['action']) && ($_POST['action'] == 'update'))
    {
        // get POST parameters
        $email = $this->GetSafeVar('email', 'post');
        $doubleclickedit = $this->GetSafeVar('doubleclickedit', 'post');
        $show_comments = $this->GetSafeVar('show_comments', 'post');
        $default_comment_display = $this->GetSafeVar('default_comment_display', 'post');
        $revisioncount = (int) $this->GetSafeVar('revisioncount', 'post');
        $changescount = (int) $this->GetSafeVar('changescount', 'post');

        // validate form input
        switch (TRUE)
        {
            case (strlen($email) == 0): //email is empty
                $error = ERROR_EMPTY_EMAIL_ADDRESS;
                $email_highlight = INPUT_ERROR_STYLE;
                break;
            case (!preg_match(VALID_EMAIL_PATTERN, $email)): //invalid email
                $error = ERROR_INVALID_EMAIL_ADDRESS;
                $email_highlight = INPUT_ERROR_STYLE;
                break;
            case (($revisioncount < REVISION_DISPLAY_LIMIT_MIN) || ($revisioncount > REVISION_DISPLAY_LIMIT_MAX)): //invalid revision display limit
                $error = sprintf(ERROR_INVALID_REVISION_DISPLAY_LIMIT, REVISION_DISPLAY_LIMIT_MAX);
                $revisioncount_highlight = INPUT_ERROR_STYLE;
                break;
            case (($changescount < RECENTCHANGES_DISPLAY_LIMIT_MIN) || ($changescount > RECENTCHANGES_DISPLAY_LIMIT_MAX)): //invalid recentchanges display limit
                $error = sprintf(ERROR_INVALID_RECENTCHANGES_DISPLAY_LIMIT, RECENTCHANGES_DISPLAY_LIMIT_MAX);
                $changescount_highlight = INPUT_ERROR_STYLE;
                break;
            default: // input is valid
                $this->Query('UPDATE '.$this->config['table_prefix'].'users SET '.
                    "email = '".mysql_real_escape_string($email)."', ".
                    "doubleclickedit = '".mysql_real_escape_string($doubleclickedit)."', ".
                    "show_comments = '".mysql_real_escape_string($show_comments)."', ".
                    "default_comment_display = '".mysql_real_escape_string($default_comment_display)."', ".
                    "revisioncount = '".mysql_real_escape_string($revisioncount)."', ".
                    "changescount = '".mysql_real_escape_string($changescount)."' ".
                    "WHERE name = '".$user['name']."' LIMIT 1");
                unset($this->specialCache['user'][strtolower($user['name'])]);  //invalidate cache if exists #368
                $this->SetUser($this->LoadUser($user["name"]));
        }
    }
    //user just logged in
    else
    {
        // get stored settings
        $email = $user['email'];
        $doubleclickedit = $user['doubleclickedit'];
        $show_comments = $user['show_comments'];
        $default_comment_display = $user['default_comment_display'];
        $revisioncount = $user['revisioncount'];
        $changescount = $user['changescount'];
    }

    // display user settings form
    echo $this->FormOpen();
?>
    <fieldset id="account"><legend><?php echo USER_ACCOUNT_LEGEND ?></legend>
    <span id="account_info">
    <?php printf(USER_LOGGED_IN_AS_CAPTION, $this->Link($user['name'])); ?>
    </span><input id="logout" name="logout" type="submit" value="<?php echo LOGOUT_BUTTON; ?>" /><!-- #353,#312-->
    <br class="clear" />
    </fieldset>
    <fieldset id="usersettings" class="usersettings"><legend><?php echo USER_SETTINGS_LEGEND ?></legend>
<?php

    // create confirmation message if needed
    switch(TRUE)
    {
        case (isset($_SESSION['usersettings_registered']) && $_SESSION['usersettings_registered'] == 'true'):
            unset($_SESSION['usersettings_registered']);
            $success = USER_REGISTERED_SUCCESS;
            break;
        //case (isset($_GET['stored']) && $_GET['stored'] == 'true'):
        case (isset($_POST['action']) && $_POST['action'] == 'update' && !isset($error)):
            $success = USER_SETTINGS_STORED_SUCCESS;
            break;
    }

    // display error or confirmation message
    switch(TRUE)
    {
        case (isset($error)):
            echo '<em class="error">'.$error.'</em><br />'."\n";
            break;
        case (isset($success)):
            echo '<em class="success">'.$success.'</em><br />'."\n";
            break;
    }

    if (isset($_POST['action']) && ($_POST['action'] == 'changepass'))
    {
        // check password
        $oldpass = $_POST['oldpass']; //can be current password or hash sent as password reminder
        $password = $_POST['password'];
        $password_confirm = $_POST['password_confirm'];
        $update_option = $this->GetSafeVar('update_option', 'post');
       
        switch (TRUE)
        {
            case (strlen($oldpass) == 0):
                $passerror = ERROR_EMPTY_PASSWORD_OR_HASH;
                $password_highlight = INPUT_ERROR_STYLE;
                break;
            case (($update_option == 'pw') && md5($oldpass) != $user['password']): //wrong old password
                $passerror = ERROR_INVALID_OLD_PASSWORD;
                $pw_selected = 'selected="selected"';
                $password_highlight = INPUT_ERROR_STYLE;           
                break;
            case (($update_option == 'hash') && $oldpass != $user['password']): //wrong reminder (hash)
                $passerror = ERROR_INVALID_HASH;
                $hash_selected = 'selected="selected"';
                $password_highlight = INPUT_ERROR_STYLE;           
                break;
            case (strlen($password) == 0):
                $passerror = ERROR_EMPTY_NEW_PASSWORD;
                $password_highlight = INPUT_ERROR_STYLE;           
                $password_new_highlight = INPUT_ERROR_STYLE;
                break;
            case (preg_match("/ /", $password)):
                $passerror = ERROR_PASSWORD_NO_BLANK;
                $password_highlight = INPUT_ERROR_STYLE;           
                $password_new_highlight = INPUT_ERROR_STYLE;
                break;
            case (strlen($password) < PASSWORD_MIN_LENGTH):
                $passerror = sprintf(ERROR_PASSWORD_TOO_SHORT, PASSWORD_MIN_LENGTH);
                $password_highlight = INPUT_ERROR_STYLE;           
                $password_new_highlight = INPUT_ERROR_STYLE;
                break;
            case (strlen($password_confirm) == 0):
                $passerror = ERROR_EMPTY_NEW_CONFIRMATION_PASSWORD;
                $password_highlight = INPUT_ERROR_STYLE;           
                $password_new_highlight = INPUT_ERROR_STYLE;
                $password_confirm_highlight = INPUT_ERROR_STYLE;
                break;
            case ($password_confirm != $password):
                $passerror = ERROR_PASSWORD_MATCH;
                $password_highlight = INPUT_ERROR_STYLE;
                $password_new_highlight = INPUT_ERROR_STYLE;           
                $password_confirm_highlight = INPUT_ERROR_STYLE;
                break;
            default:
                $this->Query('UPDATE '.$this->config['table_prefix'].'users SET '."password = md5('".mysql_real_escape_string($password)."') "."WHERE name = '".$user['name']."'");
                unset($this->specialCache['user'][strtolower($name)]);  //invalidate cache if exists #368
                $user['password'] = md5($password);
                $this->SetUser($user);
                $passsuccess = USER_PASSWORD_CHANGED_SUCCESS;
        }
    }

?>
    <input type="hidden" name="action" value="update" />
    <label for="email"><?php echo USER_EMAIL_LABEL ?></label>
    <input id="email" type="text" <?php echo $email_highlight; ?> name="email" value="<?php echo $this->htmlspecialchars_ent($email) ?>" size="40" />
    <br />
    <label for="doubleclick"><?php echo DOUBLECLICK_LABEL ?></label>
    <input type="hidden" name="doubleclickedit" value="N" />
    <input id="doubleclick" type="checkbox" name="doubleclickedit" value="Y" <?php echo $doubleclickedit == 'Y' ? 'checked="checked"' : '' ?> />
    <br />
    <label for="showcomments"><?php echo SHOW_COMMENTS_LABEL ?></label>
    <input type="hidden" name="show_comments" value="N" />
    <input id="showcomments" type="checkbox" name="show_comments" value="Y" <?php echo $show_comments == 'Y' ? 'checked="checked"' : '' ?> />
    <fieldset><legend><?php echo DEFAULT_COMMENT_STYLE_LABEL ?></legend>
    <input id="default_comment_flat_asc" type="radio" name="default_comment_display" value="1" <?php echo ($default_comment_display==1) ? 'checked="checked"' : '' ?> /><label for="default_comment_flat_asc"><?php echo COMMENT_ASC_LABEL ?></label><br />
    <input id="default_comment_flat_desc" type="radio" name="default_comment_display" value="2" <?php echo ($default_comment_display==2) ? 'checked="checked"' : '' ?> /><label for="default_comment_flat_desc"><?php echo COMMENT_DEC_LABEL ?></label><br />
    <input id="default_comment_threaded" type="radio" name="default_comment_display" value="3" <?php echo ($default_comment_display==3) ? 'checked="checked"' : '' ?> /><label for="default_comment_threaded"><?php echo COMMENT_THREADED_LABEL ?></label><br />
    </fieldset>
    <br />
    <label for="revisioncount"><?php echo PAGEREVISION_LIST_LIMIT_LABEL ?></label>
    <input id="revisioncount" type="text" <?php echo $revisioncount_highlight; ?> name="revisioncount" value="<?php echo $this->htmlspecialchars_ent($revisioncount) ?>" size="40" />
    <br />
    <label for="changescount"><?php echo RECENTCHANGES_DISPLAY_LIMIT_LABEL ?></label>
    <input id="changescount" type="text" <?php echo $changescount_highlight; ?> name="changescount" value="<?php echo $this->htmlspecialchars_ent($changescount) ?>" size="40" />
    <br />
    <input id="updatesettingssubmit" type="submit" value="<?php echo UPDATE_SETTINGS_BUTTON ?>" />
    <br />
    </fieldset>
<?php  
    echo $this->FormClose(); //close user settings form

    //display password update form
    echo $this->FormOpen();
?>
    <fieldset class="usersettings" id="changepassword"><legend><?php echo CHANGE_PASSWORD_LEGEND ?></legend>
    <input type="hidden" name="action" value="changepass" />
<?php
        if (isset($passerror))
        {
            echo '<em class="error">'.$passerror.'</em><br />'."\n";
        }
        else if (isset($passsuccess))
        {
            echo '<em class="success">'.$passsuccess.'</em><br />'."\n";           
        }
?>
    <select id="update_option" name="update_option">
        <option value="pw" <?php echo $pw_selected; ?>><?php echo CURRENT_PASSWORD_OPTION; ?></option>
        <option value="hash" <?php echo $hash_selected; ?>><?php echo PASSWORD_REMINDER_OPTION; ?></option>
    </select>
    <input <?php echo $password_highlight; ?> type="password" name="oldpass" size="40" />
    <br />
    <label for="password"><?php echo NEW_PASSWORD_LABEL ?></label>
    <input id="password" <?php echo $password_new_highlight; ?> type="password" name="password" size="40" />
    <br />
    <label for="password_confirm"><?php echo NEW_PASSWORD_CONFIRM_LABEL ?></label>
    <input id="password_confirm" <?php echo $password_confirm_highlight; ?> type="password" name="password_confirm" size="40" />
    <br />
    <input id="changepasswordsubmit" type="submit" value="<?php echo CHANGE_PASSWORD_BUTTON ?>" size="40" />
    <br />
    </fieldset>
<?php
    echo $this->FormClose();
}
// END *** Usersettings ***
// BEGIN *** LOGIN/LOGOUT ***
else // user is not logged in
{
    // print confirmation message on successful logout
    if (isset($_POST['logout']) && $_POST['logout'] == LOGOUT_BUTTON)
    {
        $success = USER_LOGGED_OUT_SUCCESS;
    }

    // is user trying to log in or register?
    $register = $this->GetConfigValue('allow_user_registration');
    if (isset($_POST['action']) && ($_POST['action'] == 'login'))
    {
        // if user name already exists, check password
        if (isset($_POST['name']) && $existingUser = $this->LoadUser($_POST['name']))
        {
            // check password
            switch(TRUE){
                case (strlen($_POST['password']) == 0):
                    $error = ERROR_EMPTY_PASSWORD;
                    $password_highlight = INPUT_ERROR_STYLE;
                    break;
                case (md5($_POST['password']) != $existingUser['password']):
                    $error = ERROR_INVALID_PASSWORD;
                    $password_highlight = INPUT_ERROR_STYLE;
                    break;
                default:
                    $this->SetUser($existingUser);
                    if ((isset($_SESSION['go_back'])) && (isset($_POST['do_redirect'])))
                    {
                        $go_back = $_SESSION['go_back'];
                        unset($_SESSION['go_back']);
                        unset($_SESSION['go_back_tag']);
                        $this->Redirect($go_back);
                    }
                    $this->Redirect($url, '');
            }
        }
        // END *** Login/Logout ***
        // BEGIN *** Register ***
        // 3 = captcha registration
        else if ($register == '1' || $register == '2' || $register == '3') // otherwise, proceed to registration
        {
            if($register == '3' &&
                (empty($_SESSION['freecap_word_hash']) ||
                 empty($_POST['word'])))  
            {
                $captcha="You entered none of the letters. Please do so.";
            }
            else if($register == '3' &&
                    !($_SESSION['hash_func'](strtolower($_POST['word']))==$_SESSION['freecap_word_hash']))
            {
                $captcha="You entered the wrong letters, please try again.";
            } else
            {
                $name = trim($_POST['name']);
                $email = trim($this->GetSafeVar('email', 'post'));
                $password = $_POST['password'];
                $confpassword = $_POST['confpassword'];

                // validate input
                switch(TRUE)
                {
                    case (strlen($name) == 0):
                        $error = ERROR_EMPTY_USERNAME;
                        $username_highlight = INPUT_ERROR_STYLE;
                        break;
                    case (!$this->IsWikiName($name)):
                        $error = $this->Format(sprintf(ERROR_WIKINAME,'##""WikiName""##','##""'.WIKKA_SAMPLE_WIKINAME.'""##'));
                        $username_highlight = INPUT_ERROR_STYLE;
                        break;
                    case ($this->ExistsPage($name)):
                        $error = ERROR_RESERVED_PAGENAME;
                        $username_highlight = INPUT_ERROR_STYLE;
                        break;
                    case (strlen($password) == 0):
                        $error = ERROR_EMPTY_PASSWORD;
                        $password_highlight = INPUT_ERROR_STYLE;
                        break;
                    case (preg_match("/ /", $password)):
                        $error = ERROR_NO_BLANK;
                        $password_highlight = INPUT_ERROR_STYLE;
                        break;
                    case (strlen($password) < PASSWORD_MIN_LENGTH):
                        $error = sprintf(ERROR_PASSWORD_TOO_SHORT, PASSWORD_MIN_LENGTH);
                        $password_highlight = INPUT_ERROR_STYLE;
                        break;
                    case (strlen($confpassword) == 0):
                        $error = ERROR_EMPTY_CONFIRMATION_PASSWORD;
                        $password_highlight = INPUT_ERROR_STYLE;
                        $password_confirm_highlight = INPUT_ERROR_STYLE;
                        break;
                    case ($confpassword != $password):
                        $error = ERROR_PASSWORD_MATCH;
                        $password_highlight = INPUT_ERROR_STYLE;
                        $password_confirm_highlight = INPUT_ERROR_STYLE;
                        break;
                    case (strlen($email) == 0):
                        $error = ERROR_EMAIL_ADDRESS_REQUIRED;
                        $email_highlight = INPUT_ERROR_STYLE;
                        $password_highlight = INPUT_ERROR_STYLE;
                        $password_confirm_highlight = INPUT_ERROR_STYLE;
                        break;
                    case (!preg_match(VALID_EMAIL_PATTERN, $email)):
                        $error = ERROR_INVALID_EMAIL_ADDRESS;
                        $email_highlight = INPUT_ERROR_STYLE;
                        $password_highlight = INPUT_ERROR_STYLE;
                        $password_confirm_highlight = INPUT_ERROR_STYLE;
                        break;
                    case ($register == '2' && $_POST['invitation_code'] !==  $this->GetConfigValue('invitation_code')):
                        $error = ERROR_INVALID_INVITATION_CODE;
                        $invitation_code_highlight = INPUT_ERROR_STYLE;
                        break;
                    default: //valid input, create user
                        $this->Query("INSERT INTO ".$this->config['table_prefix']."users SET ".
                            "signuptime = now(), ".
                            "name = '".mysql_real_escape_string($name)."', ".
                            "email = '".mysql_real_escape_string($email)."', ".
                            "password = md5('".mysql_real_escape_string($_POST['password'])."')");
                        unset($this->specialCache['user'][strtolower($name)]);  //invalidate cache if exists #368

                        // log in
                        $this->SetUser($this->LoadUser($name));
                        if ((isset($_SESSION['go_back'])) && (isset($_POST['do_redirect'])))
                        {
                            $go_back = $_SESSION['go_back'];
                            unset($_SESSION['go_back']);
                            $this->Redirect($go_back);
                        }
                        $_SESSION['usersettings_registered'] = true;
                        $this->Redirect($url.$params);
                }
            }
        }
    }
    // END *** Register ***
    // BEGIN *** Usersettings ***
    elseif  (isset($_POST['action']) && ($_POST['action'] == 'updatepass'))
    {
            $name = trim($_POST['yourname']);
        if (strlen($name) == 0) // empty username  
        {
            $newerror = WIKKA_ERROR_EMPTY_USERNAME;
            $username_temp_highlight = INPUT_ERROR_STYLE;
        }
        elseif (!$this->IsWikiName($name)) // check if name is WikiName style  
        {
            $newerror = ERROR_WIKINAME;
            $username_temp_highlight = INPUT_ERROR_STYLE;
        }
        elseif (!($this->LoadUser($_POST['yourname']))) //check if user exists
        {
            $newerror = ERROR_NONEXISTENT_USERNAME;
            $username_temp_highlight = INPUT_ERROR_STYLE;
        }
        elseif ($existingUser = $this->LoadUser($_POST['yourname']))  // if user name already exists, check password
        {
            // updatepassword
            if ($existingUser['password'] == $_POST['temppassword'])
            {
                $this->SetUser($existingUser, $_POST['remember']);
                $this->Redirect($url);
            }
            else
            {
                $newerror = ERROR_WRONG_PASSWORD;
                $password_temp_highlight = INPUT_ERROR_STYLE;
            }
        }
    }
    // END *** Usersettings ***
    // BEGIN *** Login/Logout ***
    // BEGIN ***  Register ***
    print($this->FormOpen());
    if($register == '3')
    {
?>
<script language="javascript">
<!--
function new_freecap()
{
    // loads new freeCap image
    if(document.getElementById)
    {
        // extract image name from image source (i.e. cut off
        // ?randomness)
        thesrc = document.getElementById("freecap").src;
        thesrc = thesrc.substring(0,thesrc.lastIndexOf(".")+4);
        // add ?(random) to prevent browser/isp caching
        document.getElementById("freecap").src =
thesrc+"?"+Math.round(Math.random()*100000);
    } else {
        alert("Sorry, cannot autoreload freeCap image\nSubmit the form
and a new freeCap will be loaded");
    }
}
//-->
</script>
<?php
    }
?>
    <fieldset id="register" class="usersettings"><legend><?php  echo ($register == '1' || $register == '2') ? LOGIN_REGISTER_LEGEND : LOGIN_LEGEND; ?></legend>
    <input type="hidden" name="action" value="login" />
<?php
    switch (true)
    {
        case (isset($error)):
            echo '<em class="error">'.$error.'</em><br />'."\n";
            break;
        case (isset($success)):
            echo '<em class="success">'.$success.'</em><br />'."\n";
            break;
        case (isset($captcha)):
            echo '<em class="error">'.$this->Format($captcha).'</em>'."\n";
            break;
    }
?>
    <em class="usersettings_info"><?php echo REGISTERED_USER_LOGIN_CAPTION; ?></em>
    <br />
    <label for="name"><?php printf(WIKINAME_LABEL,$wikiname_expanded) ?></label>
    <input id="name" type="text" <?php echo $username_highlight; ?> name="name" size="40" value="<?php echo $this->GetSafeVar('name', 'post'); ?>" />
    <br />
    <label for="password"><?php printf(PASSWORD_LABEL, PASSWORD_MIN_LENGTH) ?></label>
    <input id="password" <?php echo $password_highlight; ?> type="password" name="password" size="40" />
    <br />
<?php
    if (isset($_SESSION['go_back']))
    {
        // FIXME @@@ label for a checkbox should come AFTER it, not before
    ?>
    <label for="do_redirect"><?php printf(USERSETTINGS_REDIRECT_AFTER_LOGIN, $_SESSION['go_back_tag']); ?></label>
    <input type="checkbox" name="do_redirect" id="do_redirect"<?php if (isset($_POST['do_redirect']) || empty($_POST)) echo ' checked="checked"';?> />
    <br />
<?php
    }
?>
    <input id="loginsubmit" type="submit" value="<?php echo LOGIN_BUTTON ?>" size="40" />
    <br /><br />
<?php
    // END *** Login/Logout ***
    $register = $this->GetConfigValue('allow_user_registration');
    if ($register == '1' || $register == '2' || $register == '3')
    {
?>
    <em class="usersettings_info"><?php echo NEW_USER_REGISTER_CAPTION; ?></em>
    <br />
    <label for="confpassword"><?php echo CONFIRM_PASSWORD_LABEL ?></label>
    <input id="confpassword" <?php echo $password_confirm_highlight; ?> type="password" name="confpassword" size="40" />
    <br />
    <label for="email"><?php echo USER_EMAIL_LABEL ?></label>
    <input id="email" type="text" <?php echo $email_highlight; ?> name="email" size="40" value="<?php echo $email; ?>" />
    <br />
<?php
        if($register == '3')
        {
            setcookie("sessname", session_name(), 0, '/');
?>
<?php //           <img src="../freecap/freecap.php" id="freecap"/><br/> ?>
            <div id="captcha">
                <img src="3rdparty/plugins/freecap/freecap.php" id="freecap"/><br/>
                <?php echo CAPTCHA_NO_READ ?><a href="<?php echo $this->Href('', 'UserSettings'); ?>" onClick="this.blur();new_freecap();return false;"> <?php echo CAPTCHA_NO_READ_CLICK_HERE ?>.</a>
            </div>
            <label for="confpassword"><?php echo CAPTCHA_LABEL ?></label>
            <input id="confpassword" name="word" type="text" size="40" />
<?php
        }
        else if ($register == '2')
        {
            $invitation_code_expanded = '<abbr title="'.INVITATION_CODE_LONG.'">'.INVITATION_CODE_SHORT.'</abbr>'
?>
    <label for="invitation_code"><?php printf(INVITATION_CODE_LABEL,$invitation_code_expanded) ?></label>
    <input id="invitation_code" type="text" <?php echo $invitation_code_highlight; ?> size="20" name="invitation_code" />
    <br />
<?php
        }
?>
    <input id="loginsubmit" type="submit" value="<?php echo REGISTER_BUTTON ?>" size="40" />
    <br />
<?php
    }
    echo    '   </fieldset>'."\n";
    print($this->FormClose());
    // END *** Register ***
    print($this->FormOpen());
?>
    <fieldset id="password_forgotten" class="usersettings"><legend><?php echo RETRIEVE_PASSWORD_LEGEND ?></legend>
    <input type="hidden" name="action" value="updatepass" />
<?php  
    if (isset($newerror))
    {
        echo '<em class="error">'.$newerror.'</em><br />'."\n";
    }
    $retrieve_password_link = 'PasswordForgotten';
    $retrieve_password_caption = $this->Format(sprintf(RETRIEVE_PASSWORD_CAPTION,$retrieve_password_link));
?>
    <em class="usersettings_info"><?php echo $retrieve_password_caption ?></em>
    <br />
    <label for="yourname"><?php printf(WIKINAME_LABEL,$wikiname_expanded) ?></label>
    <input id="yourname" type="text" <?php echo $username_temp_highlight; ?> name="yourname" value="<?php echo $this->GetSafeVar('yourname', 'post'); ?>" size="40" />
    <br />
    <label for="temppassword"><?php echo TEMP_PASSWORD_LABEL ?></label>
    <input id="temppassword" type="text" <?php echo $password_temp_highlight; ?> name="temppassword" size="40" />
    <br />
    <input id="temppassloginsubmit" type="submit" value="<?php echo LOGIN_BUTTON ?>" size="40" />
    <br class="clear" />
    </fieldset>
<?php
    print($this->FormClose());
}
?>


Problems


Integration went very straight after the example that comes with the package except for one thing. The FreeCap script assumed a standard php session to use but because Wikka uses a custom session name it missed this. Since this is no link (no href but rather a src where the freecap.php is linked) I couldn't give the name to the script via URL (also it would be visible then) so I put the name in a cookie. This works and I don't think it's insecure for the cookie can't be read by anyone else but the one who placed it. Of course it doesn't work when cookies are off.
If anyone could come up with a better solution that would be great.



Note for Rewrite Users


The image will not show up unless you exclude it in your mod_rewrite settings. For example, I initially had a rewrite rule found elsewhere on this site that didn't exclude rewriting for the actions directory.

RewriteRule ^(css|images|wikiedit2)/(.*)$ $1/$2 [L]


Changing that rule to include the actions directory, like as follows, fixed the problem.

RewriteRule ^(actions|css|images|wikiedit2)/(.*)$ $1/$2 [L]



<IfModule mod_rewrite.c>
RewriteEngine off
</IfModule>


Customization


You can edit the freecap.php to customize the CAPTCHAs, change the hash encryption and type of image format. The script can use a dictionary to read the words from but I prefer random letters although they're hard to read sometimes. The dictionary must NOT be readable on your webspace (check it!) if you use it.

In addition there should be an option for people who can't see the CAPTCHA to register on the site (the FreeCap author suggests to let them register and check those registrations manually but audio CAPTCHA would also be possible).


Any comments are appreciated!


CategoryUserContributions
There are 7 comments on this page. [Show comments]
Valid XHTML :: Valid CSS: :: Powered by WikkaWiki