Extension:RegistrationForm

From MediaWiki.org
Jump to: navigation, search
MediaWiki extensions manual - list
Crystal Clear action run.png
RegistrationForm

Release status: experimental

Implementation User interface
Description Inserts a registration form and manages registration details
Author(s) Dave (ClausekwisTalk)
Last version 0.1.1 (02.05.2010)
MediaWiki 1.15+
PHP 5.2+
License GPLv3
Download see below
0.1.1: Add hash to notification mail
Example HaxoGreen Camp Registration page
Parameters

captcha, emailconfirm

Tags
<registration>
Hooks used
BeforePageDisplay

Check usage (experimental)

Contents

[edit] What can this extension do?

This extension allows you to handle conference and other registrations.

[edit] Todo

  • Internationalization
  • Improve & simplify XML
  • $wgParser->setHook('tag', array( &$object, 'method' ) );
  • Different way of setting maxTickets and Payment Provider Details (We have WAY too many settings)
  • Allow for various payment providers (i.e. outsource IONShop)
  • Set PaymentInfo somewhere else (allowing people to use PayPal, Google Checkout, whatever)

[edit] Usage

On the page you want to have a registration form, insert a <registration/> tag.
You can use both options captcha and emailConfirm. I.e.:

<registration emailConfirm="true" captcha="true"></registration>

You should disable caching for those specific pages you're using registration on.

[edit] Download instructions

Please cut and paste the code found below and place it in $IP/extensions/Registration/Registration.php. Note: $IP stands for the root directory of your MediaWiki installation, the same directory that holds LocalSettings.php.


[edit] Installation

[edit] Dependencies

[edit] Instructions

To install this extension, add the following to LocalSettings.php:

$wgEventName = 'Your Event';
$wgNotifyRecipient = 'your email address';
$wgNotifySubject = 'Email Subject';
$wgRegistrationFile = 'path to your registration.xml file (should be outside DOCROOT)';
include_once( "$IP/extensions/Registration/Registration.php");

[edit] Configuration parameters

  • captcha
  • emailconfirm

[edit] Code

[edit] Registration.php

<?php
/**
 * RegistrationForm.php
 * Written by David Rauber and David Raison
 *
 */
 
if ( !defined( 'MEDIAWIKI' ) )
    die( 'This is a MediaWiki extension, and must be run from within MediaWiki.' );
 
define("REGNAME",  "Registration");
$wgExtensionCredits['parserhook'][] = array(
        'path' => __FILE__,
        'name' => REGNAME,
        'version' => '0.1',
        'author' =>'David Rauber and David Raison', 
        'url' => 'http://www.mediawiki.org/wiki/Extension:RegistrationForm',
        'description' => 'Inserts a registration form into a page'
);
 
$wgAutoloadClasses['Registration'] = dirname(__FILE__) . '/Registration.body.php';
$wgExtensionFunctions[] = "wfRegistrationExtension";
$wgHooks['BeforePageDisplay'][] = 'wfAddFormScript';
 
 
/**
 * Tag extension
 * Could also use object directly:
 * $wgParser->setHook('addscript', array( &$asObj, 'pSet' ) );
 * or $wgHooks['ParserAfterTidy'][] = array( $asObj, 'feedScripts' );   
 * What exactly is the difference between addHook and addFunctionHook?
 */
function wfRegistrationExtension() {
        global $wgParser;
        $wgParser->setHook(REGNAME, "handleRegistration" );
        $wgParser->setHook("seatsleft", "showTicketsLeft");
        return true;
}
 
function showTicketsLeft(){
        $reg = new Registration();
        return($reg->countOpenTickets());
}
 
/**
 * See http://www.mediawiki.org/wiki/Manual:Tag_extensions#Example
 * <tag arg="$arg1">$input</tag>
 * $parser --> http://svn.wikimedia.org/doc/classParser.html
 * */
