From OrganicDesign Wiki
<?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( "\"", """, $part2 );
$part2 = str_replace( "!", "!", $part2 );
$part2 = str_replace( "'", "'", $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);
}