MediaWiki/Special/SiteSubscribe/code

About
NOTES:
 * The reference to "researchwikis.com" must be replaced with a variable which refers to the current site. This is probably easy, but I don't have time just at the moment to look this up and test it. --Woozle 07:47, 3 April 2009 (EDT)
 * There is a lot of logging code in the IPN handler which should either be removed or made so it can be turned on/off with a single flag.

Code
<?php /* NAME: Special:SiteSubscribe PURPOSE: Special page for managing site-access subscriptions activated in real-time AUTHOR: Woozle (Nick) Staddon VERSION: 2009-01-17 0.0 (Wzl) Writing started (again, after UPS failure) 2009-02-12 0.0 (Wzl) changed name from Special:PayPal to Special:SiteSubscribe 2009-02-28 0.1 (Wzl) everything working? payments logged, test status logged, user status checked on non-cached pages 2009-03-04 0.2 (Wzl) swap out $wgParser so specialpage can be transcluded 2009-04-03 0.3 (Wzl) additional fields in subscriber display: real name, email $wgSpecialPages['SiteSubscribe'] = 'SpecialSiteSubscribe';	# Let MediaWiki know about the special page. $wgHooks['ArticleFromTitle'][] = 'wfSubscriberCheck'; $wgExtensionCredits['other'][] = array(       'name' => 'Special:SiteSubscribe',	'url' => 'http://htyp.org/MediaWiki/Special/SiteSubscribe',        'description' => 'special page for site subscription management',        'author' => 'Woozle (Nick) Staddon',	'version' => '0.3 2009-04-03 alpha' );

// add a logging type define('ksSSLogType','subscribe'); // table names define('ksTbl_Subscr','ssub_subscrs'); define('ksTbl_Paymts','ssub_paymts'); define('ksTbl_Posting','ssub_post'); define('ksTbl_PostLine','ssub_post_line'); // formats define('ksFmt_DateSQL','Y-m-d'); define('ksFmt_DateShow','Y-m-d'); define('ksFmt_TimeSQL','Y-m-d G:i:s');		// date format for SQL // group strings define('ksPerm_SubscrUser','viewpremium');	// the permission subscribers have define('ksPerm_SubscrEdit','editpremium');	// the permission premium-page editors have define('ksPerm_SubscrAdmin','editsubscriptions');	// the permission subscription editors have define('ksGrp_SubscrUser','subscriber');	// the name for the group subscribers belong to // options define('kfDoSubscribeSandbox',FALSE);		// set to TRUE to subscribe from sandbox IPNs $wgGroupPermissions[ksGrp_SubscrUser]	[ksPerm_SubscrUser]	= true;		// a new default permission

function wfSpecialSiteSubscribe { // This registers the page's class. I think. global $wgRequest;

$app = new SpecialSiteSubscribe($wgRequest);

//	$app->doMain; }

function wfSubscriberCheck(&$title, &$article) { global $wgUser;

//LogSession; //LogText("\n== Checking subscriber status - user ".$wgUser->getName.' ('.$wgUser->getID.') page '.$title->getFullText);

$objUser = new SiteSubscriber; $objUser->Init_fromMWObj($wgUser); $objUser->SynchStatus; //LogText("\n== Status synch complete"); }

require_once( $wgScriptPath.'includes/SpecialPage.php' ); class SpecialSiteSubscribe extends SpecialPage { //require_once( $wgScriptPath.'includes/QueryPage.php' ); //class SpecialSiteSubscribe extends QueryPage { /*========== STATIC private static $vDB; private static $vLog;

public static function DB { if (!is_object(self::$vDB)) { self::$vDB = wfGetDB( DB_MASTER ); }	return self::$vDB; } public static function Logger { if (!is_object(self::$vLog)) { self::$vLog = new LogPage(ksSSLogType,FALSE); }	return self::$vLog; } public static function DoLog($iAction,$iNotes,$iParams=array) { global $wgTitle;

self::Logger->addEntry( $iAction, $wgTitle, $iNotes, $iParams); }

/*========== DYNAMIC

public function __construct { global $wgMessageCache; global $wgLogTypes, $wgLogNames;

parent::__construct( 'SiteSubscribe' ); $wgMessageCache->addMessage('sitesubscribe', 'Site subscriptions');	// sets description on Special:SpecialPages $this->includable( false );

// logging $wgLogTypes[] = ksSSLogType; $wgLogNames[ksSSLogType] = 'subscribe-log-page'; //$messages['en'] = array('subscribe-log-page' => 'Subscriber log'); $wgMessageCache->addMessage('subscribe-log-page','Subscriber log');

//       $wgMessageCache->addMessage('logpost', 'Log POSTed data'); } public function execute( $par ) { $this->doMain($par); } public function includable( $x = NULL ) { return TRUE; } private function HandleLoginForm($iRtnPage) { global $wgParser, $wgRequest, $wgTitle;

$objTitleRtn = $this->getTitle('pay/'.$iRtnPage);	// return to payment form with return to original page $strPage = $objTitleRtn->getPrefixedText;

$objParserMain = $wgParser; $wgParser = new Parser;

//*	$objLogin = new LoginForm($wgRequest); $objLogin->mType = 'signup'; // set return page for account-creation form // return page must be purged so that it will change according to user's new status $objLogin->action = $wgTitle->getFullURL; if ($strPage != '') { $objLogin->mReturnTo = $strPage; }	$objLogin->execute; /**/