function handleRegistration($input, $argv, &$parser){
        $reg = new Registration();
        if($_POST['registered'])
                $output = $reg->saveRegistration($_POST['emailconfirm'],$_POST['captcha']);
        elseif($_GET['hash'])
                $output = $reg->confirmEmail($_GET['hash']);
        else $output = $reg->renderForm($argv['emailconfirm'],$argv['captcha']);
        return($output);
}
 
/**
 * BAD!! because not working everywhere! 
 * But using the setHook(), this doesn't seem to work :(
 */
function wfAddFormScript(){
        if (strpos($_SERVER['REQUEST_URI'],'Registration')){
                global $wgOut, $wgScriptPath;
                $wgOut->addScript('<script type="text/javascript" src="'.$wgScriptPath.'/extensions/Registration/formSetup.js"></script>'."\n");
                $wgOut->addInlineScript('addOnloadHook(shirts);');
        }
    return true;
}

[edit] Registration.body.php

<?php
/**
 *
 * RegistrationForm.php
 * Written by David Rauber and David Raison
 */
 
class Registration{
 
        private $_dbName;                       // OUTSOURCE to paymentprovider class
        private $_dbTable;              // OUTSOURCE to paymentprovider class
        private $_header;       // headers for emails
        private $_notifySubject;
        private $_notifyRecipient;
        private $_eventName;
        private $_maxTickets;
        private $_regFile;
        private $_paymentSiteURL;
        private $_paymentSiteName;
 
        public function __construct(){
                global $IP, $wgEventName, $wgNotifySubject, $wgNotifyRecipient;
                global $wgRegistrationFile, $wgMaxTickets, $wgBillingDB, $wgBillingTable;
                global $wgPaymentSiteURL, $wgPaymentSiteName;
                $this->_recaptchaLib = $IP.'/extensions/recaptcha/recaptchalib.php';    // how to do if not having recaptcha?
                $this->_notifySubject = $wgNotifySubject;
                $this->_notifyRecipient = $wgNotifyRecipient;
                $this->_maxTickets = $wgMaxTickets;
                $this->_eventName = $wgEventName;
                $this->_regFile = $wgRegistrationFile;
                $this->_dbName = $wgBillingDB;
                $this->_dbTable = $wgBillingTable;
                $this->_header = 'From: '.$this->_notifyRecipient . "\r\n" .
                        'Reply-To: '.$this->_notifyRecipient . "\r\n" .
                        'X-Mailer: PHP/' . phpversion();
 
                //  add to localsettings?
                $this->_paymentSiteURL = $wgPaymentSiteURL;
                $this->_paymentSiteName = $wgPaymentSiteName;
        }
 
        public function countOpenTickets(){
                $xml = simplexml_load_file($this->_regFile);
                $left = $this->_maxTickets - sizeof($xml);
                return $left;
        }
 
        /**
         * Insert payment info into dbcetrel.tblion_bill
         * idbill ass int, billdate ass date, amount ass decimal 10,2 an sin euro'en dei aner 3 sin varchar
         * 
         * TODO: This function is specific to our payment provider, outsource it to a helping library or better yet use an encrypted API call
         * 
         * */
        private function _insertPaymentDetails($amount,$email,$hash){
                global $wgDBuser, $wgDBpassword, $wgDBserver;
                $dbCetrel = new mysqli($wgDBserver,$wgDBuser,$wgDBpassword,$this->_dbName);
                $billDate = date("Y-m-d");
                $query = "INSERT INTO ".$this->_dbTable."(dthash,dtlabel,dtbilldate,dtcustomer,dtamount) VALUES ('$hash','Camp fees $email','$billDate','HaxoGreen','$amount');";
                if($dbCetrel->query($query))
                        return $dbCetrel->insert_id;
                else throw new Exception("Couldn't add information to ION Bill DB: ".$dbCetrel->error);
        }
 
