Personal tools
Make a donation
$ 




Extension:PayPal.php

From OrganicDesign Wiki

Jump to: navigation, search
<?php
/* IpbWiki Paypal WikiMedia extension
** IpbWiki (c) 2006
** Installation Instructions: http://www.ipbwiki.com/IpbWiki_Paypal_Extension
 
Changes by User:Nad (1.0.3 - 2007-12-04):
  - Added IPN support (which can also work with AJAX updating if $wgUseAjax set)
  - Change button definitions to work from LocalSettings rather than code hack
  - Output warnings into sitenotice in error box
  - Log IPN post errors to log file
*/
 
if (!defined('MEDIAWIKI')) die('Not an entry point.');
 
define('PAYPAL_VERSION','1.0.3, 2007-12-13');
 
$wgPayPalIPN      = false;           # Use IPN notification
$wgPayPalTest     = false;           # Set to the email address of the test merchant account if testing IPN in the PayPal sandbox site
$wgPayPalPollms   = 5000;            # Number of milliseconds to wait between each call to AJAX updater
$wgPayPalUpdating = 'updating...';   # Message to add to form totals text (in $3) before IPN post arrives
 
$wgExtensionCredits['parserhook'][] = array(
	'name'        => 'IpbWiki PayPal',
	'version'     => PAYPAL_VERSION,
	'author'      => 'Peter De Decker, (IPN support by [http://www.mediawiki.org/wiki/User:Nad User:Nad])',
	'url'         => 'http://www.ipbwiki.com/IpbWiki_Paypal_Extension',
	'description' => 'Mediawiki PayPal Extension'
);
 
$wgPayPalLog            = str_replace('.php','.log',__FILE__);
$ipbwiki_paypal         = array();
$wgExtensionFunctions[] = "wfPayPalExtension";
$wgAjaxExportList[]     = 'wfPayPalAjaxUpdater';
 
# If returning from merchant, get item number (form id)
$wgPayPalAfterPurchase = (isset($_POST['item_number']) && !($_REQUEST['title'] == '__ipn_post')) ? $_POST['item_number'] : false;
 
# Set up the extension
function wfPayPalExtension() {
	global $wgParser, $ipbwiki_paypal, $wgPayPalIPN;
 
	# Register the extension with the WikiText parser
	$wgParser->setHook( "paypal", "wfPayPalRenderButton" );
 
	# Set button 1 to a default value if not defined
	if (!isset($ipbwiki_paypal[1]))
		$ipbwiki_paypal[1] = '<form action="https://www.paypal.com/cgi-bin/webscr" method="post"><input type="hidden" name="cmd" value="_xclick"><input type="hidden" name="business" value="ipbwiki@gmail.com"><input type="hidden" name="item_name" value="IpbWiki PayPal"><input type="hidden" name="no_shipping" value="1"><input type="hidden" name="cn" value="Optional Comments"><input type="hidden" name="currency_code" value="EUR"><input type="hidden" name="tax" value="0"><input type="hidden" name="bn" value="PP-DonationsBF"><input type="image" src="https://www.paypal.com/en_US/i/btn/x-click-but04.gif" border="0" name="submit" alt="Make payments with PayPal - it\'s fast, free and secure!"></form>';
 
	# IPN
	if ($wgPayPalIPN) {
 
		global $wgHooks, $wgPayPalTest, $wgPayPalIPNTable, $wgUseAjax, $wgPayPalAfterPurchase;
		$db = &wfGetDB(DB_MASTER);
 
		# If using AJAX add JS updater after page rendered
		if ($wgUseAjax && $wgPayPalAfterPurchase) $wgHooks['OutputPageBeforeHTML'][] = 'wfPayPalAddAjaxUpdater';
 
		# Create the IPN database table if it doesn't exist
		$wgPayPalIPNTable = $db->tableName('PayPalIPN');
		if (!$db->tableExists($wgPayPalIPNTable)) {
			$query = "CREATE TABLE $wgPayPalIPNTable (ipn_id VARCHAR(32), ipn_date TINYTEXT, ipn_item INTEGER NOT NULL, ipn_from TINYTEXT, ipn_amount NUMERIC, ipn_status TINYTEXT, PRIMARY KEY (ipn_id));";
			$result = $db->query($query);
			$db->freeResult($result);
		}
 
		# If the table couldn't be created, disable IPN and add error to site notice
		if (!$db->tableExists($wgPayPalIPNTable)) {
			global $wgSiteNotice;
			$wgPayPalIPN = false;
			$wgSiteNotice = "<div class=\"errorbox\"><b>PayPal IPN Error! Could not create IPN database table, IPN functionality is disabled.</b><br>Please ensure the wiki database user has CREATE permission, or add the table manually with the following query:<br><tt>$query</tt></div>";
		}
 
		# If this is an IPN post from paypal, validate and update the DB if verified
		if ($_REQUEST['title'] == '__ipn_post') {
 
			# Disable normal wiki rendering and output
			global $wgOut;
			$wgOut->disable();
			wfResetOutputBuffers();
 
			# Read the relavent info from posted data
			$id     = $_POST['txn_id'];
			$item   = isset($_POST['item_number']) ? $_POST['item_number'] : 1;
			$date   = $_POST['payment_date'];
			$from   = $_POST['payer_email'];
			$cur    = $_POST['mc_currency'];
			$amount = $_POST['payment_gross'];
			$status = $_POST['payment_status'];
 
			# Post variables back to PayPal (with cmd appended) system (or sandbox if testing) to validate
			$log = "Transaction $id ($cur$amt) from $from";
			$req = 'cmd=_notify-validate';
			foreach ($_POST as $k => $v) $req .= "&$k=".urlencode(stripslashes($v));
			$header .= "POST /cgi-bin/webscr HTTP/1.0\r\n";
			$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
			$header .= "Content-Length: ".strlen($req)."\r\n\r\n";
			$domain = $wgPayPalTest ? 'www.sandbox.paypal.com' : 'www.paypal.com';
			$fp = fsockopen ($domain, 80, $errno, $errstr, 30);
			if (!$fp) wfPayPalLog("$log: Could not open HTTP socket to $domain!");
			else {
				fputs($fp, $header.$req);
				while (!feof($fp)) $res = fgets($fp, 1024);
				if ($res === 'VERIFIED') wfPayPalUpdateItem($id,$date,$item,$from,$amount,$status); # Update DB
				else wfPayPalLog("$log returned $res, status: $status");
			}
			fclose ($fp);
		}
	}
}
 