/*	$strDoCreate = $wgRequest->getVal('wpCreateaccount'); if ($strDoCreate != '') { $wgOut->addWikiText('['.$strDoCreate.'] Creating account...'); $objLogin = new LoginForm($wgRequest); $strMsg = $objLogin->addNewAccount; $wgOut->addWikiText($strMsg,TRUE); } else { $template = new UsercreateTemplate;

//$q = 'action=submitlogin&type=signup'; //$linkq = 'type=login'; $q = ''; $linkq = ''; $linkmsg = 'gotaccount';

if (!is_object($objTitle)) { $objTitle = Title::newFromText($strPage); }

$link = 'getLocalUrl( $linkq ) ) . '">'; $link .= wfMsgHtml( $linkmsg . 'link' ); $link .= ''; $template->set( 'link', wfMsgHtml( $linkmsg, $link ) );

$template->set( 'header', '' );

$template->set( 'name', $wgRequest->getVal('wpName') ); $template->set( 'password', $wgRequest->getVal('wpPassword') ); $template->set( 'retype', $wgRequest->getVal('wpRetype') ); $template->set( 'email', $wgRequest->getVal('wpEmail') ); $template->set( 'realname', $wgRequest->getVal('wpRealName') ); $template->set( 'domain', $wgRequest->getVal('domain') ); $template->set( 'usedomain', FALSE );

$msg = ''; $msgtype = 'error';

$template->set( 'action', $objTitle->getLocalUrl( $q ) ); $template->set( 'message', $msg ); $template->set( 'messagetype', $msgtype ); $template->set( 'createemail', $wgEnableEmail && $wgUser->isLoggedIn ); $template->set( 'userealname', $wgAllowRealName ); $template->set( 'useemail', $wgEnableEmail ); $template->set( 'canreset', $wgAuth->allowPasswordChange ); $template->set( 'remember', $wgUser->getOption( 'rememberpassword' ) or $wgRequest->getVal('wpRemember') );

$wgOut->addTemplate( $template );

$wgParser = $objParserMain; } }  private function HandlePayButton($iRtnPage) { global $wgSS_PayPalButtonID; global $wgUser, $wgOut;

$idUser = $wgUser->getID;

$strBtn = ''; $strBtn .= ''; $strBtn .= ''; // this would be a better way to pass parameters back, but I don't think it's currently being used: $strBtn .= ''; $strBtn .= ''; $strBtn .= ''; $strBtn .= ''; $strBtn .= ''; $strBtn .= ' ';

$wgOut->addHTML($strBtn); } public function doMain( $par ) { global $wgOut, $wgUser;

$intPos = strpos($par, '/'); if ($intPos === FALSE) { $strType = $par; $strPage = NULL; } else { $strType = substr($par,0,$intPos); $strPage = substr($par,$intPos+1); }	switch ($strType) { case 'login': $this->HandleLoginForm($strPage); break; case 'pay': $this->HandlePayButton($strPage); break; default: $this->setHeaders;

// OLD CODE -- chop out later // Log POSTing if ($_SERVER['REQUEST_METHOD'] == 'POST') { LogSession; $out = "\n== ENV:"; foreach ($_ENV AS $key => $value) { $out .= "\n\t\t$key\t$value"; }   /*		$out .= $this->EnvLine('REQUEST_METHOD'); $out .= $this->EnvLine('HTTP_USER_AGENT'); $out .= $this->EnvLine('REMOTE_ADDR'); */		$out .= "\n== SERVER:"; foreach ($_SERVER AS $key => $value) { //		   $wgOut->AddWikiText("* $key: $value"); $out .= "\n\t\t$key\t$value"; }		$out .= "\n== POST:"; foreach ($_POST AS $key => $value) { //		   $wgOut->AddWikiText("* $key: $value"); $out .= "\n\t\t$key\t$value"; }

// write more complete data to log file: $out .= "\n== checking for PayPal post"; LogText($out); if (isset($_POST['txn_type'])) { // process POST data from PayPal LogText("\n== PayPal post detected"); $this->ProcessPayPalPost; LogText("\n== PayPal post processed"); } else { LogText("\n== NOT PayPal"); }	   }	    if ($wgUser->isAllowed(ksPerm_SubscrAdmin)) { //$this->ProcessPayPalPost; $this->doAdmin; } else { $this->doUser; }	   $out = '&rarr;[ [ refresh] ]&larr;';

$wgOut->AddWikiText($out); } }  private function EnvLine($iName) { return "\t".$iName."\t".$_ENV[$iName]."\n"; } public function doAdmin { global $wgRequest, $wgOut, $wgTitle; /*	PURPOSE: do stuff that only admins are allowed to do	$out = '==Subscription Admin=='; // ** check for submitted changes // *** added subscribers? if (isset($_GET['btn_add'])) { $strUserAdd = $_GET['username']; if ($strUserAdd) { $out .= SiteSubscriber::doAddName($strUserAdd); } else { $out .= "\nPlease enter name of user you wish to add."; }	}

if (isset($_GET['do'])) { $strAct = $_GET['do']; if ($strAct == 'remove') { $idUser = $_GET['user']; SiteSubscriber::doDelID($idUser); }	}

// ** Show list of subscribers $dbr = self::DB; $ppq = $dbr->query( 'SELECT * FROM '.ksTbl_Subscr); //	$ppg = $dbr->query( 'SELECT * FROM '.ksTbl_Subscr.' AS ss LEFT JOIN user AS u ON ss.ID_User=u.user_id' ); // ultimately uses mysql_query or similar $out .= "\n===subscribers===\n{| border=1\n|-\n! username || real name || email || expiration || actions"; $intRows = 0; while ($row = mysql_fetch_assoc($ppq)) { $intRows++; $idUser = $row['ID_User']; $objUser = User::newFromId($idUser); if (is_object($objUser)) { $strUser = $objUser->getName; $strName = $objUser->getRealName; $strEmail = $objUser->getEmail; $wtUser = .$strUser.; } else { $wtUser = 'Unknown ID '.$idUser; } //		$objExp = new Timestamp(strtotime($row['WhenExp'])); $objExp = new Timestamp($row['WhenExp']); $strExp = $objExp->Format(ksFmt_DateShow); $out .= "\n|-\n| ".$wtUser.' || '.$strName.' || '.$strEmail.' || '.$strExp.' || [ remove]'; }	if ($intRows == 0) { $out .= "\n|-\n| colspan=2 | No subscribers yet"; }	$out .= "\n|}"; $wgOut->AddWikiText($out); // *** allow users to be subscribed manually

$out = 'getFullURL.'">Subscribe a user:  '; $wgOut->addHTML($out); } public function doUser { global $wgOut; /*	PURPOSE: do only stuff that regular users are allowed to do	$wgOut->AddWikiText('Hello boring regular user!'); }

public function ProcessPayPalPost { // Adapted from https://www.paypal.com/us/cgi-bin/webscr?cmd=p/pdn/ipn-codesamples-pop-outside#php // read the post from PayPal system and add 'cmd' $req = 'cmd=_notify-validate';

// record that a post has been received LogText("\n== RUNNING ProcessPayPalPost"); $objPost = new Posting; $idUser = $objPost->ID_User; LogText("\n== USER ID = $idUser");

foreach ($_POST as $key => $value) { $valRtn = urlencode(stripslashes($value)); $req .= "&$key=$valRtn";

// record each piece of data in the post $objPost->AddLine($key,$value); }	$objPost->PaymtRecd; // post back to PayPal system to validate $header = NULL; $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";

if ($objPost->isTest) { $urlAuth = 'ssl://www.sandbox.paypal.com'; } else { $urlAuth = 'ssl://www.paypal.com'; }	LogText("\n== AUTH URL=".$urlAuth); $fp = fsockopen ($urlAuth, 443, $errno, $errstr, 30);

// assign posted variables to local variables $item_name = $_POST['item_name']; $item_number = $_POST['item_number']; $payment_status = $_POST['payment_status']; $payment_amount = $_POST['mc_gross']; $payment_currency = $_POST['mc_currency']; $txn_id = $_POST['txn_id']; $receiver_email = $_POST['receiver_email']; $payer_email = $_POST['payer_email'];

if (!$fp) { // HTTP ERROR LogText("\n== ERROR: Could not contact confirmation server!"); $objPost->ConfirmRecd(FALSE, $errstr); } else { $objPost->ConfirmSent;

fputs ($fp, $header . $req); $isDone = FALSE; while (!feof($fp) && !$isDone) { $res = fgets ($fp, 1024);

if (strcmp ($res, "VERIFIED") == 0) { LogText("\n== PayPal says VERIFIED"); $strStat = 'OKAY'; $objPost->ConfirmRecd(TRUE, $res); LogText("\n== CONFIRM logged"); $isOk = TRUE; // check the payment_status is Completed $isOk = $isOk && ($payment_status == 'Completed'); // check that txn_id has not been previously processed // TO DO				// check that receiver_email is your Primary PayPal email // TO DO				// check that payment_amount/payment_currency are correct $isOk = $isOk && ($payment_amount >= 19.95); $isOk = $isOk && ($payment_currency == 'USD');	// US Dollars // process payment if ($isOk) { if ($idUser) { LogText("\n== ADDING SUBSCRIBER ID=".$idUser); $arrParams['user id'] = $idUser; $arrParams['post id'] = $objPost->ID;

if (!$objPost->isTest || kfDoSubscribeSandbox) { SiteSubscriber::doAddID($idUser); LogText("\n== SUBSCRIBER ADDED=".$idUser); } else { LogText("\n== SANDBOX SUBSCRIPTION IGNORED"); }					} else { LogText("\n== NO SUBSCRIBER ID RECEIVED"); }				}			} else if (strcmp ($res, "INVALID") == 0) { $strStat = 'FAIL'; $objPost->ConfirmRecd(FALSE, $res); LogText("\n== NOT a valid payment"); } else { // log for manual investigation $strStat = ''; }			$strLine = trim($res); LogText("\n== [".$strStat.'] POST ID='.$objPost->ID.' - PayPal says: ['.$strLine.']'); }		fclose ($fp); LogText("\n== PayPal Processing complete"); } /**/ } } class SiteSubscriber { /*========== DYNAMIC SECTION private $vmwObj;	// MediaWiki User object (if known) private $vID;	// ID_User private $vExp;	// WhenExp public $Status;	// status message

public function Init_fromMWObj($imwObj) { // INPUT: MediaWiki User object $this->vmwObj = $imwObj; $this->vID = $imwObj->getID; $dbr = self::DB; $ppq = $dbr->query( 'SELECT * FROM `'.ksTbl_Subscr.'` WHERE ID_User='.$this->vID); if (mysql_num_rows($ppq) == 0) { $this->Exp = NULL; } else { $row = mysql_fetch_assoc($ppq); $this->Exp = $row['WhenExp']; }			}	public function IsSubscriber { /*	RETURNS: SiteSubscriber if user is one, otherwise FALSE */		if (is_null($this->Exp)) { return FALSE;	// no subscriber record, so no - not a subscriber } else { $dtExp = new Timestamp($this->Exp); return $dtExp->IsLater; }	}	public function SetGroup($iOn,$iLogMsg) { global $wgUser;

$msg = 'User:'.$this->vmwObj->getName; $arrParams['user id'] = $this->vID; if ($iOn) { $this->vmwObj->addGroup(ksGrp_SubscrUser); $msg .= ' added to subscriber group'; } else { $this->vmwObj->removeGroup(ksGrp_SubscrUser); $msg .= ' removed from subscriber group'; }		$msg .= $iLogMsg; SpecialSiteSubscribe::DoLog('rights',$msg,$arrParams); $wgUser->invalidateCache;	// clear cached pages so new permissions will take effect }	public function SynchStatus { /*	ACTION: Update the user's "subscribers" group membership according to their subscription status */		if ($this->vmwObj->isAllowed(ksPerm_SubscrUser) != $this->IsSubscriber) { if ($this->IsSubscriber) { $this->SetGroup(TRUE,' due to active subscription'); } else { $this->SetGroup(FALSE,' due to expired subscription'); }		}	} /*========== STATIC SECTION private static $vDB;

public static function DB { if (!is_object(self::$vDB)) { self::$vDB = SpecialSiteSubscribe::DB; }		return self::$vDB; }	public static function NewFromName($iName) { $objNew = new Self;

$objNew->Init_fromMWObj(User::newFromName($iName)); return $objNew; }

// TO DO: convert doAddName and doAddID to non-static functions which act on the current object

public static function doAddName($iName) { $out = ''; $out .= "\nSubscribing user $iName..."; $objUserAdd = User::newFromName($iName); if (is_object($objUserAdd)) { if ($objUserAdd->isLoggedIn) { $out .= ' found...'; $out .= self::doAddID($objUserAdd->getID); } else { $out .= " user not found, can't add"; }		} else { $out .= ' ERROR: user object would not load.'; }		return $out; }	public static function doAddID($iID) { //$log = new LogPage( 'subscribe' ); $dbw = self::DB; $ppq = $dbw->query( 'SELECT * FROM `'.ksTbl_Subscr.'` WHERE ID_User='.$iID); $dtExp = self::figExpiration; $strExp = $dtExp->Format(ksFmt_DateShow); $sqlExp = $dtExp->Format(ksFmt_DateSQL);

// setup for logging $msg = 'User ID '.$iID; $arrParams['user id'] = $iID; $arrParams['expiration new'] = $strExp;

if (mysql_num_rows($ppq) == 0) { $out = ' subscription active until '.$strExp; $sql = 'INSERT INTO '.ksTbl_Subscr.' (ID_User, WhenExp) VALUES('.$iID.', "'.$sqlExp.'");'; // logging $msg .= ' subscription record created - expires '.$strExp; } else { $row = mysql_fetch_assoc($ppq); $strExpOld = $row['WhenExp']; $out = ' updating expiration from '.$row['WhenExp'].' to '.$strExp; $sql = 'UPDATE '.ksTbl_Subscr.' SET WhenExp="'.$sqlExp.'" WHERE ID_User='.$iID.';'; // logging $msg .= ' subscription record updated - expires '.$strExp.'(was: '.$strExpOld.')'; $arrParams['expiration old'] = $strExpOld; }		$dbw->query($sql); SpecialSiteSubscribe::DoLog('',$msg,$arrParams);

return $out; }	public static function doDelID($iID) { $msg = 'User ID '.$iID; $arrParams['user id'] = $iID; $msg .= ' subscription record deleted';

// delete user subscription record $sql = 'DELETE FROM '.ksTbl_Subscr.' WHERE ID_User='.$iID.';'; $dbw = self::DB; $dbw->query($sql); SpecialSiteSubscribe::DoLog('',$msg,$arrParams);

// remove user from group $objUser = User::newFromId($iID); $objUser->removeGroup(ksGrp_SubscrUser); $arrParams['user id'] = $iID; $arrParams['user name'] = $objUser->getName; $msg = 'User:'.$objUser->getName.' manually removed from subscriber group by admin'; SpecialSiteSubscribe::DoLog('rights',$msg,$arrParams); }	private static function figExpiration { $dtNow = new Timestamp; return $dtExp = $dtNow->PlusDays(366);	// add one year -- later, this should be a config setting } }

class Posting {

/*========== STATIC SECTION private static $vDB;

public static function DB { if (!is_object(self::$vDB)) { self::$vDB = SpecialSiteSubscribe::DB; }		return self::$vDB; }

/*========== DYNAMIC SECTION private $vID; private $vRmtAddr; private $isPaymt; private $vPayAmt; private $vTxnID; private $vIDUser;

public $isTest;

public function __construct { $this->vIDUser = $_GET[userid];

if (isset($_SERVER['REMOTE_ADDR'])) { $this->vRmtAddr = $_SERVER['REMOTE_ADDR']; $strAddr = $this->vRmtAddr; } else { $this->vRmtAddr = NULL; $strAddr = NULL; }		$this->isTest = FALSE;

$dbw = self::DB; //$sql = 'INSERT INTO '.ksTbl_Posting.' (RmtAddr, WhenRecd) VALUES('.$iRmtAddr.', NOW);'; //$dbw->query($sql); $dbw->insert(ksTbl_Posting,array( 'RmtAddr' => $strAddr, 'WhenRecd' => date(ksFmt_TimeSQL) ));		$this->vID = $dbw->insertId; }

public function ID { return $this->vID; }	public function ID_User { return $this->vIDUser; }

public function AddLine($iKey, $iVal) { $dbw = self::DB; $dbw->insert(ksTbl_PostLine,array( 'ID_Post' => $this->vID, 'DataKey' => $iKey, 'DataVal' => $iVal ));		switch ($iKey) { case 'test_ipn': if ($iVal == '1') { $this->isTest = TRUE; }		   break; case 'txn_type': $this->isPaymt = ($iVal == ''); break; case 'txn_id': $this->vTxnID = $iVal; break; case 'mc_gross': $this->vPayAmt = $iVal; break; }	}	public function PaymtRecd { $dbw = self::DB;

// set test flag appropriately, now that we know whether this is a sandbox payment or not: $sqlTest = $this->isTestSQL; $sql = 'UPDATE '.ksTbl_Posting.' SET isTest='.$sqlTest.' WHERE ID='.$this->ID; $dbw->query($sql);

}	private function isTestSQL { return $this->isTest?-1:0; }	public function ConfirmSent { $dbw = self::DB; /* Using update doesn't seem to work; this was the code: $arrChg = array(			'WhenConf' => date(ksFmt_TimeSQL)); $dbw->ignoreErrors(TRUE); $dbw->update( ksTbl_Posting, $arrChg, 'ID='.$this->vID); /*/		$sql = 'UPDATE '.ksTbl_Posting.' SET WhenConf=NOW WHERE ID='.$this->ID; $dbw->query($sql); /**/	}	public function ConfirmRecd($iOK, $iText) { $dbw = self::DB; /* Using update doesn't seem to work; this was the code: $dbw->update( ksTbl_Posting, array( 'WhenRepl' => date(ksFmt_TimeSQL), 'ConfText' => $iText, 'isOkay' => $iOK),				'ID='.$this->vID); /*/		LogText("\n== running ConfirmRecd"); $sqlConfText = $dbw->strencode($iText); LogText("\n== text=[$sqlConfText]"); $sqlIsOkay = $iOK?-1:0; $sql = 'UPDATE '.ksTbl_Posting.' SET'. ' WhenRepl=NOW,'. ' ConfText="'.$sqlConfText.'",'. ' isOkay='.$sqlIsOkay. ' WHERE ID='.$this->ID; LogText("\n== SQL=[$sql]"); $dbw->query($sql); LogText("\n== posting updated"); /**/

if ($iOK) { // we now know a valid payment has been received, so log it in the payments table: // log the payment in the payments table:

$sqlIsTest = $this->isTest?-1:0; $sql = 'INSERT INTO '.ksTbl_Paymts. ' (ID_User, WhenRecd, AmtRecd, IDS_Trx, isTest) '. ' VALUES('.$this->vIDUser.',NOW,'.$this->vPayAmt.',"'.$this->vTxnID.'",'.$sqlIsTest.');'; LogText("\n== SQL=[$sql]"); $dbw->query($sql); LogText("\n== payment logged"); }	} }

class Timestamp { private $vWhen;

public function __construct($iWhen = -1) { if ($iWhen == -1) { // use NOW $this->vWhen = time; } else { if (is_numeric($iWhen)) { $this->vWhen = $iWhen; } else if (is_string($iWhen)) { $this->vWhen = strtotime($iWhen); } else { $this->vWhen = NULL; }		}	}	public function PlusDays($iDays) { $dtOut = new Timestamp($this->vWhen + ($iDays * 60*60*24)); return $dtOut; }	public function Format($iFmt) { return date($iFmt,$this->vWhen); }	public function IsLater { return $this->vWhen > time; } } function LogText($iText) { global $wgfpLogs;

$fh = fopen($wgfpLogs.'SiteSubscribe.log', 'a'); $qb = fwrite($fh, $iText); fclose($fh); } function LogSession { LogText("\n=====\n".date('Y-m-d H:i:s')); }