Extension:RegistrationForm

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

Release status: unmaintained

Implementation User interface
Description Inserts a registration form and manages registration details
Author(s) Dave (Clausekwistalk)
Latest 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

Translate the RegistrationForm extension if it is available at translatewiki.net

Check usage and version matrix; code metrics

What can this extension do?[edit | edit source]

This extension allows you to handle conference and other registrations.

Todo[edit | edit source]

  • 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)

Usage[edit | edit source]

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.

Download instructions[edit | edit source]

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.


Installation[edit | edit source]

Dependencies[edit | edit source]

Instructions[edit | edit source]

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");

Configuration parameters[edit | edit source]

  • captcha
  • emailconfirm

Code[edit | edit source]

Registration.php[edit | edit source]

<?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.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;
}

Registration.body.php[edit | edit source]

<?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);
		}
	}
}

formSetup.js[edit | edit source]

// 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/>";
 
}