# The callback function for converting the input text to HTML output
function wfPayPalRenderButton( $input, $argv ) {
	global $ipbwiki_paypal, $wgAuth, $wgSiteNotice, $wgPayPalIPN;
	$error = '<div class="errorbox">warning, specified paypal button not found, defaulting to button 1</div>';
	$pos_space = strpos($input,' ');
	if (!$pos_space) {
		if (is_numeric($input)) {  // format <paypal>number</paypal>
			$part1 = $input;
			$part2 = '';
			if (!$ipbwiki_paypal[$part1]) {
				$wgSiteNotice .= $error;
				$part1 = 1;
				$part2 = $input;
			}
		} else {				   // format <paypal>text</paypal> & format <paypal></paypal>
			$part1 = 1;
			$part2 = $input;
		}
	} else {					   // format <paypal>number text</paypal>
		$part1 = substr($input,0,$pos_space);
		$part2 = substr($input,$pos_space+1);
		if (is_numeric($part1)) {
			if (!$ipbwiki_paypal[$part1]) {
				$wgSiteNotice .= $error;
				$part1 = 1;
			}
		} else {				 // format <paypal>text</paypal>
			$part1 = 1;
			$part2 = $input;
		}
	}
	$form = $ipbwiki_paypal[$part1];
	// if the ipbwiki interface is available, then use the clean function which is defined there, otherwise just clean the necessities...
	if (class_exists ('ipbwiki')) {
		$input = $wgAuth->ipbwiki->ipbwiki->clean_value ($part2);
	} else {
		$part2 = str_replace( ">",  ">",    $part2 );
		$part2 = str_replace( "<",  "<",    $part2 );
		$part2 = str_replace( "\"", "&quot;",  $part2 );
		$part2 = str_replace( "!",  "&#33;",   $part2 );
		$part2 = str_replace( "'",  "&#39;",   $part2 );
		$input = $part2;
	}
 
	# IPN
	if ($wgPayPalIPN) {
		global $wgParser, $wgServer, $wgScript, $wgTitle, $wgPayPalIPNTable, $wgPayPalTest, $wgPayPalUpdating, $wgPayPalAfterPurchase;
		$wgParser->disableCache();
 
		# Set item_number to this forms id
		$form = str_replace('</form>',"<input type=\"hidden\" name=\"item_number\" value=\"$part1\" /></form>",$form);
 
		# Add a notify_url value in the form to tell paypal to post account changes to this script
		# todo: append shared secret to notify url
		$form = str_replace('</form>',"<input type=\"hidden\" name=\"notify_url\" value=\"$wgServer$wgScript/__ipn_post\" /></form>",$form);
 
		# Add the receiver_email and change the URL's to sandbox site if testing in the paypal sandbox
		if ($wgPayPalTest) {
			$form = str_replace('www.paypal.com','www.sandbox.paypal.com',$form);
			$form = str_replace('business','receiver_email',$form);
			$form = str_replace('</form>',"<input type=\"hidden\" name=\"business\" value=\"$wgPayPalTest\" /></form>",$form);
		}
 
		# Set return URL to this page and method to GET (and include the item id of the submitted form)
		$url = $wgTitle->getFullUrl();
		$form = str_replace('</form>',"<input type=\"hidden\" name=\"return\" value=\"$url\" /></form>",$form);
		#$form = str_replace('</form>',"<input type=\"hidden\" name=\"rm\" value=\"1\" /></form>",$form);
 
		# Get the current totals for this form from DB
		wfPayPalGetTotals($part1,$total_donated,$total_donations);
		$total_donated = number_format($total_donated,2);
 
		# If returning from paypal purchase which used this form, wrap totals in spans with id's so they can be updated by AJAX
		if ($wgPayPalAfterPurchase == $part1) {
			$total_donated   = "<span id=\"paypal_donated\">$total_donated</span>";
			$total_donations = "<span id=\"paypal_donations\">$total_donations</span>";
			$total_updating  = "<span id=\"paypal_updating\">$wgPayPalUpdating</span>";
			$input = wfMsgReplaceArgs($input,array($total_donated,$total_donations,$total_updating));
		}
 
		# Replace $1, $2 and $3 in the text with the total amount donated and the total number of donations
		# Don't display any text if no donations
		$input = $total_donations ? wfMsgReplaceArgs($input,array($total_donated,$total_donations,'')) : '';
	}
 
	$output = "<table border='0' cellpadding='0' cellspacing='0'><tr><td>$form</td><td valign='center'>$input</td></tr></table>";
	return $output;
}
 