        /**
         * TODO: Outsource payment info into config file?!
         */
        private function _displayPaymentInfo($amount,$email,$hash){
                if($this->_insertPaymentDetails($amount,$email,$hash)){
                        $info = '<p>Thank you for registering for '.$this->_eventName.'!</p>'
                                .'[...payment system + hash...]'
                                .'<p>Note that your registration is only final once your payment has been received. A copy of this message has been sent to your email address.</p>'
                                .'<p>Mail <a href="mailto:'.$this->_notifyRecipient.'">the Organisers</a> if you require assistance.</p>';
                        $mailInfo = "Thank you for registering for ".$this->_eventName."!\r\n\r\n"
                                .'[...payment system + hash...]'
                                ."Note that your registration is only final once your payment has been received.\r\n"
                                ."Reply to this eMail if you require assistance.";
                        $this->_mailPaymentInfo($mailInfo,$email);
                } else $info = "<p>There was a problem creating your payment information. Please notify ".$this->_notifyRecipient."</p>";
                return $info;
        }
 
        private function _mailPaymentInfo($msg,$address){
                mail($address,$this->_eventName.' - your payment information.',$msg,$this->_header);
        }
 
        private function _sendConfirmationMail($address,$hash){
                global $wgTitle;
                //generate hash and return it to the calling function, then send it
                $confirmText = "Thank you for registering for HaxoGreen 2010.\r\nPlease click the following link to confirm your email address:\r\n %s";
                $link = $wgTitle->getFullUrl().'?hash='.$hash;
                $finalText = sprintf($confirmText,$link);
                return (mail($address,$this->_eventName.' Email Confirmation',$finalText,$this->_header)) ? true : false;
        }
 
        /** 
         * load email address and price info and show payment information & send out notification
         * verify hash & change <confirmed> field
         * NOTE: using a single hash! to be able to find payment info!
         */ 
        public function confirmEmail($mailHash){
                $mail = "%s confirmed camp registration.";
                $xml = simplexml_load_file($this->_regFile);
                $element = 0;
                foreach($xml->registration as $reg){
                        if((string) $reg->hash == $mailHash){
                                // fetch data for payment information
                                $address = (string) $reg->email;
                                $price = (string) $reg->price;
 
                                if((string) $reg->confirmed != 'true') {        // only send email once
                                        $mail = "%s confirmed camp registration. Hash: %s";
                                        $msg = sprintf($mail,$address,$mailHash);
                                        $this->_notifyOfRegistration($msg);
                                }
                                // set email to confirmed state and write back to file
                                $xml->registration[$element]->confirmed = "true";
                                $xml->asXML($this->_regFile);
                                return($this->_displayPaymentInfo($price,$address,$mailHash));  // returns string to be displayed
                        }
                        $element++;     // got any better idea?
                } // getting here only if the hash wasn't found.
                $errMsg = '<p>Sorry, the hash you submitted doesn\'t exist.<br/>If you feel this to be in error,'
                        .'please contact <a href="mailto:'.$this->_notifyRecipient.'?subject=Inexisting hash">the Organisers</a>';
                return($errMsg);
        }
 