# Obtain the amount donated and total number of donations for an item (form number) from the DB
# - must sum all completed transactions associated with the passed item number
function wfPayPalGetTotals($item,&$total_donated,&$total_donations) {
	global $wgPayPalIPNTable;
	$db = &wfGetDB(DB_SLAVE);
	$result = $db->query("SELECT ipn_amount FROM $wgPayPalIPNTable WHERE ipn_item = $item AND ipn_status = 'Completed'");
	if ($result instanceof ResultWrapper) $result = $result->result;
	$total_donated = $total_donations = 0;
	while ($row = $db->fetchRow($result)) {
		$total_donations++;
		$total_donated += $row[0];
	}
	$db->freeResult($result);
}
 
# Update a transaction (id) with a new status or insert a new transaction
# - if transaction id already exists, only the date and status are updated
function wfPayPalUpdateItem($id,$date,$item,$from,$amount,$status) {
	global $wgPayPalIPNTable;
	$db = &wfGetDB(DB_MASTER);
	$result = $db->query("SELECT ipn_status FROM $wgPayPalIPNTable WHERE ipn_id = '$id'");
	if ($result instanceof ResultWrapper) $result = $result->result;
	$exists = $db->fetchRow($result);
	$db->freeResult($result);	
	if ($exists) $db->update($wgPayPalIPNTable,array('ipn_date' => $date, 'ipn_status' => $status),array("ipn_id = '$id'"));
	else $db->insert($wgPayPalIPNTable,array(
		'ipn_id'     => $id,
		'ipn_date'   => $date,
		'ipn_item'   => $item,
		'ipn_from'   => $from,
		'ipn_amount' => $amount,
		'ipn_status' => $status
	));
}
 
# Adds a JS function to poll for changes to the forms totals after returning from purchase
function wfPayPalAddAjaxUpdater(&$out) {
	global $wgJsMimeType,$wgPayPalPollms;
	$id = $_POST['txn_id'];
	$out->addScript("<script type='$wgJsMimeType'>
		var poll = setInterval('paypalPollUpdater()',$wgPayPalPollms);
		function paypalResponseHandler(xmlhttp) {
			var totals = new Array();
			totals = xmlhttp.responseText.split('|');
			if (totals[2] == 'Completed') clearTimeout(poll);
			document.getElementById('paypal_donated').innerHTML = totals[0];
			document.getElementById('paypal_donations').innerHTML = totals[1];
			document.getElementById('paypal_updating').innerHTML = totals[2];
		}
		function paypalPollUpdater() {
			sajax_do_call('wfPayPalAjaxUpdater',['$id'],paypalResponseHandler);
		}</script>");
	return true;
}
 
# The function called by the AJAX dispatcher to return the current totals of the form used to make the purchase
function wfPayPalAjaxUpdater($id) {
	global $wgPayPalIPNTable;
	$db = &wfGetDB(DB_SLAVE);
	$result = $db->query("SELECT ipn_status,ipn_item FROM $wgPayPalIPNTable WHERE ipn_id = '$id'");
	if ($result instanceof ResultWrapper) $result = $result->result;
	$total_donated = $total_donations = 0;
	$status = "Transaction $id not found!";
	if ($row = $db->fetchRow($result)) {
		list($status,$item) = $row;
		wfPayPalGetTotals($item,$total_donated,$total_donations);
		}
	$total_donated = number_format($total_donated,2);
	return "$total_donated|$total_donations|$status";
}
 
# A log file for recording IPN error conditions
function wfPayPalLog($text) {
	global $wgPayPalLog;
	$ts = date("Y-m-d H:i:s");
	@file_put_contents($wgPayPalLog,"$ts $text\n",FILE_APPEND);
}

The GNU Project Debian Linux Ubuntu Linux Wikipedia Affiliate Button MediaWiki

Content under the www.organicdesign.co.nz domain is available under the Creative Commons Attribution-ShareAlike License