        public function saveRegistration($emailConfirm,$captcha){
                if($captcha == 'true'){
                        require_once($this->_recaptchaLib);
                        global $recaptcha_private_key;
                        $resp = recaptcha_check_answer($recaptcha_private_key,
                                $_SERVER["REMOTE_ADDR"],$_POST["recaptcha_challenge_field"],
                                $_POST["recaptcha_response_field"]);
                }
                // check captcha
                if($captcha == 'true' && !$resp->is_valid){
                        return("<p>Sorry, the captcha you typed was wrong, please try again.</p>".$this->renderForm($emailConfirm,$captcha));
                } else {
                        foreach($_POST as $arg => $value)
                                ${$arg} = htmlspecialchars($value);     // gives $firstname, $lastname, $email, $amount, $comment
 
                        if(!(preg_match("/^[0-9]{1}$/",$amount) && preg_match("/^([^-><&>!]){1,20}$/",$firstname) && 
                        preg_match("/^([^-><&>!]){1,20}$/",$lastname) && 
                        preg_match("/^([a-zA-Z0-9_\-\.]{1,50})@([a-zA-Z0-9_\-\.]{1,50})$/",$email)))
                                return('<p>Sorry, the data you entered didn\'t pass our checks. Please try again.</p><p>'.$this->renderForm($emailConfirm,$captcha));
 
                        // passed regexp, continue
                        $price = $amount * 25 + 26;
 
                        // prepare mail
                        $mail = "Registration OK! \r\n";
                        $mail .= "Firstname: $firstname \r\n";
                        $mail .= "Lastname: $lastname \r\n";
                        $mail .= "Email: $email \r\n";
                        $mail .= "Price: $price EUR \r\n";
 
                        // prepare xml  (wouldn't it be easier to just use DOM(SimpleXML from the beginning?)
                        $xml  = "\n<registration>\n";
                        $xml .= "<date>".date("r")."</date>\n";
                        $xml .= "<firstname>$firstname</firstname>\n";
                        $xml .= "<lastname>$lastname</lastname>\n";
                        $xml .= "<email>$email</email>\n";
                        $xml .= "<price>$price</price>\n";
 
                        // adding shirt data
                        $mail .= "T-Shirts:\r\n";
                        $xml .= "<tshirts>\n";
 
                        // add each shirt separately (but combine them if they're the same)
                        $order = array();
                        for($i = 1; $i <= $amount; $i++){
                                $thisSize = $_POST["size-$i"];
                                if(!preg_match("/^((S)|(M)|(L)|(XL)|(XXL))$/",$thisSize)) 
                                        $thisSize = "?";
                                $order[$thisSize] += 1;
                        }
 
                        foreach($order as $size => $num){
                                $mail .= "$num x $size \r\n";
                                $xml  .= "<shirt amount=\"$num\" size=\"$size\"/>\n";            
                        }
                        $xml .= "</tshirts>";
 
                        // comments?
                        if(strlen($comment)>0) {
                                $comment = htmlentities($comment);
                                $mail .= "Comment: $comment \r\n";
                                $xml  .= "<comment>$comment</comment>";         
                        } else $xml  .= "<comment/>";
 
                        // force utf8 encoding  
                        if(!(mb_detect_encoding($xml)=='UTF-8') || !(mb_check_encoding($xml,"UTF-8")))
                                $xml = utf8_encode($xml);
 
                        // send a confirmation Mail (generate the hash here to have it available everywhere)
                        $hash = 'hgcc'.substr(md5(date(r).mt_rand(0,99)),0,16);
                        if($emailConfirm == "true"){
                                $this->_sendConfirmationMail($email,$hash);
                        } else $this->_notifyOfRegistration($mail);
 
                        $xml .= "<hash>$hash</hash>";
                        $xml .= "<confirmed/>";
                        $xml .= "</registration>";
 
                        $doc = new DOMDocument();
                        $doc->preserveWhiteSpace = false;
                        $doc->load($this->_regFile);
 
                        // create DOMDocument from string in $xml
                        $doc2 = new DOMDocument();
                        //$doc2->formatOutput = true;
                        $doc2->preserveWhiteSpace = false;
                        $doc2->loadXML($xml);
 
                        // integrating $doc2 into $doc
                        $doc->documentElement->appendChild($doc->importNode($doc2->documentElement,true));
                        $doc->formatOutput = true;
                        $doc->saveXML();
                        $doc->save($this->_regFile);
 
                        $response = "<p>Thank you for your registration!</p>";
                        if($emailConfirm == 'true') $response .= "<p>You will receive a confirmation email shortly.</p>";
                        else $respone .= $this->_displayPaymentInfo($price,$email,$hash);
                        return $response;
                }       // end valid captcha
        }
 
        private function _notifyOfRegistration($message){
                return(mail($this->_notifyRecipient,$this->_notifySubject,$message));
        }
 
        public function renderForm($emailConfirm=false,$captcha=false){ //$input, $argv, &$parser
                global $wgTitle, $recaptcha_public_key;
                if ($captcha == 'true') require_once($this->_recaptchaLib);
                if ( !($wgTitle->isProtected ('edit')) )
                        return ( REGNAME . " is only active on protected pages." );
                else {
                        $form ='<p><form action="'. $wgTitle->getFullURL() . '" method="post" onsubmit="return checkForm">'
                                .'<input type="hidden" name="registered" value="true">';
                        if ($emailConfirm == 'true') $form .= '<input type="hidden" name="emailconfirm" value="true">';
                        if ($captcha == 'true') $form .= '<input type="hidden" name="captcha" value="true">';
                        $form .= '<table>'
                                .'<tr><td><label for="firstname">First name:</label></td><td><input type="text" name="firstname" /></td></tr>'
                                .'<tr><td><label for="lastname">Last name:</label></td><td><input type="text" name="lastname" /></td></tr>'
                                .'<tr><td><label for="email">Email</label></td><td><input type="text" name="email" /></td></tr>'
                                .'<tr><td><label for="amount">T-Shirts Amount</label></td><td><input size="1" maxlength="1" value="1" type="text" id="amount" name="amount" onkeyup="shirts()"/></td></tr>'
                                .'<tr><td colspan="2"><div id="sizes"></div></td></tr>'
                                .'<tr><td/><td><div style="margin:10px 0 10px 0;font-weight:bold;" id="price"></div></td></tr>'
                                .'<tr><td><label for="comment">Comment:</label></td><td><textarea name="comment" rows="12" cols="50"/></textarea></td></tr>';
                        if ($captcha == 'true') $form .= '<tr><td>Please enter the captcha</td><td>'.recaptcha_get_html($recaptcha_public_key).'</td></tr>';
                        $form .= '<tr><td/><td><input type="submit" name="Submit" value="Proceed" /></td></tr>'
                                .'</table></form></p>';
                        return($form);
                }
        }
}

[edit] formSetup.js

// Todo: turn into OO code
 
function checkForm() {
        var price = 26 + document.getElementById("amount").value * 25;
        return confirm("Submit? Price: " + price +" EUR");
}
 
// called by hookEvent
function shirts() {
        var amount = document.getElementById("amount").value;
 
        var pattern = /^[0-9]{1}$/;
        if (!pattern.test(amount)) {
                var pattern2 = /^$/;
            if (pattern2.test(amount)) {
                //OK! BACKSPACE USAGE!
                        amount = 0;
                } else {
                        //NO ALERTS!
                document.getElementById("amount").value = 1;
                amount = 1;
                }
        }
 
        var sizes = document.getElementById("sizes");
        sizes.innerHTML = "";
        var html = "";
        for(var i = 1; i<= amount; i++) {
           html += '<label for="size-'+ i +'">T-Shirt '+ i +' Size</label>&nbsp;';
           html += '<select name="size-'+ i +'">';
           html += ' <option value="S">S</option>';
           html += ' <option value="M">M</option>';
           html += ' <option value="L">L</option>';
           html += ' <option value="XL">XL</option>';
           html += ' <option value="XXL">XXL</option>';
                html += '</select><br/>';
        }
        sizes.innerHTML = html;
 
        var price = 26 + amount * 25;
 
        var priceDiv = document.getElementById("price");
        priceDiv.innerHTML = "";
        priceDiv.innerHTML = "Price: " + price + " &euro; <br/>";
 
}
Personal tools
Namespaces

Variants
Actions
Navigation
Support
Download
Development
Communication
Print/export
Toolbox