r100123 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r100122‎ | r100123 | r100124 >
Date:17:23, 18 October 2011
Author:khorn
Status:ok
Tags:fundraising 
Comment:
Merging the fundraising branch of DonationInterface back into trunk.
Adding all the directories which are completely new to the trunk version.
As this is a mid-merge commit, the extension will be broken until the merge is completed.
Modified paths:
  • /trunk/extensions/DonationInterface/donationinterface.php (added) (history)
  • /trunk/extensions/DonationInterface/gateway_common (added) (history)
  • /trunk/extensions/DonationInterface/globalcollect_gateway (added) (history)
  • /trunk/extensions/DonationInterface/tests (added) (history)
  • /trunk/extensions/DonationInterface/tests/coverage (modified) (history)

Diff [purge]

Index: trunk/extensions/DonationInterface/donationinterface.php
@@ -0,0 +1,518 @@
 2+<?php
 3+
 4+/**
 5+ * Donation Interface
 6+ *
 7+ * To install the DontaionInterface extension, put the following line in LocalSettings.php:
 8+ * require_once( "\$IP/extensions/DonationInterface/donationinterface.php" );
 9+ *
 10+ */
 11+
 12+
 13+# Alert the user that this is not a valid entry point to MediaWiki if they try to access the special pages file directly.
 14+if ( !defined( 'MEDIAWIKI' ) ) {
 15+ echo <<<EOT
 16+To install the DontaionInterface extension, put the following line in LocalSettings.php:
 17+require_once( "\$IP/extensions/DonationInterface/donationinterface.php" );
 18+EOT;
 19+ exit( 1 );
 20+}
 21+
 22+// Extension credits that will show up on Special:Version
 23+$wgExtensionCredits['specialpage'][] = array(
 24+ 'name' => 'Donation Interface',
 25+ 'author' => 'Katie Horn',
 26+ 'version' => '1.0.0',
 27+ 'descriptionmsg' => 'donationinterface-desc',
 28+ 'url' => 'http://www.mediawiki.org/wiki/Extension:DonationInterface',
 29+);
 30+
 31+$donationinterface_dir = dirname( __FILE__ ) . '/';
 32+
 33+/**
 34+ * Figure out what we've got enabled.
 35+ */
 36+
 37+$optionalParts = array( //define as fail closed. This variable will be unset before we leave this file.
 38+ 'Extras' => false, //this one gets set in the next loop, so don't bother.
 39+ 'Stomp' => false,
 40+ 'CustomFilters' => false, //this is definitely an Extra
 41+ 'ConversionLog' => false, //this is definitely an Extra
 42+ 'Minfraud' => false, //this is definitely an Extra
 43+ 'Minfraud_as_filter' => false, //extra
 44+ 'Recaptcha' => false, //extra
 45+ 'PayflowPro' => false,
 46+ 'GlobalCollect' => false,
 47+
 48+);
 49+
 50+foreach ($optionalParts as $subextension => $enabled){
 51+ $globalname = 'wgDonationInterfaceEnable' . $subextension;
 52+ global $$globalname;
 53+ if ( isset( $$globalname ) && $$globalname === true ) {
 54+ $optionalParts[$subextension] = true;
 55+ if ( $subextension === 'CustomFilters' ||
 56+ $subextension === 'ConversionLog' ||
 57+ $subextension === 'Minfraud' ||
 58+ $subextension === 'Recaptcha' ) {
 59+
 60+ $optionalParts['Extras'] = true;
 61+ }
 62+ }
 63+}
 64+
 65+
 66+/**
 67+ * CLASSES
 68+ */
 69+$wgAutoloadClasses['DonationData'] = $donationinterface_dir . 'gateway_common/DonationData.php';
 70+$wgAutoloadClasses['GatewayAdapter'] = $donationinterface_dir . 'gateway_common/gateway.adapter.php';
 71+$wgAutoloadClasses['GatewayForm'] = $donationinterface_dir . 'gateway_common/GatewayForm.php';
 72+
 73+//load all possible form classes
 74+$wgAutoloadClasses['Gateway_Form'] = $donationinterface_dir . 'gateway_forms/Form.php';
 75+$wgAutoloadClasses['Gateway_Form_OneStepTwoColumn'] = $donationinterface_dir . 'gateway_forms/OneStepTwoColumn.php';
 76+$wgAutoloadClasses['Gateway_Form_TwoStepAmount'] = $donationinterface_dir . 'gateway_forms/TwoStepAmount.php';
 77+$wgAutoloadClasses['Gateway_Form_TwoStepTwoColumn'] = $donationinterface_dir . 'gateway_forms/TwoStepTwoColumn.php';
 78+$wgAutoloadClasses['Gateway_Form_TwoColumnPayPal'] = $donationinterface_dir . 'gateway_forms/TwoColumnPayPal.php';
 79+$wgAutoloadClasses['Gateway_Form_TwoColumnLetter'] = $donationinterface_dir . 'gateway_forms/TwoColumnLetter.php';
 80+$wgAutoloadClasses['Gateway_Form_TwoColumnLetter2'] = $donationinterface_dir . 'gateway_forms/TwoColumnLetter2.php';
 81+$wgAutoloadClasses['Gateway_Form_TwoColumnLetter3'] = $donationinterface_dir . 'gateway_forms/TwoColumnLetter3.php';
 82+$wgAutoloadClasses['Gateway_Form_TwoColumnLetter4'] = $donationinterface_dir . 'gateway_forms/TwoColumnLetter4.php';
 83+$wgAutoloadClasses['Gateway_Form_TwoColumnLetter5'] = $donationinterface_dir . 'gateway_forms/TwoColumnLetter5.php';
 84+$wgAutoloadClasses['Gateway_Form_TwoColumnLetter6'] = $donationinterface_dir . 'gateway_forms/TwoColumnLetter6.php';
 85+$wgAutoloadClasses['Gateway_Form_TwoColumnLetter7'] = $donationinterface_dir . 'gateway_forms/TwoColumnLetter7.php';
 86+$wgAutoloadClasses['Gateway_Form_TwoStepTwoColumnLetter'] = $donationinterface_dir . 'gateway_forms/TwoStepTwoColumnLetter.php';
 87+$wgAutoloadClasses['Gateway_Form_TwoStepTwoColumnLetterCA'] = $donationinterface_dir . 'gateway_forms/TwoStepTwoColumnLetterCA.php';
 88+$wgAutoloadClasses['Gateway_Form_TwoStepTwoColumnLetter2'] = $donationinterface_dir . 'gateway_forms/TwoStepTwoColumnLetter2.php';
 89+$wgAutoloadClasses['Gateway_Form_TwoStepTwoColumnLetter3'] = $donationinterface_dir . 'gateway_forms/TwoStepTwoColumnLetter3.php';
 90+$wgAutoloadClasses['Gateway_Form_TwoStepTwoColumnPremium'] = $donationinterface_dir . 'gateway_forms/TwoStepTwoColumnPremium.php';
 91+$wgAutoloadClasses['Gateway_Form_TwoStepTwoColumnPremiumUS'] = $donationinterface_dir . 'gateway_forms/TwoStepTwoColumnPremiumUS.php';
 92+$wgAutoloadClasses['Gateway_Form_RapidHtml'] = $donationinterface_dir . 'gateway_forms/RapidHtml.php';
 93+$wgAutoloadClasses['Gateway_Form_SingleColumn'] = $donationinterface_dir . 'gateway_forms/SingleColumn.php';
 94+
 95+
 96+//GlobalCollect gateway classes
 97+if ( $optionalParts['GlobalCollect'] === true ){
 98+ $wgAutoloadClasses['GlobalCollectGateway'] = $donationinterface_dir . 'globalcollect_gateway/globalcollect_gateway.body.php';
 99+ $wgAutoloadClasses['GlobalCollectGatewayResult'] = $donationinterface_dir . 'globalcollect_gateway/globalcollect_resultswitcher.body.php';
 100+ $wgAutoloadClasses['GlobalCollectAdapter'] = $donationinterface_dir . 'globalcollect_gateway/globalcollect.adapter.php';
 101+}
 102+//PayflowPro gateway classes
 103+if ( $optionalParts['PayflowPro'] === true ){
 104+ $wgAutoloadClasses['PayflowProGateway'] = $donationinterface_dir . 'payflowpro_gateway/payflowpro_gateway.body.php';
 105+ $wgAutoloadClasses['PayflowProAdapter'] = $donationinterface_dir . 'payflowpro_gateway/payflowpro.adapter.php';
 106+}
 107+
 108+//Stomp classes
 109+if ($optionalParts['Stomp'] === true){
 110+ $wgAutoloadClasses['activemq_stomp'] = $donationinterface_dir . 'activemq_stomp/activemq_stomp.php'; # Tell MediaWiki to load the extension body.
 111+}
 112+
 113+//Extras classes - required for ANY optional class that is considered an "extra".
 114+if ($optionalParts['Extras'] === true){
 115+ $wgAutoloadClasses['Gateway_Extras'] = $donationinterface_dir . "extras/extras.body.php";
 116+}
 117+
 118+//Custom Filters classes
 119+if ($optionalParts['CustomFilters'] === true){
 120+ $wgAutoloadClasses['Gateway_Extras_CustomFilters'] = $donationinterface_dir . "extras/custom_filters/custom_filters.body.php";
 121+}
 122+
 123+//Conversion Log classes
 124+if ($optionalParts['ConversionLog'] === true){
 125+ $wgAutoloadClasses['Gateway_Extras_ConversionLog'] = $donationinterface_dir . "extras/conversion_log/conversion_log.body.php";
 126+}
 127+
 128+//Minfraud classes
 129+if ( $optionalParts['Minfraud'] === true || $optionalParts['Minfraud_as_filter'] === true ){
 130+ $wgAutoloadClasses['Gateway_Extras_MinFraud'] = $donationinterface_dir . "extras/minfraud/minfraud.body.php";
 131+}
 132+
 133+//Minfraud as Filter classes
 134+if ( $optionalParts['Minfraud_as_filter'] === true ){
 135+ $wgAutoloadClasses['Gateway_Extras_CustomFilters_MinFraud'] = $donationinterface_dir . "extras/custom_filters/filters/minfraud/minfraud.body.php";
 136+}
 137+
 138+//Recaptcha classes
 139+if ( $optionalParts['Recaptcha'] === true ){
 140+ $wgAutoloadClasses['Gateway_Extras_ReCaptcha'] = $donationinterface_dir . "extras/recaptcha/recaptcha.body.php";
 141+}
 142+
 143+
 144+/**
 145+ * GLOBALS
 146+ */
 147+
 148+/**
 149+ * Global form dir and RapidHTML whitelist
 150+ */
 151+$wgDonationInterfaceHtmlFormDir = dirname( __FILE__ ) . "/gateway_forms/html";
 152+//ffname is the $key from now on.
 153+$wgDonationInterfaceAllowedHtmlForms = array(
 154+ 'demo' => $wgDonationInterfaceHtmlFormDir . "/demo.html",
 155+ 'globalcollect_test' => $wgDonationInterfaceHtmlFormDir . "/globalcollect_test.html",
 156+);
 157+
 158+$wgDonationInterfaceTest = false;
 159+
 160+/**
 161+ * The URL to redirect a transaction to PayPal
 162+ * This should probably point to ContributionTracking.
 163+ */
 164+$wgDonationInterfacePaypalURL = '';
 165+$wgDonationInterfaceRetrySeconds = 5;
 166+
 167+//all of the following variables make sense to override directly,
 168+//or change "DonationInterface" to the gateway's id to override just for that gateway.
 169+//for instance: To override $wgDonationInterfaceUseSyslog just for GlobalCollect, add
 170+// $wgGolbalCollectGatewayUseSyslog = true
 171+// to LocalSettings.
 172+//
 173+
 174+$wgDonationInterfaceDisplayDebug = false;
 175+$wgDonationInterfaceUseSyslog = false;
 176+$wgDonationInterfaceSaveCommStats = false;
 177+
 178+$wgDonationInterfaceCSSVersion = 1;
 179+$wgDonationInterfaceTimeout = 5;
 180+$wgDonationInterfaceDefaultForm = 'TwoStepTwoColumn';
 181+
 182+/**
 183+ * A string or array of strings for making tokens more secure
 184+ *
 185+ * Please set this! If you do not, tokens are easy to get around, which can
 186+ * potentially leave you and your users vulnerable to CSRF or other forms of
 187+ * attack.
 188+ */
 189+$wgDonationInterfaceSalt = $wgSecretKey;
 190+
 191+/**
 192+ * A string that can contain wikitext to display at the head of the credit card form
 193+ *
 194+ * This string gets run like so: $wg->addHtml( $wg->Parse( $wgpayflowGatewayHeader ))
 195+ * You can use '@language' as a placeholder token to extract the user's language.
 196+ *
 197+ */
 198+$wgDonationInterfaceHeader = NULL;
 199+
 200+/**
 201+ * A string containing full URL for Javascript-disabled credit card form redirect
 202+ */
 203+$wgDonationInterfaceNoScriptRedirect = null;
 204+
 205+/**
 206+ * Proxy settings
 207+ *
 208+ * If you need to use an HTTP proxy for outgoing traffic,
 209+ * set wgPayflowGatweayUseHTTPProxy=TRUE and set $wgPayflowProGatewayHTTPProxy
 210+ * to the proxy desination.
 211+ * eg:
 212+ * $wgPayflowProGatewayUseHTTPProxy=TRUE;
 213+ * $wgPayflowProGatewayHTTPProxy='192.168.1.1:3128'
 214+ */
 215+$wgDonationInterfaceUseHTTPProxy = FALSE;
 216+$wgDonationInterfaceHTTPProxy = '';
 217+
 218+/**
 219+ * Set the max-age value for Squid
 220+ *
 221+ * If you have Squid enabled for caching, use this variable to configure
 222+ * the s-max-age for cached requests.
 223+ * @var int Time in seconds
 224+ */
 225+$wgDonationInterfaceSMaxAge = 6000;
 226+
 227+/**
 228+ * Configure price cieling and floor for valid contribution amount. Values
 229+ * should be in USD.
 230+ */
 231+$wgDonationInterfacePriceFloor = '1.00';
 232+$wgDonationInterfacePriceCeiling = '10000.00';
 233+
 234+/**
 235+ * Default Thank You and Fail pages for all of donationinterface - language will be calc'd and appended at runtime.
 236+ */
 237+//$wgDonationInterfaceThankYouPage = 'https://wikimediafoundation.org/wiki/Thank_You';
 238+$wgDonationInterfaceThankYouPage = 'Donate-thanks';
 239+$wgDonationInterfaceFailPage = 'Donate-error';
 240+
 241+
 242+//GlobalCollect gateway globals
 243+if ( $optionalParts['GlobalCollect'] === true ){
 244+ $wgGlobalCollectGatewayURL = 'https://ps.gcsip.nl/wdl/wdl';
 245+ $wgGlobalCollectGatewayTestingURL = 'https://'; // GlobalCollect testing URL
 246+
 247+ $wgGlobalCollectGatewayMerchantID = ''; // GlobalCollect ID
 248+
 249+ $wgGlobalCollectGatewayHtmlFormDir = $donationinterface_dir . 'globalcollect_gateway/forms/html';
 250+ //this really should be redefined in LocalSettings.
 251+ $wgGlobalCollectGatewayAllowedHtmlForms = $wgDonationInterfaceAllowedHtmlForms;
 252+}
 253+
 254+//PayflowPro gateway globals
 255+if ( $optionalParts['PayflowPro'] === true ){
 256+ $wgPayflowProGatewayURL = 'https://payflowpro.paypal.com';
 257+ $wgPayflowProGatewayTestingURL = 'https://pilot-payflowpro.paypal.com'; // Payflow testing URL
 258+
 259+ $wgPayflowProGatewayPartnerID = ''; // PayPal or original authorized reseller
 260+ $wgPayflowProGatewayVendorID = ''; // paypal merchant login ID
 261+ $wgPayflowProGatewayUserID = ''; // if one or more users are set up, authorized user ID, else same as VENDOR
 262+ $wgPayflowProGatewayPassword = ''; // merchant login password
 263+
 264+ $wgPayflowProGatewayHtmlFormDir = $donationinterface_dir . 'payflowpro_gateway/forms/html';
 265+ //this really should be redefined in LocalSettings.
 266+ $wgPayflowProGatewayAllowedHtmlForms = $wgDonationInterfaceAllowedHtmlForms;
 267+}
 268+
 269+//Stomp globals
 270+if ($optionalParts['Stomp'] === true){
 271+ $wgStompServer = "";
 272+ //$wgStompQueueName = ""; //only set this with an actual value. Default is unset.
 273+ //$wgPendingStompQueueName = ""; //only set this with an actual value. Default is unset.
 274+}
 275+
 276+//Extras globals - required for ANY optional class that is considered an "extra".
 277+if ($optionalParts['Extras'] === true){
 278+ $wgDonationInterfaceExtrasLog = '';
 279+}
 280+
 281+//Custom Filters globals
 282+if ( $optionalParts['CustomFilters'] === true ){
 283+ //Define the action to take for a given $risk_score
 284+ $wgDonationInterfaceCustomFiltersActionRanges = array(
 285+ 'process' => array( 0, 100 ),
 286+ 'review' => array( -1, -1 ),
 287+ 'challenge' => array( -1, -1 ),
 288+ 'reject' => array( -1, -1 ),
 289+ );
 290+
 291+ /**
 292+ * A value for tracking the 'riskiness' of a transaction
 293+ *
 294+ * The action to take based on a transaction's riskScore is determined by
 295+ * $action_ranges. This is built assuming a range of possible risk scores
 296+ * as 0-100, although you can probably bend this as needed.
 297+ */
 298+ $wgDonationInterfaceCustomFiltersRiskScore = 0;
 299+}
 300+
 301+//Minfraud globals
 302+if ( $optionalParts['Minfraud'] === true || $optionalParts['Minfraud_as_filter'] === true ){
 303+ /**
 304+ * Your minFraud license key.
 305+ */
 306+ $wgMinFraudLicenseKey = '';
 307+
 308+ /**
 309+ * Set the risk score ranges that will cause a particular 'action'
 310+ *
 311+ * The keys to the array are the 'actions' to be taken (eg 'process').
 312+ * The value for one of these keys is an array representing the lower
 313+ * and upper bounds for that action. For instance,
 314+ * $wgMinFraudActionRagnes = array(
 315+ * 'process' => array( 0, 100)
 316+ * ...
 317+ * );
 318+ * means that any transaction with a risk score greather than or equal
 319+ * to 0 and less than or equal to 100 will be given the 'process' action.
 320+ *
 321+ * These are evauluated on a >= or <= basis. Please refer to minFraud
 322+ * documentation for a thorough explanation of the 'riskScore'.
 323+ */
 324+ $wgMinFraudActionRanges = array(
 325+ 'process' => array( 0, 100 ),
 326+ 'review' => array( -1, -1 ),
 327+ 'challenge' => array( -1, -1 ),
 328+ 'reject' => array( -1, -1 )
 329+ );
 330+
 331+ // Timeout in seconds for communicating with MaxMind
 332+ $wgMinFraudTimeout = 2;
 333+
 334+ /**
 335+ * Define whether or not to run minFraud in stand alone mode
 336+ *
 337+ * If this is set to run in standalone, these scripts will be
 338+ * accessed directly via the "GatewayValidate" hook.
 339+ * You may not want to run this in standalone mode if you prefer
 340+ * to use this in conjunction with Custom Filters. This has the
 341+ * advantage of sharing minFraud info with other filters.
 342+ */
 343+ $wgMinFraudStandalone = TRUE;
 344+
 345+}
 346+
 347+//Minfraud as Filter globals
 348+if ( $optionalParts['Minfraud_as_filter'] === true ){
 349+ $wgMinFraudStandalone = FALSE;
 350+}
 351+
 352+//Recaptcha globals
 353+if ( $optionalParts['Recaptcha'] === true ){
 354+ /**
 355+ * Public and Private reCaptcha keys
 356+ *
 357+ * These can be obtained at:
 358+ * http://www.google.com/recaptcha/whyrecaptcha
 359+ */
 360+ $wgDonationInterfaceRecaptchaPublicKey = '';
 361+ $wgDonationInterfaceRecaptchaPrivateKey = '';
 362+
 363+ // Timeout (in seconds) for communicating with reCatpcha
 364+ $wgDonationInterfaceRecaptchaTimeout = 2;
 365+
 366+ /**
 367+ * HTTP Proxy settings
 368+ */
 369+ $wgDonationInterfaceRecaptchaUseHTTPProxy = false;
 370+ $wgDonationInterfaceRecaptchaHTTPProxy = false;
 371+
 372+ /**
 373+ * Use SSL to communicate with reCaptcha
 374+ */
 375+ $wgDonationInterfaceRecaptchaUseSSL = 1;
 376+
 377+ /**
 378+ * The # of times to retry communicating with reCaptcha if communication fails
 379+ * @var int
 380+ */
 381+ $wgDonationInterfaceRecaptchaComsRetryLimit = 3;
 382+}
 383+
 384+/**
 385+ * SPECIAL PAGES
 386+ */
 387+
 388+//GlobalCollect gateway special pages
 389+if ( $optionalParts['GlobalCollect'] === true ){
 390+ $wgSpecialPages['GlobalCollectGateway'] = 'GlobalCollectGateway';
 391+ $wgSpecialPages['GlobalCollectGatewayResult'] = 'GlobalCollectGatewayResult';
 392+}
 393+//PayflowPro gateway special pages
 394+if ( $optionalParts['PayflowPro'] === true ){
 395+ $wgSpecialPages['PayflowProGateway'] = 'PayflowProGateway';
 396+}
 397+
 398+
 399+/**
 400+ * HOOKS
 401+ */
 402+
 403+//Unit tests
 404+$wgHooks['UnitTestsList'][] = 'efDonationInterfaceUnitTests';
 405+
 406+//Stomp hooks
 407+if ($optionalParts['Stomp'] === true){
 408+ $wgHooks['ParserFirstCallInit'][] = 'efStompSetup';
 409+ $wgHooks['gwStomp'][] = 'sendSTOMP';
 410+ $wgHooks['gwPendingStomp'][] = 'sendPendingSTOMP';
 411+}
 412+
 413+//Custom Filters hooks
 414+if ($optionalParts['CustomFilters'] === true){
 415+ $wgHooks["GatewayValidate"][] = array( 'Gateway_Extras_CustomFilters::onValidate' );
 416+}
 417+
 418+//Conversion Log hooks
 419+if ($optionalParts['ConversionLog'] === true){
 420+ // Sets the 'conversion log' as logger for post-processing
 421+ $wgHooks["GatewayPostProcess"][] = array( "Gateway_Extras_ConversionLog::onPostProcess" );
 422+}
 423+
 424+//Recaptcha hooks
 425+if ($optionalParts['Recaptcha'] === true){
 426+ // Set reCpatcha as plugin for 'challenge' action
 427+ $wgHooks["GatewayChallenge"][] = array( "Gateway_Extras_ReCaptcha::onChallenge" );
 428+}
 429+
 430+/**
 431+ * APIS
 432+ */
 433+// enable the API
 434+$wgAPIModules['donate'] = 'DonationApi';
 435+$wgAutoloadClasses['DonationApi'] = $donationinterface_dir . 'gateway_common/donation.api.php';
 436+
 437+//Payflowpro API
 438+if ( $optionalParts['PayflowPro'] === true ){
 439+ $wgAPIModules['pfp'] = 'ApiPayflowProGateway';
 440+ $wgAutoloadClasses['ApiPayflowProGateway'] = $donationinterface_dir . 'payflowpro_gateway/api_payflowpro_gateway.php';
 441+}
 442+
 443+
 444+/**
 445+ * ADDITIONAL MAGICAL GLOBALS
 446+ */
 447+
 448+// Resource modules
 449+$wgResourceTemplate = array(
 450+ 'localBasePath' => $donationinterface_dir . 'modules',
 451+ 'remoteExtPath' => 'DonationInterface/modules',
 452+);
 453+$wgResourceModules['iframe.liberator'] = array(
 454+ 'scripts' => 'iframe.liberator.js',
 455+ 'position' => 'top'
 456+ ) + $wgResourceTemplate;
 457+$wgResourceModules['donationInterface.skinOverride'] = array(
 458+ 'styles' => 'skinOverride.css',
 459+ 'position' => 'top'
 460+ ) + $wgResourceTemplate;
 461+
 462+$wgExtensionMessagesFiles['DonateInterface'] = $donationinterface_dir . 'donate_interface/donate_interface.i18n.php';
 463+
 464+
 465+//GlobalCollect gateway magical globals
 466+
 467+//TODO: all the bits where we make the i18n make sense for multiple gateways. This is clearly less than ideal.
 468+if ( $optionalParts['GlobalCollect'] === true ){
 469+ $wgExtensionMessagesFiles['GlobalCollectGateway'] = $donationinterface_dir . 'payflowpro_gateway/payflowpro_gateway.i18n.php';
 470+ $wgExtensionMessagesFiles['GlobalCollectGatewayCountries'] = $donationinterface_dir . 'payflowpro_gateway/payflowpro_gateway.countries.i18n.php';
 471+ $wgExtensionMessagesFiles['GlobalCollectGatewayUSStates'] = $donationinterface_dir . 'payflowpro_gateway/payflowpro_gateway.us-states.i18n.php';
 472+ $wgExtensionAliasesFiles['GlobalCollectGateway'] = $donationinterface_dir . 'payflowpro_gateway/payflowpro_gateway.alias.php';
 473+}
 474+
 475+//PayflowPro gateway magical globals
 476+if ( $optionalParts['PayflowPro'] === true ){
 477+ $wgExtensionMessagesFiles['PayflowProGateway'] = $donationinterface_dir . 'payflowpro_gateway/payflowpro_gateway.i18n.php';
 478+ $wgExtensionMessagesFiles['PayflowProGatewayCountries'] = $donationinterface_dir . 'payflowpro_gateway/payflowpro_gateway.countries.i18n.php';
 479+ $wgExtensionMessagesFiles['PayflowProGatewayUSStates'] = $donationinterface_dir . 'payflowpro_gateway/payflowpro_gateway.us-states.i18n.php';
 480+ $wgExtensionAliasesFiles['PayflowProGateway'] = $donationinterface_dir . 'payflowpro_gateway/payflowpro_gateway.alias.php';
 481+ $wgAjaxExportList[] = "fnPayflowProofofWork";
 482+}
 483+
 484+//Minfraud magical globals
 485+if ( $optionalParts['Minfraud'] === true ){ //We do not want this in filter mode.
 486+ $wgExtensionFunctions[] = 'efMinFraudSetup';
 487+}
 488+
 489+//Minfraud as Filter globals
 490+if ( $optionalParts['Minfraud_as_filter'] === true ){
 491+ $wgExtensionFunctions[] = 'efCustomFiltersMinFraudSetup';
 492+}
 493+
 494+
 495+/**
 496+ * FUNCTIONS
 497+ */
 498+
 499+//---Stomp functions---
 500+if ($optionalParts['Stomp'] === true){
 501+ require_once( $donationinterface_dir . 'activemq_stomp/activemq_stomp.php' );
 502+}
 503+
 504+//---Minfraud functions---
 505+if ($optionalParts['Minfraud'] === true){
 506+ require_once( $donationinterface_dir . 'extras/minfraud/minfraud.php' );
 507+}
 508+
 509+//---Minfraud as filter functions---
 510+if ($optionalParts['Minfraud_as_filter'] === true){
 511+ require_once( $donationinterface_dir . 'extras/custom_filters/filters/minfraud/minfraud.php' );
 512+}
 513+
 514+function efDonationInterfaceUnitTests( &$files ) {
 515+ $files[] = dirname( __FILE__ ) . '/tests/AllTests.php';
 516+ return true;
 517+}
 518+
 519+unset( $optionalParts );
Property changes on: trunk/extensions/DonationInterface/donationinterface.php
___________________________________________________________________
Added: svn:eol-style
1520 + native
Index: trunk/extensions/DonationInterface/tests/TestConfiguration.php.dist
@@ -0,0 +1,52 @@
 2+<?php
 3+/**
 4+ * Wikimedia Foundation
 5+ *
 6+ * LICENSE
 7+ *
 8+ * This program is free software; you can redistribute it and/or modify
 9+ * it under the terms of the GNU General Public License as published by
 10+ * the Free Software Foundation; either version 2 of the License, or
 11+ * (at your option) any later version.
 12+ *
 13+ * This program is distributed in the hope that it will be useful,
 14+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
 15+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 16+ * GNU General Public License for more details.
 17+ *
 18+ * @since r98249
 19+ * @author Jeremy Postlethwaite <jpostlethwaite@wikimedia.org>
 20+ */
 21+
 22+/*
 23+ * This file contains custom options and constants for test configuration.
 24+ */
 25+
 26+/**
 27+ * TESTS_MESSAGE_NOT_IMPLEMENTED
 28+ *
 29+ * Message for code that has not been implemented.
 30+ */
 31+define( 'TESTS_MESSAGE_NOT_IMPLEMENTED', 'Not implemented yet!' );
 32+
 33+/**
 34+ * TESTS_HOSTNAME
 35+ *
 36+ * The hostname for the system
 37+ */
 38+define( 'TESTS_HOSTNAME', 'localhost' );
 39+
 40+/**
 41+ * TESTS_EMAIL
 42+ *
 43+ * An email address to use in case test send mail
 44+ */
 45+define( 'TESTS_EMAIL', 'no-reply@wikimedia.org' );
 46+
 47+/**
 48+ * TESTS_PFP_CREDIT_CARDS_AMEREICAN_EXPRESS_VALID_CARD
 49+ *
 50+ * A "valid" test American Express Card for PayFlowPro.
 51+ */
 52+define( 'TESTS_PFP_CREDIT_CARDS_AMEREICAN_EXPRESS_VALID_CARD', '378282246310005' );
 53+
Index: trunk/extensions/DonationInterface/tests/Adapter/AllTests.php
@@ -0,0 +1,61 @@
 2+<?php
 3+/**
 4+ * Wikimedia Foundation
 5+ *
 6+ * LICENSE
 7+ *
 8+ * This program is free software; you can redistribute it and/or modify
 9+ * it under the terms of the GNU General Public License as published by
 10+ * the Free Software Foundation; either version 2 of the License, or
 11+ * (at your option) any later version.
 12+ *
 13+ * This program is distributed in the hope that it will be useful,
 14+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
 15+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 16+ * GNU General Public License for more details.
 17+ *
 18+ *
 19+ * @since r98249
 20+ * @author Jeremy Postlethwaite <jpostlethwaite@wikimedia.org>
 21+ */
 22+
 23+/**
 24+ * @see DonationInterface_Adapter_ServerTestCase
 25+ */
 26+require_once dirname( __FILE__ ) . DIRECTORY_SEPARATOR . 'GatewayAdapterTestCase.php';
 27+require_once dirname( __FILE__ ) . DIRECTORY_SEPARATOR . 'GlobalCollect/AllTests.php';
 28+
 29+/**
 30+ * AllTests
 31+ */
 32+class DonationInterface_Adapter_AllTests
 33+{
 34+
 35+ /**
 36+ * Run the main test and load any parameters if needed.
 37+ *
 38+ */
 39+ public static function main()
 40+ {
 41+ $parameters = array();
 42+
 43+ PHPUnit_TextUI_TestRunner::run( self::suite(), $parameters );
 44+ }
 45+
 46+ /**
 47+ * Regular suite
 48+ *
 49+ * All tests except those that require output buffering.
 50+ *
 51+ * @return PHPUnit_Framework_TestSuite
 52+ */
 53+ public static function suite()
 54+ {
 55+ $suite = new PHPUnit_Framework_TestSuite( 'Donation Interface - Adapter Suite' );
 56+
 57+ $suite->addTestSuite( 'DonationInterface_Adapter_GatewayAdapterTestCase' );
 58+ $suite->addTestSuite( 'DonationInterface_Adapter_GlobalCollect_AllTests' );
 59+
 60+ return $suite;
 61+ }
 62+}
Property changes on: trunk/extensions/DonationInterface/tests/Adapter/AllTests.php
___________________________________________________________________
Added: svn:mime-type
163 + text/plain
Added: svn:keywords
264 + Author Date HeadURL Header Id Revision
Added: svn:eol-style
365 + native
Index: trunk/extensions/DonationInterface/tests/Adapter/GatewayAdapterTestCase.php
@@ -0,0 +1,331 @@
 2+<?php
 3+/**
 4+ * Wikimedia Foundation
 5+ *
 6+ * LICENSE
 7+ *
 8+ * This program is free software; you can redistribute it and/or modify
 9+ * it under the terms of the GNU General Public License as published by
 10+ * the Free Software Foundation; either version 2 of the License, or
 11+ * (at your option) any later version.
 12+ *
 13+ * This program is distributed in the hope that it will be useful,
 14+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
 15+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 16+ * GNU General Public License for more details.
 17+ *
 18+ * @since r98249
 19+ * @author Katie Horn <khorn@wikimedia.org>
 20+ */
 21+
 22+/**
 23+ * @see DonationInterfaceTestCase
 24+ */
 25+require_once dirname( dirname( __FILE__ ) ) . DIRECTORY_SEPARATOR . 'DonationInterfaceTestCase.php';
 26+
 27+/**
 28+ * TODO: Test everything.
 29+ * Make sure all the basic functions in the gateway_adapter are tested here.
 30+ * Also, the extras and their hooks firing properly and... that the fail score
 31+ * they give back is acted upon in the way we think it does.
 32+ * Hint: For that mess, use GatewayAdapter's $debugarray
 33+ *
 34+ * Also, note that it barely makes sense to test the functions that need to be
 35+ * defined in each gateway as per the abstract class. If we did that here, we'd
 36+ * basically be just testing the test code. So, don't do it.
 37+ * Those should definitely be tested in the various gateway-specific test
 38+ * classes.
 39+ *
 40+ * @group Fundraising
 41+ * @group Splunge
 42+ * @group Gateways
 43+ * @group DonationInterface
 44+ */
 45+class DonationInterface_Adapter_GatewayAdapterTestCase extends DonationInterfaceTestCase {
 46+
 47+ /**
 48+ *
 49+ * @covers GatewayAdapter::__construct
 50+ * @covers GatewayAdapter::defineVarMap
 51+ * @covers GatewayAdapter::defineReturnValueMap
 52+ * @covers GatewayAdapter::defineTransactions
 53+ */
 54+ public function testbuildRequestXML() {
 55+ $gateway = new TestAdapter();
 56+ $gateway->publicCurrentTransaction( 'Test1' );
 57+ $built = $gateway->buildRequestXML();
 58+ $expected = '<?xml version="1.0"?>' . "\n";
 59+ $expected .= '<XML><REQUEST><ACTION>Donate</ACTION><ACCOUNT><MERCHANTID>128</MERCHANTID><PASSWORD>k4ftw</PASSWORD><VERSION>3.2</VERSION><RETURNURL>http://' . TESTS_HOSTNAME . '/index.php/Special:GlobalCollectGatewayResult</RETURNURL></ACCOUNT><DONATION><DONOR>Tester Testington</DONOR><AMOUNT>35000</AMOUNT><CURRENCYCODE>USD</CURRENCYCODE><LANGUAGECODE>en</LANGUAGECODE><COUNTRYCODE>US</COUNTRYCODE></DONATION></REQUEST></XML>' . "\n";
 60+ $this->assertEquals($built, $expected, "The constructed XML for transaction type Test1 does not match our expected.");
 61+
 62+ }
 63+
 64+ /**
 65+ *
 66+ */
 67+ public function testParseResponseStatusXML() {
 68+
 69+ $returned = $this->getTestGatewayTransactionTest2Results();
 70+ $this->assertEquals($returned['status'], true, "Status should be true at this point.");
 71+ }
 72+
 73+ /**
 74+ *
 75+ */
 76+ public function testParseResponseErrorsXML() {
 77+
 78+ $returned = $this->getTestGatewayTransactionTest2Results();
 79+ $expected_errors = array(
 80+ 128 => "Your shoe's untied...",
 81+ 45 => "Low clearance!"
 82+ );
 83+ $this->assertEquals($returned['errors'], $expected_errors, "Expected errors were not found.");
 84+
 85+ }
 86+
 87+ /**
 88+ *
 89+ */
 90+ public function testParseResponseDataXML() {
 91+
 92+ $returned = $this->getTestGatewayTransactionTest2Results();
 93+ $expected_data = array(
 94+ 'thing' => 'stuff',
 95+ 'otherthing' => 12,
 96+ );
 97+ $this->assertEquals($returned['data'], $expected_data, "Expected data was not found.");
 98+
 99+ }
 100+
 101+ /**
 102+ *
 103+ */
 104+ public function testResponseMessage() {
 105+
 106+ $returned = $this->getTestGatewayTransactionTest2Results();
 107+ $this->assertEquals($returned['message'], "Test2 Transaction Successful!", "Expected message was not returned.");
 108+
 109+ }
 110+
 111+ /**
 112+ *
 113+ */
 114+ public function testGetGlobal(){
 115+ $gateway = new TestAdapter();
 116+ $found = $gateway::getGlobal("TestVar");
 117+ $expected = "Hi there!";
 118+ $this->assertEquals($found, $expected, "getGlobal is not functioning properly.");
 119+ }
 120+
 121+
 122+ /**
 123+ *
 124+ */
 125+ public function getTestGatewayTransactionTest2Results(){
 126+ $gateway = new TestAdapter();
 127+ return $gateway->do_transaction( 'Test2' );
 128+ }
 129+
 130+}
 131+
 132+/**
 133+ * Test Adapter
 134+ */
 135+class TestAdapter extends GatewayAdapter {
 136+
 137+ const GATEWAY_NAME = 'Test Gateway';
 138+ const IDENTIFIER = 'testgateway';
 139+ const COMMUNICATION_TYPE = 'xml';
 140+ const GLOBAL_PREFIX = 'wgTestAdapterGateway';
 141+
 142+ /**
 143+ *
 144+ */
 145+ public function stageData( $type = 'request' ){
 146+ $this->postdata['amount'] = $this->postdata['amount'] * 1000;
 147+ $this->postdata['name'] = $this->postdata['fname'] . " " . $this->postdata['lname'];
 148+ }
 149+
 150+ /**
 151+ *
 152+ */
 153+ public function __construct( ) {
 154+ global $wgTestAdapterGatewayTestVar, $wgTestAdapterGatewayUseSyslog, $wgTestAdapterGatewayTest;
 155+ $wgTestAdapterGatewayTest = true;
 156+ $wgTestAdapterGatewayTestVar = "Hi there!";
 157+ $wgTestAdapterGatewayUseSyslog = true;
 158+ parent::__construct();
 159+
 160+ }
 161+
 162+ /**
 163+ *
 164+ */
 165+ public function defineAccountInfo(){
 166+ $this->accountInfo = array(
 167+ 'MERCHANTID' => '128',
 168+ 'PASSWORD' => 'k4ftw',
 169+ //'IPADDRESS' => '', //TODO: Not sure if this should be OUR ip, or the user's ip. Hurm.
 170+ 'VERSION' => "3.2",
 171+ );
 172+ }
 173+
 174+ /**
 175+ *
 176+ */
 177+ public function defineStagedVars(){
 178+ }
 179+
 180+ /**
 181+ *
 182+ */
 183+ public function defineVarMap(){
 184+ $this->var_map = array(
 185+ 'DONOR' => 'name',
 186+ 'AMOUNT' => 'amount',
 187+ 'CURRENCYCODE' => 'currency',
 188+ 'LANGUAGECODE' => 'language',
 189+ 'COUNTRYCODE' => 'country',
 190+ 'OID' => 'order_id',
 191+ 'RETURNURL' => 'returnto', //TODO: Fund out where the returnto URL is supposed to be coming from.
 192+ );
 193+ }
 194+
 195+ /**
 196+ *
 197+ */
 198+ public function defineReturnValueMap(){
 199+ $this->return_value_map = array(
 200+ 'AOK' => true,
 201+ 'WRONG' => false,
 202+ );
 203+ }
 204+
 205+ /**
 206+ *
 207+ */
 208+ public function defineTransactions(){
 209+ $this->transactions = array();
 210+
 211+ $this->transactions['Test1'] = array(
 212+ 'request' => array(
 213+ 'REQUEST' => array(
 214+ 'ACTION',
 215+ 'ACCOUNT' => array(
 216+ 'MERCHANTID',
 217+ 'PASSWORD',
 218+ 'VERSION',
 219+ 'RETURNURL',
 220+ ),
 221+ 'DONATION' => array(
 222+ 'DONOR',
 223+ 'AMOUNT',
 224+ 'CURRENCYCODE',
 225+ 'LANGUAGECODE',
 226+ 'COUNTRYCODE',
 227+ //'OID', //move this to another test. It's different every time, dorkus.
 228+ )
 229+ )
 230+ ),
 231+ 'values' => array(
 232+ 'ACTION' => 'Donate',
 233+ ),
 234+ );
 235+
 236+ $this->transactions['Test2'] = array(
 237+ 'request' => array(
 238+ 'REQUEST' => array(
 239+ 'ACTION',
 240+ )
 241+ ),
 242+ 'values' => array(
 243+ 'ACTION' => 'Donate2',
 244+ ),
 245+ );
 246+ }
 247+
 248+ /**
 249+ * Take the entire response string, and strip everything we don't care about.
 250+ * For instance: If it's XML, we only want correctly-formatted XML. Headers must be killed off.
 251+ * return a string.
 252+ */
 253+ public function getFormattedResponse( $rawResponse ){
 254+ $xmlString = $this->stripXMLResponseHeaders($rawResponse);
 255+ $displayXML = $this->formatXmlString( $xmlString );
 256+ $realXML = new DomDocument( '1.0' );
 257+ self::log( "Here is the Raw XML: " . $displayXML ); //I am apparently a huge fibber.
 258+ $realXML->loadXML( trim( $xmlString ) );
 259+ return $realXML;
 260+ }
 261+
 262+ /**
 263+ * Parse the response to get the status. Not sure if this should return a bool, or something more... telling.
 264+ */
 265+ public function getResponseStatus( $response ){
 266+
 267+ $aok = true;
 268+
 269+ foreach ( $response->getElementsByTagName( 'RESULT' ) as $node ) {
 270+ if ( array_key_exists( $node->nodeValue, $this->return_value_map ) && $this->return_value_map[$node->nodeValue] !== true ) {
 271+ $aok = false;
 272+ }
 273+ }
 274+
 275+ return $aok;
 276+ }
 277+
 278+ /**
 279+ * Parse the response to get the errors in a format we can log and otherwise deal with.
 280+ * return a key/value array of codes (if they exist) and messages.
 281+ */
 282+ public function getResponseErrors( $response ){
 283+ $errors = array();
 284+ foreach ( $response->getElementsByTagName( 'warning' ) as $node ) {
 285+ $code = '';
 286+ $message = '';
 287+ foreach ( $node->childNodes as $childnode ) {
 288+ if ($childnode->nodeName === "code"){
 289+ $code = $childnode->nodeValue;
 290+ }
 291+ if ($childnode->nodeName === "message"){
 292+ $message = $childnode->nodeValue;
 293+ }
 294+ }
 295+ $errors[$code] = $message;
 296+ }
 297+ return $errors;
 298+ }
 299+
 300+ /**
 301+ * Harvest the data we need back from the gateway.
 302+ * return a key/value array
 303+ */
 304+ public function getResponseData( $response ){
 305+ $data = array();
 306+ foreach ( $response->getElementsByTagName( 'ImportantData' ) as $node ) {
 307+ foreach ( $node->childNodes as $childnode ) {
 308+ if (trim($childnode->nodeValue) != ''){
 309+ $data[$childnode->nodeName] = $childnode->nodeValue;
 310+ }
 311+ }
 312+ }
 313+ self::log( "Returned Data: " . print_r($data, true));
 314+ return $data;
 315+ }
 316+
 317+ public function processResponse( $response ) {
 318+ //TODO: Stuff.
 319+ }
 320+
 321+ public function publicCurrentTransaction( $transaction = '' ){
 322+ $this->currentTransaction( $transaction );
 323+ }
 324+
 325+ public function curl_transaction($data) {
 326+ $data = "";
 327+ $data['result'] = 'BLAH BLAH BLAH BLAH whatever something blah blah<?xml version="1.0"?>' . "\n" . '<XML><Response><Status>AOK</Status><ImportantData><thing>stuff</thing><otherthing>12</otherthing></ImportantData><errorswarnings><warning><code>128</code><message>Your shoe\'s untied...</message></warning><warning><code>45</code><message>Low clearance!</message></warning></errorswarnings></Response></XML>';
 328+ $this->setTransactionResult( $data );
 329+ return true;
 330+ }
 331+}
 332+
Property changes on: trunk/extensions/DonationInterface/tests/Adapter/GatewayAdapterTestCase.php
___________________________________________________________________
Added: svn:eol-style
1333 + native
Index: trunk/extensions/DonationInterface/tests/Adapter/GlobalCollect/AllTests.php
@@ -0,0 +1,61 @@
 2+<?php
 3+/**
 4+ * Wikimedia Foundation
 5+ *
 6+ * LICENSE
 7+ *
 8+ * This program is free software; you can redistribute it and/or modify
 9+ * it under the terms of the GNU General Public License as published by
 10+ * the Free Software Foundation; either version 2 of the License, or
 11+ * (at your option) any later version.
 12+ *
 13+ * This program is distributed in the hope that it will be useful,
 14+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
 15+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 16+ * GNU General Public License for more details.
 17+ *
 18+ *
 19+ * @since r98249
 20+ * @author Jeremy Postlethwaite <jpostlethwaite@wikimedia.org>
 21+ */
 22+
 23+/**
 24+ * @see DonationInterface_Adapter_ServerTestCase
 25+ */
 26+require_once dirname( __FILE__ ) . DIRECTORY_SEPARATOR . 'BankTransferTestCase.php';
 27+require_once dirname( __FILE__ ) . DIRECTORY_SEPARATOR . 'GlobalCollectTestCase.php';
 28+
 29+/**
 30+ * AllTests
 31+ */
 32+class DonationInterface_Adapter_GlobalCollect_AllTests
 33+{
 34+
 35+ /**
 36+ * Run the main test and load any parameters if needed.
 37+ *
 38+ */
 39+ public static function main()
 40+ {
 41+ $parameters = array();
 42+
 43+ PHPUnit_TextUI_TestRunner::run( self::suite(), $parameters );
 44+ }
 45+
 46+ /**
 47+ * Regular suite
 48+ *
 49+ * All tests except those that require output buffering.
 50+ *
 51+ * @return PHPUnit_Framework_TestSuite
 52+ */
 53+ public static function suite()
 54+ {
 55+ $suite = new PHPUnit_Framework_TestSuite( 'Donation Interface - Adapter Suite' );
 56+
 57+ $suite->addTestSuite( 'DonationInterface_Adapter_GlobalCollect_BankTransferTestCase' );
 58+ $suite->addTestSuite( 'DonationInterface_Adapter_GlobalCollect_GlobalCollectTestCase' );
 59+
 60+ return $suite;
 61+ }
 62+}
Property changes on: trunk/extensions/DonationInterface/tests/Adapter/GlobalCollect/AllTests.php
___________________________________________________________________
Added: svn:mime-type
163 + text/plain
Added: svn:keywords
264 + Author Date HeadURL Header Id Revision
Added: svn:eol-style
365 + native
Index: trunk/extensions/DonationInterface/tests/Adapter/GlobalCollect/BankTransferTestCase.php
@@ -0,0 +1,193 @@
 2+<?php
 3+/**
 4+ * Wikimedia Foundation
 5+ *
 6+ * LICENSE
 7+ *
 8+ * This program is free software; you can redistribute it and/or modify
 9+ * it under the terms of the GNU General Public License as published by
 10+ * the Free Software Foundation; either version 2 of the License, or
 11+ * (at your option) any later version.
 12+ *
 13+ * This program is distributed in the hope that it will be useful,
 14+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
 15+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 16+ * GNU General Public License for more details.
 17+ *
 18+ * @since r98249
 19+ * @author Jeremy Postlethwaite <jpostlethwaite@wikimedia.org>
 20+ */
 21+
 22+/**
 23+ * @see DonationInterfaceTestCase
 24+ */
 25+require_once dirname( dirname( dirname( __FILE__ ) ) ) . DIRECTORY_SEPARATOR . 'DonationInterfaceTestCase.php';
 26+
 27+/**
 28+ *
 29+ * @group Fundraising
 30+ * @group Gateways
 31+ * @group DonationInterface
 32+ * @group GlobalCollect
 33+ * @group BankTransfer
 34+ */
 35+class DonationInterface_Adapter_GlobalCollect_BankTransferTestCase extends DonationInterfaceTestCase {
 36+
 37+ /*
 38+ Acceptance Criteria
 39+ 308.1 *Given that a donor wants to donate via an offline bank transfer
 40+ When they submit the following information:
 41+ Amount
 42+ Country (Required – use ISO 3166 codes)
 43+ First Name
 44+ Surname
 45+ Street Address
 46+ Zip
 47+ City
 48+ State (optional)
 49+ Then an XML submission is created and sent to Global Collect with the above information AND:
 50+ MERCHANTREFERENCE (contribution tracking id)
 51+ curencycode
 52+ 308.2
 53+ GIven that a donation order was submitted with the above information
 54+ When the submit button is pressed and a response recieved from Global Collect
 55+ The response is parased and the following information is displayed to the donor:
 56+ PAYMENTREFERENCE
 57+ Account Holder
 58+ Bank Name
 59+ City
 60+ Swift Code
 61+ SpecialID (if provided)
 62+ Bank Account Number
 63+ IBAN
 64+ CountryDescription
 65+ Notes
 66+ We do not need to have donor information stored on our side yet as long as it is sent to Global Collect
 67+ */
 68+
 69+ /**
 70+ * Copied from Katie's test code
 71+ */
 72+ public function testbuildRequestXML() {
 73+
 74+ $this->markTestIncomplete( TESTS_MESSAGE_NOT_IMPLEMENTED );
 75+
 76+ return;
 77+
 78+ $gateway = new TestAdapter();
 79+ $gateway->publicCurrentTransaction( 'Test1' );
 80+ $built = $gateway->buildRequestXML();
 81+ $expected = '<?xml version="1.0"?>' . "\n";
 82+ $expected .= '<XML><REQUEST><ACTION>Donate</ACTION><ACCOUNT><MERCHANTID>128</MERCHANTID><PASSWORD>k4ftw</PASSWORD><VERSION>3.2</VERSION><RETURNURL>http://' . TESTS_HOSTNAME . '/index.php/Donate-thanks/en</RETURNURL></ACCOUNT><DONATION><DONOR>Tester Testington</DONOR><AMOUNT>35000</AMOUNT><CURRENCYCODE>USD</CURRENCYCODE><LANGUAGECODE>en</LANGUAGECODE><COUNTRYCODE>US</COUNTRYCODE></DONATION></REQUEST></XML>' . "\n";
 83+ $this->assertEquals($built, $expected, "The constructed XML for transaction type Test1 does not match our expected.");
 84+
 85+ }
 86+
 87+
 88+ /**
 89+ * testRequestHasRequiredFields
 90+ */
 91+ public function testRequestHasRequiredFields() {
 92+
 93+ $this->markTestIncomplete( TESTS_MESSAGE_NOT_IMPLEMENTED );
 94+
 95+ }
 96+
 97+ /**
 98+ * testReturnDonorResponse
 99+ */
 100+ public function testReturnDonorResponse() {
 101+
 102+ $this->markTestIncomplete( TESTS_MESSAGE_NOT_IMPLEMENTED );
 103+
 104+ }
 105+
 106+ /**
 107+ * testSendToGlobalCollect
 108+ *
 109+ * Adding
 110+ */
 111+ public function testSendToGlobalCollect() {
 112+ $this->markTestIncomplete( TESTS_MESSAGE_NOT_IMPLEMENTED );
 113+
 114+ //global $wgContLang, $wgAuth, $wgMemc, $wgRequest, $wgUser, $wgServer;
 115+ global $wgGlobalCollectGatewayTest;
 116+
 117+ $wgGlobalCollectGatewayTest = true;
 118+
 119+ $_SERVER = array();
 120+
 121+ $_SERVER['SERVER_PROTOCOL'] = 'HTTP/1.1';
 122+ $_SERVER['HTTP_HOST'] = TESTS_HOSTNAME;
 123+ $_SERVER['SERVER_NAME'] = TESTS_HOSTNAME;
 124+ $_SERVER['REQUEST_URI'] = '/index.php/Special:GlobalCollectGateway?form_name=TwoStepAmount';
 125+
 126+ /**
 127+ * @see WebStart.php
 128+ */
 129+ require_once TESTS_WEB_ROOT . '/includes/WebStart.php';
 130+
 131+ $options = array();
 132+
 133+ $options['test'] = true;
 134+ $options['transactionType'] = 'BANK_TRANSFER';
 135+
 136+ $options['postDefaults'] = array(
 137+ 'returnTitle' => true,
 138+ 'returnTo' => 'http://' . TESTS_HOSTNAME . '/index.php/Special:GlobalCollectGatewayResult',
 139+ );
 140+
 141+ $options['testData'] = array(
 142+ 'amount' => "35",
 143+ 'amountOther' => '',
 144+ 'email' => 'test@example.com',
 145+ 'fname' => 'Tester',
 146+ 'mname' => 'T.',
 147+ 'lname' => 'Testington',
 148+ 'street' => '548 Market St.',
 149+ 'city' => 'San Francisco',
 150+ 'state' => 'CA',
 151+ 'zip' => '94104',
 152+ 'country' => 'US',
 153+ 'fname2' => 'Testy',
 154+ 'lname2' => 'Testerson',
 155+ 'street2' => '123 Telegraph Ave.',
 156+ 'city2' => 'Berkeley',
 157+ 'state2' => 'CA',
 158+ 'zip2' => '94703',
 159+ 'country2' => 'US',
 160+ 'size' => 'small',
 161+ 'premium_language' => 'es',
 162+ //'card_num' => TESTS_CREDIT_CARDS_AMEREICAN_EXPRESS_VALID_CARD,
 163+ //'card_type' => 'american',
 164+ //'expiration' => date( 'my', strtotime( '+1 year 1 month' ) ),
 165+ //'cvv' => '001',
 166+ 'currency' => 'USD',
 167+ 'payment_method' => '',
 168+ 'order_id' => '1234567890',
 169+ 'i_order_id' => '1234567890',
 170+ 'numAttempt' => 0,
 171+ 'referrer' => 'http://' . TESTS_HOSTNAME . '/index.php/Special:GlobalCollectGateway?form_name=TwoStepAmount',
 172+ 'utm_source' => '..gc_bt',
 173+ 'utm_medium' => null,
 174+ 'utm_campaign' => null,
 175+ 'language' => 'en',
 176+ 'comment-option' => '',
 177+ 'comment' => '',
 178+ 'email-opt' => 1,
 179+ 'test_string' => '',
 180+ 'token' => '',
 181+ 'contribution_tracking_id' => '',
 182+ 'data_hash' => '',
 183+ 'action' => '',
 184+ 'gateway' => 'globalcollect',
 185+ 'owa_session' => '',
 186+ 'owa_ref' => 'http://localhost/defaultTestData',
 187+ 'transaction_type' => '', // Used by GlobalCollect for payment types
 188+ );
 189+
 190+ $gateway = new GlobalCollectAdapter( $options );
 191+ return $gateway->do_transaction( $options['transactionType'] );
 192+ }
 193+}
 194+
Property changes on: trunk/extensions/DonationInterface/tests/Adapter/GlobalCollect/BankTransferTestCase.php
___________________________________________________________________
Added: svn:mime-type
1195 + text/plain
Added: svn:keywords
2196 + Author Date HeadURL Header Id Revision
Added: svn:eol-style
3197 + native
Index: trunk/extensions/DonationInterface/tests/Adapter/GlobalCollect/GlobalCollectTestCase.php
@@ -0,0 +1,71 @@
 2+<?php
 3+/**
 4+ * Wikimedia Foundation
 5+ *
 6+ * LICENSE
 7+ *
 8+ * This program is free software; you can redistribute it and/or modify
 9+ * it under the terms of the GNU General Public License as published by
 10+ * the Free Software Foundation; either version 2 of the License, or
 11+ * (at your option) any later version.
 12+ *
 13+ * This program is distributed in the hope that it will be useful,
 14+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
 15+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 16+ * GNU General Public License for more details.
 17+ *
 18+ * @since r98249
 19+ * @author Jeremy Postlethwaite <jpostlethwaite@wikimedia.org>
 20+ */
 21+
 22+/**
 23+ * @see DonationInterfaceTestCase
 24+ */
 25+require_once dirname( dirname( dirname( __FILE__ ) ) ) . DIRECTORY_SEPARATOR . 'DonationInterfaceTestCase.php';
 26+
 27+/**
 28+ *
 29+ * @group Fundraising
 30+ * @group Gateways
 31+ * @group DonationInterface
 32+ * @group GlobalCollect
 33+ */
 34+class DonationInterface_Adapter_GlobalCollect_GlobalCollectTestCase extends DonationInterfaceTestCase {
 35+
 36+
 37+ /**
 38+ * testDefineVarMap
 39+ * @covers GlobalCollectAdapter::__construct
 40+ * @covers GlobalCollectAdapter::defineVarMap
 41+ */
 42+ public function testDefineVarMap() {
 43+
 44+ $adapter = new GlobalCollectAdapter();
 45+
 46+ $var_map = array(
 47+ 'ORDERID' => 'order_id',
 48+ 'AMOUNT' => 'amount',
 49+ 'CURRENCYCODE' => 'currency',
 50+ 'LANGUAGECODE' => 'language',
 51+ 'COUNTRYCODE' => 'country',
 52+ 'MERCHANTREFERENCE' => 'order_id',
 53+ 'RETURNURL' => 'returnto', //TODO: Fund out where the returnto URL is supposed to be coming from.
 54+ 'IPADDRESS' => 'user_ip', //TODO: Not sure if this should be OUR ip, or the user's ip. Hurm.
 55+ 'PAYMENTPRODUCTID' => 'card_type',
 56+ 'CVV' => 'cvv',
 57+ 'EXPIRYDATE' => 'expiration',
 58+ 'CREDITCARDNUMBER' => 'card_num',
 59+ 'FIRSTNAME' => 'fname',
 60+ 'SURNAME' => 'lname',
 61+ 'STREET' => 'street',
 62+ 'CITY' => 'city',
 63+ 'STATE' => 'state',
 64+ 'ZIP' => 'zip',
 65+ 'EMAIL' => 'email',
 66+ );
 67+
 68+ $this->assertEquals( $var_map, $adapter->var_map );
 69+
 70+ }
 71+}
 72+
Property changes on: trunk/extensions/DonationInterface/tests/Adapter/GlobalCollect/GlobalCollectTestCase.php
___________________________________________________________________
Added: svn:mime-type
173 + text/plain
Added: svn:keywords
274 + Author Date HeadURL Header Id Revision
Added: svn:eol-style
375 + native
Index: trunk/extensions/DonationInterface/tests/AllTests.php
@@ -0,0 +1,60 @@
 2+<?php
 3+/**
 4+ * Wikimedia Foundation
 5+ *
 6+ * LICENSE
 7+ *
 8+ * This program is free software; you can redistribute it and/or modify
 9+ * it under the terms of the GNU General Public License as published by
 10+ * the Free Software Foundation; either version 2 of the License, or
 11+ * (at your option) any later version.
 12+ *
 13+ * This program is distributed in the hope that it will be useful,
 14+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
 15+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 16+ * GNU General Public License for more details.
 17+ *
 18+ * @since r98249
 19+ * @author Jeremy Postlethwaite <jpostlethwaite@wikimedia.org>
 20+ */
 21+
 22+/**
 23+ * @see DonationInterface_Adapter_AllTests
 24+ */
 25+require_once 'Adapter/AllTests.php';
 26+require_once 'DonationDataTestCase.php';
 27+
 28+/**
 29+ * AllTests
 30+ */
 31+class DonationInterface_AllTests
 32+{
 33+
 34+ /**
 35+ * Run the main test and load any parameters if needed.
 36+ *
 37+ */
 38+ public static function main()
 39+ {
 40+ $parameters = array();
 41+
 42+ PHPUnit_TextUI_TestRunner::run( self::suite(), $parameters );
 43+ }
 44+
 45+ /**
 46+ * Run test suites
 47+ *
 48+ * @return PHPUnit_Framework_TestSuite
 49+ */
 50+ public static function suite()
 51+ {
 52+ $suite = new PHPUnit_Framework_TestSuite( 'Donation Interface Suite' );
 53+
 54+ $suite->addTestSuite( 'DonationInterface_Adapter_AllTests' );
 55+ $suite->addTestSuite( 'DonationInterface_DonationDataTestCase' );
 56+ //$suite->addTest(DonationInterface_Adapter_AllTests::suite());
 57+
 58+ return $suite;
 59+ }
 60+}
 61+
Property changes on: trunk/extensions/DonationInterface/tests/AllTests.php
___________________________________________________________________
Added: svn:mime-type
162 + text/plain
Added: svn:keywords
263 + Author Date HeadURL Header Id Revision
Added: svn:eol-style
364 + native
Index: trunk/extensions/DonationInterface/tests/TestHelper.php
@@ -0,0 +1,84 @@
 2+<?php
 3+/**
 4+ * Wikimedia Foundation
 5+ *
 6+ * LICENSE
 7+ *
 8+ * This program is free software; you can redistribute it and/or modify
 9+ * it under the terms of the GNU General Public License as published by
 10+ * the Free Software Foundation; either version 2 of the License, or
 11+ * (at your option) any later version.
 12+ *
 13+ * This program is distributed in the hope that it will be useful,
 14+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
 15+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 16+ * GNU General Public License for more details.
 17+ *
 18+ *
 19+ * @category UnitTesting
 20+ * @package Fundraising_QueueHandling
 21+ * @license http://www.gnu.org/copyleft/gpl.html GNU GENERAL PUBLIC LICENSE
 22+ * @since r462
 23+ * @author Jeremy Postlethwaite <jpostlethwaite@wikimedia.org>
 24+ */
 25+
 26+/*
 27+ * Include PHPUnit dependencies
 28+ */
 29+require_once 'PHPUnit/Framework/IncompleteTestError.php';
 30+require_once 'PHPUnit/Framework/TestCase.php';
 31+require_once 'PHPUnit/Framework/TestSuite.php';
 32+require_once 'PHPUnit/Runner/Version.php';
 33+require_once 'PHPUnit/TextUI/TestRunner.php';
 34+require_once 'PHPUnit/Util/Filter.php';
 35+
 36+/*
 37+ * Set error reporting to the level to which code must comply.
 38+ */
 39+error_reporting( E_ALL | E_STRICT );
 40+
 41+
 42+/**
 43+ * @see DonationData
 44+ */
 45+require_once dirname( dirname( __FILE__ ) ) . DIRECTORY_SEPARATOR . 'gateway_common/DonationData.php';
 46+
 47+/**
 48+ * @see GatewayAdapter
 49+ */
 50+require_once dirname( dirname( __FILE__ ) ) . DIRECTORY_SEPARATOR . 'gateway_common/gateway.adapter.php';
 51+
 52+/**
 53+ * @see GlobalCollectAdapter
 54+ */
 55+require_once dirname( dirname( __FILE__ ) ) . DIRECTORY_SEPARATOR . 'globalcollect_gateway/globalcollect.adapter.php';
 56+
 57+/**
 58+ * @see ContributionTrackingProcessor
 59+ */
 60+require_once dirname( dirname( dirname( __FILE__ ) ) ) . DIRECTORY_SEPARATOR . 'ContributionTracking/ContributionTracking.processor.php';
 61+
 62+/**
 63+ * TESTS_WEB_ROOT
 64+ *
 65+ * This is similar to $IP, the installation path in Mediawiki.
 66+ */
 67+define( 'TESTS_WEB_ROOT', dirname( dirname( dirname( dirname( __FILE__ ) ) ) ) );
 68+
 69+/*
 70+ * Unit tests are run from the command line.
 71+ *
 72+ * It should be confirmed that this will not affect other tests such as Selenium.
 73+ */
 74+$wgCommandLineMode = true;
 75+
 76+/*
 77+ * Load the user-defined test configuration file, if it exists; otherwise, load
 78+ * the default configuration.
 79+ */
 80+if ( is_file( 'TestConfiguration.php' ) ) {
 81+ require_once 'TestConfiguration.php';
 82+} else {
 83+ require_once 'TestConfiguration.php.dist';
 84+}
 85+
Property changes on: trunk/extensions/DonationInterface/tests/TestHelper.php
___________________________________________________________________
Added: svn:mime-type
186 + text/plain
Added: svn:keywords
287 + Author Date HeadURL Header Id Revision
Added: svn:eol-style
388 + native
Index: trunk/extensions/DonationInterface/tests/DonationDataTestCase.php
@@ -0,0 +1,331 @@
 2+<?php
 3+/**
 4+ * Wikimedia Foundation
 5+ *
 6+ * LICENSE
 7+ *
 8+ * This program is free software; you can redistribute it and/or modify
 9+ * it under the terms of the GNU General Public License as published by
 10+ * the Free Software Foundation; either version 2 of the License, or
 11+ * (at your option) any later version.
 12+ *
 13+ * This program is distributed in the hope that it will be useful,
 14+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
 15+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 16+ * GNU General Public License for more details.
 17+ *
 18+ * @since r98249
 19+ * @author Katie Horn <khorn@wikimedia.org>
 20+ */
 21+
 22+/**
 23+ * @see DonationInterfaceTestCase
 24+ */
 25+require_once dirname( __FILE__ ) . DIRECTORY_SEPARATOR . 'DonationInterfaceTestCase.php';
 26+
 27+/**
 28+ * @group Fundraising
 29+ * @group Splunge
 30+ * @group Gateways
 31+ * @author Katie Horn <khorn@wikimedia.org>
 32+ */
 33+class DonationInterface_DonationDataTestCase extends DonationInterfaceTestCase {
 34+
 35+ /**
 36+ *
 37+ */
 38+ public function __construct(){
 39+ parent::__construct();
 40+ $this->testData = array(
 41+ 'amount' => '128',
 42+ 'email' => 'unittest@example.com',
 43+ 'fname' => 'Testocres',
 44+ 'mname' => 'S.',
 45+ 'lname' => 'McTestingyou',
 46+ 'street' => '123 Fake Street',
 47+ 'city' => 'Springfield',
 48+ 'state' => 'US',
 49+ 'zip' => '99999',
 50+ 'country' => 'US',
 51+ 'fname2' => 'Testor',
 52+ 'lname2' => 'Testeyton',
 53+ 'street2' => '123 W. Grand Ave.',
 54+ 'city2' => 'Oakland',
 55+ 'state2' => 'CA',
 56+ 'zip2' => '94607',
 57+ 'country2' => 'US',
 58+ 'size' => 'Big mcLargeHuge',
 59+ 'premium_language' => 'fr',
 60+ 'card_num' => '42',
 61+ 'card_type' => 'visa',
 62+ 'expiration' => '1138',
 63+ 'cvv' => '665',
 64+ 'currency' => 'USD',
 65+ 'payment_method' => '',
 66+ 'i_order_id' => '1234567890',
 67+ 'numAttempt' => '5',
 68+ 'referrer' => 'http://www.testing.com/',
 69+ 'utm_source' => '..dd',
 70+ 'utm_medium' => 'large',
 71+ 'utm_campaign' => 'yes',
 72+ 'comment-option' => '',
 73+ 'comment' => 'My hovercraft is full of eels',
 74+ 'email-opt' => '',
 75+ 'test_string' => '',
 76+ 'token' => '113811',
 77+ 'contribution_tracking_id' => '',
 78+ 'data_hash' => '',
 79+ 'action' => '',
 80+ 'gateway' => 'chainlink',
 81+ 'owa_session' => '',
 82+ 'owa_ref' => 'http://localhost/importedTestData',
 83+ );
 84+
 85+ }
 86+
 87+
 88+ /**
 89+ * @covers DonationData::__construct
 90+ * @covers DonationData::getData
 91+ * @covers DonationData::populateData
 92+ * @covers DonationData::doCacheStuff
 93+ * @covers DonationData::normalizeAndSanitize
 94+ * @covers DonationData::getVal
 95+ */
 96+ public function testConstruct(){
 97+ $ddObj = new DonationData(''); //as if we were posted.
 98+ $returned = $ddObj->getData();
 99+ $expected = array( 'posted' => '',
 100+ 'amount' => '0.00',
 101+ 'email' => '',
 102+ 'fname' => '',
 103+ 'mname' => '',
 104+ 'lname' => '',
 105+ 'street' => '',
 106+ 'city' => '',
 107+ 'state' => '',
 108+ 'zip' => '',
 109+ 'country' => '',
 110+ 'fname2' => '',
 111+ 'lname2' => '',
 112+ 'street2' => '',
 113+ 'city2' => '',
 114+ 'state2' => '',
 115+ 'zip2' => '',
 116+ 'country2' => '',
 117+ 'size' => '',
 118+ 'premium_language' => 'en',
 119+ 'card_num' => '',
 120+ 'card_type' => '',
 121+ 'expiration' => '',
 122+ 'cvv' => '',
 123+ 'currency' => '',
 124+ 'payment_method' => '',
 125+ 'numAttempt' => '0',
 126+ 'referrer' => '',
 127+ 'utm_source' => '..cc',
 128+ 'utm_medium' => '',
 129+ 'utm_campaign' => '',
 130+ 'language' => '',
 131+ 'comment-option' => '',
 132+ 'comment' => '',
 133+ 'email-opt' => '',
 134+ 'test_string' => '',
 135+ '_cache_' => '',
 136+ 'token' => '',
 137+ 'contribution_tracking_id' => '',
 138+ 'data_hash' => '',
 139+ 'action' => '',
 140+ 'gateway' => '',
 141+ 'owa_session' => '',
 142+ 'owa_ref' => '',
 143+ );
 144+ unset($returned['order_id']);
 145+ unset($returned['i_order_id']);
 146+ $this->assertEquals($returned, $expected, "Staged post data does not match expected (largely empty).");
 147+ }
 148+
 149+ /**
 150+ *
 151+ */
 152+ public function testConstructAsTest(){
 153+ $ddObj = new DonationData('', true); //test mode from the start, no data
 154+ $returned = $ddObj->getData();
 155+ $expected = array(
 156+ 'amount' => '35',
 157+ 'email' => 'test@example.com',
 158+ 'fname' => 'Tester',
 159+ 'mname' => 'T.',
 160+ 'lname' => 'Testington',
 161+ 'street' => '548 Market St.',
 162+ 'city' => 'San Francisco',
 163+ 'state' => 'CA',
 164+ 'zip' => '94104',
 165+ 'country' => 'US',
 166+ 'fname2' => 'Testy',
 167+ 'lname2' => 'Testerson',
 168+ 'street2' => '123 Telegraph Ave.',
 169+ 'city2' => 'Berkeley',
 170+ 'state2' => 'CA',
 171+ 'zip2' => '94703',
 172+ 'country2' => 'US',
 173+ 'size' => 'small',
 174+ 'premium_language' => 'es',
 175+ 'card_num' => '378282246310005',
 176+ 'card_type' => 'american',
 177+ 'expiration' => '1012',
 178+ 'cvv' => '001',
 179+ 'currency' => 'USD',
 180+ 'payment_method' => '',
 181+ 'i_order_id' => '1234567890',
 182+ 'numAttempt' => '0',
 183+ 'referrer' => 'http://www.baz.test.com/index.php?action=foo&amp;action=bar',
 184+ 'utm_source' => '..cc',
 185+ 'utm_medium' => '',
 186+ 'utm_campaign' => '',
 187+ 'language' => 'en',
 188+ 'comment-option' => '',
 189+ 'comment' => '',
 190+ 'email-opt' => '',
 191+ 'test_string' => '',
 192+ 'token' => '',
 193+ 'contribution_tracking_id' => '',
 194+ 'data_hash' => '',
 195+ 'action' => '',
 196+ 'gateway' => 'payflowpro',
 197+ 'owa_session' => '',
 198+ 'owa_ref' => 'http://localhost/defaultTestData',
 199+ );
 200+ unset($returned['order_id']);
 201+
 202+ $this->assertEquals($expected, $returned, "Staged default test data does not match expected.");
 203+ }
 204+
 205+ /**
 206+ *
 207+ */
 208+ public function testRepopulate(){
 209+ $expected = $this->testData;
 210+ //just unset a handfull... doesn't matter what, really.
 211+ unset($expected['comment-option']);
 212+ unset($expected['email-opt']);
 213+ unset($expected['test_string']);
 214+
 215+ $ddObj = new DonationData('');
 216+ $ddObj->populateData(true, $expected); //change to test mode with explicit test data
 217+ $returned = $ddObj->getData();
 218+ //unset these, because they're always new
 219+ unset($returned['order_id']);
 220+ unset($expected['order_id']);
 221+ $this->assertEquals($returned, $expected, "The forced test data did not populate as expected.");
 222+ }
 223+
 224+ /**
 225+ *
 226+ */
 227+ public function testIsSomething(){
 228+ $data = $this->testData;
 229+ unset($data['zip']);
 230+ $ddObj = new DonationData('', true, $data);
 231+ $this->assertEquals($ddObj->isSomething('zip'), false, "Zip should currently be nothing.");
 232+ $this->assertEquals($ddObj->isSomething('lname'), true, "Lname should currently be something.");
 233+ }
 234+
 235+ /**
 236+ *
 237+ */
 238+ public function testGetVal(){
 239+ $data = $this->testData;
 240+ unset($data['zip']);
 241+ $ddObj = new DonationData('', true, $data);
 242+ $this->assertEquals($ddObj->getVal('zip'), null, "Zip should currently be nothing.");
 243+ $this->assertEquals($ddObj->getVal('lname'), 'McTestingyou', "Lname should currently be 'McTestingyou'.");
 244+ }
 245+
 246+ /**
 247+ *
 248+ */
 249+ public function testSetNormalizedAmount_amtGiven() {
 250+ $data = $this->testData;
 251+ $data['amount'] = 'this is not a number';
 252+ $data['amountGiven'] = 42.50;
 253+ //unset($data['zip']);
 254+ $ddObj = new DonationData('', true, $data);
 255+ $returned = $ddObj->getData();
 256+ $this->assertEquals($returned['amount'], '42.50', "Amount was not properly reset");
 257+ $this->assertTrue(!(array_key_exists('amountGiven', $returned)), "amountGiven should have been removed from the data");
 258+ }
 259+
 260+ /**
 261+ *
 262+ */
 263+ public function testSetNormalizedAmount_amount() {
 264+ $data = $this->testData;
 265+ $data['amount'] = 88.15;
 266+ $data['amountGiven'] = 42.50;
 267+ //unset($data['zip']);
 268+ $ddObj = new DonationData('', true, $data);
 269+ $returned = $ddObj->getData();
 270+ $this->assertEquals($returned['amount'], 88.15, "Amount was not properly reset");
 271+ $this->assertTrue(!(array_key_exists('amountGiven', $returned)), "amountGiven should have been removed from the data");
 272+ }
 273+
 274+ /**
 275+ *
 276+ */
 277+ public function testSetNormalizedAmount_neagtiveAmount() {
 278+ $data = $this->testData;
 279+ $data['amount'] = -1;
 280+ $data['amountOther'] = 3.25;
 281+ //unset($data['zip']);
 282+ $ddObj = new DonationData('', true, $data);
 283+ $returned = $ddObj->getData();
 284+ $this->assertEquals($returned['amount'], 3.25, "Amount was not properly reset");
 285+ $this->assertTrue(!(array_key_exists('amountOther', $returned)), "amountOther should have been removed from the data");
 286+ }
 287+
 288+ /**
 289+ *
 290+ */
 291+ public function testSetNormalizedAmount_noGoodAmount() {
 292+ $data = $this->testData;
 293+ $data['amount'] = 'splunge';
 294+ $data['amountGiven'] = 'wombat';
 295+ $data['amountOther'] = 'macedonia';
 296+ //unset($data['zip']);
 297+ $ddObj = new DonationData('', true, $data);
 298+ $returned = $ddObj->getData();
 299+ $this->assertEquals($returned['amount'], 0.00, "Amount was not properly reset");
 300+ $this->assertTrue(!(array_key_exists('amountOther', $returned)), "amountOther should have been removed from the data");
 301+ $this->assertTrue(!(array_key_exists('amountGiven', $returned)), "amountGiven should have been removed from the data");
 302+ }
 303+
 304+ /*
 305+ * TODO: Make sure ALL these functions in DonationData are tested, either directly or through a calling function.
 306+ * I know that's more regression-ish, but I stand by it. :p
 307+ function isCache(){
 308+ function setOwaRefId(){
 309+ function setNormalizedOrderIDs(){
 310+ function generateOrderId() {
 311+ public function sanitizeInput( &$value, $key, $flags=ENT_COMPAT, $double_encode=false ) {
 312+ function setGateway(){
 313+ function doCacheStuff(){
 314+ function getAnnoyingOrderIDLogLinePrefix(){
 315+ public function getEditToken( $salt = '' ) {
 316+ public static function generateToken( $salt = '' ) {
 317+ function matchEditToken( $val, $salt = '' ) {
 318+ function unsetEditToken() {
 319+ public static function ensureSession() {
 320+ public function checkTokens() {
 321+ function wasPosted(){
 322+ public static function getUtmSource( $utm_source = null, $utm_source_id = null ) {
 323+ public function getOptOuts() {
 324+ public function getCleanTrackingData( $clean_optouts = false ) {
 325+ function saveContributionTracking() {
 326+ public static function insertContributionTracking( $tracking_data ) {
 327+ public function updateContributionTracking( $force = false ) {
 328+
 329+ */
 330+}
 331+
 332+
Property changes on: trunk/extensions/DonationInterface/tests/DonationDataTestCase.php
___________________________________________________________________
Added: svn:eol-style
1333 + native
Property changes on: trunk/extensions/DonationInterface/tests/coverage
___________________________________________________________________
Added: svn:ignore
2334 + *
Index: trunk/extensions/DonationInterface/tests/DonationInterfaceTestCase.php
@@ -0,0 +1,37 @@
 2+<?php
 3+/**
 4+ * Wikimedia Foundation
 5+ *
 6+ * LICENSE
 7+ *
 8+ * This program is free software; you can redistribute it and/or modify
 9+ * it under the terms of the GNU General Public License as published by
 10+ * the Free Software Foundation; either version 2 of the License, or
 11+ * (at your option) any later version.
 12+ *
 13+ * This program is distributed in the hope that it will be useful,
 14+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
 15+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 16+ * GNU General Public License for more details.
 17+ *
 18+ * @since r98249
 19+ * @author Jeremy Postlethwaite <jpostlethwaite@wikimedia.org>
 20+ */
 21+
 22+/**
 23+ * @see TestHelper.php
 24+ */
 25+require_once dirname( __FILE__ ) . DIRECTORY_SEPARATOR . 'TestHelper.php';
 26+
 27+/**
 28+ * @group Fundraising
 29+ * @group QueueHandling
 30+ * @group ClassMethod
 31+ * @group ListenerAdapter
 32+ *
 33+ * @category UnitTesting
 34+ * @package Fundraising_QueueHandling
 35+ */
 36+abstract class DonationInterfaceTestCase extends PHPUnit_Framework_TestCase
 37+{
 38+}
Property changes on: trunk/extensions/DonationInterface/tests/DonationInterfaceTestCase.php
___________________________________________________________________
Added: svn:mime-type
139 + text/plain
Added: svn:keywords
240 + Author Date HeadURL Header Id Revision
Added: svn:eol-style
341 + native
Property changes on: trunk/extensions/DonationInterface/tests
___________________________________________________________________
Added: svn:ignore
442 + TestConfiguration.php
Index: trunk/extensions/DonationInterface/globalcollect_gateway/globalcollect_resultswitcher.body.php
@@ -0,0 +1,102 @@
 2+<?php
 3+
 4+class GlobalCollectGatewayResult extends GatewayForm {
 5+
 6+ /**
 7+ * Defines the action to take on a PFP transaction.
 8+ *
 9+ * Possible values include 'process', 'challenge',
 10+ * 'review', 'reject'. These values can be set during
 11+ * data processing validation, for instance.
 12+ *
 13+ * Hooks are exposed to handle the different actions.
 14+ *
 15+ * Defaults to 'process'.
 16+ * @var string
 17+ */
 18+ public $action = 'process';
 19+
 20+ /**
 21+ * An array of form errors
 22+ * @var array
 23+ */
 24+ public $errors = array( );
 25+
 26+ /**
 27+ * Constructor - set up the new special page
 28+ */
 29+ public function __construct() {
 30+ $this->adapter = new GlobalCollectAdapter();
 31+ parent::__construct(); //the next layer up will know who we are.
 32+ }
 33+
 34+ /**
 35+ * Show the special page
 36+ *
 37+ * @param $par Mixed: parameter passed to the page or null
 38+ */
 39+ public function execute( $par ) {
 40+ global $wgRequest, $wgOut, $wgExtensionAssetsPath;
 41+
 42+ $referrer = $wgRequest->getHeader( 'referer' );
 43+
 44+ global $wgServer;
 45+ //TODO: Whitelist! We only want to do this for servers we are configured to like!
 46+ //I didn't do this already, because this may turn out to be backwards anyway. It might be good to do the work in the iframe,
 47+ //and then pop out. Maybe. We're probably going to have to test it a couple different ways, for user experience.
 48+ //However, we're _definitely_ going to need to pop out _before_ we redirect to the thank you or fail pages.
 49+ if ( strpos( $referrer, $wgServer ) === false ) {
 50+ $wgOut->allowClickjacking();
 51+ $wgOut->addModules( 'iframe.liberator' );
 52+ return;
 53+ }
 54+
 55+ $wgOut->addExtensionStyle(
 56+ $wgExtensionAssetsPath . '/DonationInterface/gateway_forms/css/gateway.css?284' .
 57+ $this->adapter->getGlobal( 'CSSVersion' ) );
 58+
 59+ $this->setHeaders();
 60+
 61+
 62+ // dispatch forms/handling
 63+ if ( $this->adapter->checkTokens() ) {
 64+ // Display form for the first time
 65+ $oid = $wgRequest->getText( 'order_id' );
 66+ $adapter_oid = $this->adapter->getData();
 67+ $adapter_oid = $adapter_oid['order_id'];
 68+ if ( $oid && !empty( $oid ) && $oid === $adapter_oid ) {
 69+ if ( !array_key_exists( 'order_status', $_SESSION ) || !array_key_exists( $oid, $_SESSION['order_status'] ) ) {
 70+ $_SESSION['order_status'][$oid] = $this->adapter->do_transaction( 'GET_ORDERSTATUS' );
 71+ $_SESSION['order_status'][$oid]['data']['count'] = 0;
 72+ } else {
 73+ $_SESSION['order_status'][$oid]['data']['count'] = $_SESSION['order_status'][$oid]['data']['count'] + 1;
 74+ }
 75+ $result = $_SESSION['order_status'][$oid];
 76+ $this->displayResultsForDebug( $result );
 77+ //do the switching between the... stuff.
 78+
 79+ switch ( $this->adapter->getTransactionWMFStatus() ) {
 80+ case 'complete':
 81+ case 'pending':
 82+ case 'pending-poke':
 83+ $go = $this->adapter->getThankYouPage();
 84+ break;
 85+ case 'failed':
 86+ $go = $this->adapter->getFailPage();
 87+ break;
 88+ }
 89+
 90+ $wgOut->addHTML( "<br>Redirecting to page $go" );
 91+ $wgOut->redirect( $go );
 92+ }
 93+ $this->adapter->log( "Not posted, or not processed. Showing the form for the first time." );
 94+ } else {
 95+ if ( !$this->adapter->isCache() ) {
 96+ // if we're not caching, there's a token mismatch
 97+ $this->errors['general']['token-mismatch'] = wfMsg( 'payflowpro_gateway-token-mismatch' );
 98+ }
 99+ }
 100+ }
 101+}
 102+
 103+// end class
Property changes on: trunk/extensions/DonationInterface/globalcollect_gateway/globalcollect_resultswitcher.body.php
___________________________________________________________________
Added: svn:eol-style
1104 + native
Index: trunk/extensions/DonationInterface/globalcollect_gateway/forms/html/demo.html
@@ -0,0 +1,162 @@
 2+<table width="100%" cellspacing="0" cellpadding="0" border="0"><tr><td id="appeal" valign="top"><h2 id="appeal-head"> <span class="mw-headline" id="An_appeal_from_Wikipedia_founder_Jimmy_Wales">An appeal from Wikipedia founder Jimmy Wales</span></h2>
 3+<div class="plainlinks" id="appeal-body">I got a lot of funny looks ten years ago when I started talking to people about Wikipedia.
 4+<p>Let’s just say some people were skeptical of the notion that volunteers from all across the world could come together to create a remarkable pool of human knowledge – all for the simple purpose of sharing.</p>
 5+<p>No ads. No agenda. No strings attached.</p>
 6+<p>A decade after its founding, nearly 400 million people use Wikipedia and its sister sites every month - almost a third of the Internet-connected world.</p>
 7+<p>It is the 5th most popular website in the world but Wikipedia isn’t anything like a commercial website. It is a community creation, written by volunteers making one entry at a time. You are part of our community. And I’m writing today to ask you to protect and sustain Wikipedia.</p>
 8+<p>Together, we can keep it free of charge and free of advertising. We can keep it open – you can use the information in Wikipedia any way you want. We can keep it growing – spreading knowledge everywhere, and inviting participation from everyone.</p>
 9+
 10+<p>Each year at this time, we reach out to ask you and others all across the Wikimedia community to help sustain our joint enterprise with a modest donation of $20, $35, $50 or more.</p>
 11+<p>If you value Wikipedia as a source of information – and a source of inspiration – I hope you’ll choose to act right now.</p>
 12+<p>All the best,</p>
 13+<p><b>Jimmy Wales</b></p>
 14+<p>Founder, Wikipedia</p>
 15+<p>P.S. Wikipedia is about the power of people like us to do extraordinary things. People like us write Wikipedia, one word at a time. People like us fund it, one donation at a time. It's proof of our collective potential to change the world.</p>
 16+<p><br />
 17+</p>
 18+</div>
 19+</td><td id="donate" valign="top">
 20+<noscript><div id="noscript"><p id="noscript-msg">It appears that you do not have JavaScript enabled, or your browser does not support it.
 21+In order to provide a safe, secure and pleasant experience, our donation form requires JavaScript.</p><p id="noscript-redirect-msg">If you cannot or do not wish to enable JavaScript, you may still contribute by visiting:</p><p id="noscript-redirect-link"><a href="http://wikimediafoundation.org/wiki/DonateNonJS/en">http://wikimediafoundation.org/wiki/DonateNonJS/en</a></p></div></noscript>
 22+
 23+<h2 id="donate-head">Make your donation now</h2>
 24+<p class='creditcard-error-msg'>#general</p>
 25+<p class='creditcard-error-msg'>#retryMsg</p>
 26+<form name="payment" method="post" action="/index.php/Special:PayflowProGateway?form_name=RapidHtml&amp;ffname=demo" autocomplete="off">
 27+ <div id="payflowpro_gateway-personal-info"><table id="payflow-table-donor">
 28+ <tr>
 29+ <td colspan=2><span class="creditcard-error-msg">#fname</span></td>
 30+ </tr>
 31+ <tr>
 32+ <td colspan=2><span class="creditcard-error-msg">#lname</span></td>
 33+ </tr>
 34+ <tr>
 35+ <td class="label"><label for="fname">Name</label></td>
 36+ <td>
 37+ <input name="fname" size="30" value="@fname" type="text" onfocus="clearField( this, &#039;First&#039; )" maxlength="25" class="required" id="fname" />
 38+ <input name="lname" size="30" value="@lname" type="text" onfocus="clearField( this, &#039;Last&#039; )" maxlength="25" id="lname" />
 39+ </td>
 40+ </tr>
 41+ <tr>
 42+ <td colspan=2><span class="creditcard-error-msg">#emailAdd</span></td>
 43+ </tr>
 44+ <tr>
 45+ <td class="label"><label for="emailAdd">Email address</label></td>
 46+ <td><input name="emailAdd" size="30" value="@emailAdd" type="text" maxlength="64" id="emailAdd" class="fullwidth" /></td>
 47+ </tr>
 48+ <tr>
 49+ <td colspan="2"><span class="creditcard-error-msg">#amount</span></td>
 50+ </tr>
 51+ <tr>
 52+ <td class="label"><label for="amount">Amount</label></td>
 53+ <td>
 54+ <input name="amount" size="7" value="@amount" type="text" maxlength="10" id="amount" />
 55+ <select name="currency_code" id="input_currency_code">
 56+ <option value="USD">USD: U.S. Dollar</option><option value="GBP">GBP: British Pound</option><option value="EUR">EUR: Euro</option><option value="AUD">AUD: Australian Dollar</option><option value="CAD">CAD: Canadian Dollar</option><option value="JPY">JPY: Japanese Yen</option>
 57+ </select>
 58+ </td>
 59+ </tr>
 60+ <tr>
 61+ <td />
 62+ <td><img src="/extensions/DonationInterface/payflowpro_gateway/includes/credit_card_logos.gif" /></td>
 63+ </tr>
 64+ <tr>
 65+ <td class="label"><label for="card_num">Card number</label></td>
 66+ <td><input name="card_num" size="30" value="@card_num" type="text" maxlength="100" id="card_num" class="fullwidth" autocomplete="off" /></td>
 67+ </tr>
 68+ <tr>
 69+ <td colspan=2><span class="creditcard-error-msg">#cvv</span></td>
 70+ <tr>
 71+ <td class="label"><label for="cvv">Security code</label></td>
 72+ <td><input name="cvv" size="5" value="@cvv" type="text" maxlength="10" id="cvv" autocomplete="off" /> <a href="javascript:PopupCVV();">Where is this?</a></td>
 73+ </tr>
 74+ <tr>
 75+ <td class="label"><label for="expiration">Expiration date</label></td>
 76+ <td>
 77+ <select name="mos" id="expiration">
 78+ <option value="01">1 (January)</option><option value="02">2 (February)</option><option value="03">3 (March)</option><option value="04">4 (April)</option><option value="05">5 (May)</option><option value="06">6 (June)</option><option value="07">7 (July)</option><option value="08">8 (August)</option><option value="09">9 (September)</option><option value="10">10 (October)</option><option value="11">11 (November)</option><option value="12">12 (December)</option>
 79+ </select>
 80+ <select name="year" id="year">
 81+ <option value="2010">2010</option><option value="2011">2011</option><option value="2012">2012</option><option value="2013">2013</option><option value="2014">2014</option><option value="2015">2015</option><option value="2016">2016</option><option value="2017">2017</option><option value="2018">2018</option><option value="2019">2019</option><option value="2020">2020</option>
 82+ </select>
 83+ </td>
 84+ </tr>
 85+ <tr>
 86+ <td colspan=2><span class="creditcard-error-msg">#street</span></td>
 87+ </tr>
 88+ <tr>
 89+ <td class="label"><label for="street">Street</label></td>
 90+ <td><input name="street" size="30" value="@street" type="text" maxlength="100" id="street" class="fullwidth" /></td>
 91+ </tr>
 92+ <tr>
 93+ <td colspan=2><span class="creditcard-error-msg">#city</span></td>
 94+ </tr>
 95+ <tr>
 96+ <td class="label"><label for="city">City</label></td>
 97+ <td><input name="city" size="30" value="@city" type="text" maxlength="40" id="city" class="fullwidth" /></td>
 98+ </tr>
 99+ <tr>
 100+ <td colspan=2><span class="creditcard-error-msg">#state</span></td>
 101+ </tr>
 102+ <tr>
 103+ <td class="label"><label for="state">State</label></td>
 104+ <td>
 105+ <select name="state" id="state">
 106+ <option value="YY">Select a State</option><option value="XX">Outside the U.S.</option><option value="AK">Alaska</option><option value="AL">Alabama</option><option value="AR">Arkansas</option><option value="AZ">Arizona</option><option value="CA">California</option><option value="CO">Colorado</option><option value="CT">Connecticut</option><option value="DC">Washington D.C.</option><option value="DE">Delaware</option><option value="FL">Florida</option><option value="GA">Georgia</option><option value="HI">Hawaii</option><option value="IA">Iowa</option><option value="ID">Idaho</option><option value="IL">Illinois</option><option value="IN">Indiana</option><option value="KS">Kansas</option><option value="KY">Kentucky</option><option value="LA">Louisiana</option><option value="MA">Massachusetts</option><option value="MD">Maryland</option><option value="ME">Maine</option><option value="MI">Michigan</option><option value="MN">Minnesota</option><option value="MO">Missouri</option><option value="MS">Mississippi</option><option value="MT">Montana</option><option value="NC">North Carolina</option><option value="ND">North Dakota</option><option value="NE">Nebraska</option><option value="NH">New Hampshire</option><option value="NJ">New Jersey</option><option value="NM">New Mexico</option><option value="NV">Nevada</option><option value="NY">New York</option><option value="OH">Ohio</option><option value="OK">Oklahoma</option><option value="OR">Oregon</option><option value="PA">Pennsylvania</option><option value="PR">Puerto Rico</option><option value="RI">Rhode Island</option><option value="SC">South Carolina</option><option value="SD">South Dakota</option><option value="TN">Tennessee</option><option value="TX">Texas</option><option value="UT">Utah</option><option value="VA">Virginia</option><option value="VT">Vermont</option><option value="WA">Washington</option><option value="WI">Wisconsin</option><option value="WV">West Virginia</option><option value="WY">Wyoming</option><option value="AA">AA</option><option value="AE">AE</option><option value="AP">AP</option>
 107+ </select>
 108+ </td>
 109+ </tr>
 110+ <tr>
 111+ <td colspan=2><span class="creditcard-error-msg">#zip</span></td>
 112+ </tr>
 113+ <tr>
 114+ <td class="label"><label for="zip">Postal code</label></td>
 115+ <td><input name="zip" size="30" value="@zip" type="text" maxlength="9" id="zip" class="fullwidth" /></td>
 116+ </tr>
 117+ <tr>
 118+ <td colspan=2><span class="creditcard-error-msg">#country</span></td>
 119+ </tr>
 120+ <tr>
 121+ <td class="label"><label for="country">Country/Region</label></td>
 122+ <td>
 123+ <select name="country" id="country" onchange="return disableStates( this )">
 124+ <option value="004">Afghanistan</option><option value="008">Albania</option><option value="012">Algeria</option><option value="016">American Samoa</option><option value="020">Andorra</option><option value="024">Angola</option><option value="660">Anguilla</option><option value="010">Antarctica</option><option value="028">Antigua and Barbuda</option><option value="032">Argentina</option><option value="051">Armenia</option><option value="533">Aruba</option><option value="036">Australia</option><option value="040">Austria</option><option value="031">Azerbaijan</option><option value="044">Bahamas</option><option value="048">Bahrain</option><option value="050">Bangladesh</option><option value="052">Barbados</option><option value="112">Belarus</option><option value="056">Belgium</option><option value="084">Belize</option><option value="204">Benin</option><option value="060">Bermuda</option><option value="064">Bhutan</option><option value="068">Bolivia, Plurinational State of</option><option value="070">Bosnia and Herzegovina</option><option value="072">Botswana</option><option value="074">Bouvet Island</option><option value="076">Brazil</option><option value="086">British Indian Ocean Territory</option><option value="096">Brunei Darussalam</option><option value="100">Bulgaria</option><option value="854">Burkina Faso</option><option value="108">Burundi</option><option value="116">Cambodia</option><option value="120">Cameroon</option><option value="124">Canada</option><option value="132">Cape Verde</option><option value="136">Cayman Islands</option><option value="140">Central African Republic</option><option value="148">Chad</option><option value="152">Chile</option><option value="156">China</option><option value="162">Christmas Island</option><option value="166">Cocos (Keeling) Islands</option><option value="017">Colombia</option><option value="174">Comoros</option><option value="178">Congo</option><option value="180">Congo, the Democratic Republic of the</option><option value="184">Cook Islands</option><option value="188">Costa Rica</option><option value="384">Cote D'Ivoire</option><option value="191">Croatia</option><option value="192">Cuba</option><option value="196">Cyprus</option><option value="203">Czech Republic</option><option value="208">Denmark</option><option value="262">Djibouti</option><option value="212">Dominica</option><option value="214">Dominican Republic</option><option value="626">East Timor</option><option value="218">Ecuador</option><option value="818">Egypt</option><option value="222">El Salvador</option><option value="226">Equatorial Guinea</option><option value="232">Eritrea</option><option value="233">Estonia</option><option value="231">Ethiopia</option><option value="238">Falkland Islands (Malvinas)</option><option value="234">Faroe Islands</option><option value="242">Fiji</option><option value="246">Finland</option><option value="250">France</option><option value="254">French Guiana</option><option value="258">French Polynesia</option><option value="260">French Southern Territories</option><option value="266">Gabon</option><option value="270">Gambia</option><option value="268">Georgia</option><option value="276">Germany</option><option value="288">Ghana</option><option value="292">Gibraltar</option><option value="300">Greece</option><option value="304">Greenland</option><option value="308">Grenada</option><option value="312">Guadeloupe</option><option value="316">Guam</option><option value="320">Guatemala</option><option value="324">Guinea</option><option value="624">Guinea-Bissau</option><option value="328">Guyana</option><option value="332">Haiti</option><option value="334">Heard Island and McDonald Islands</option><option value="340">Honduras</option><option value="344">Hong Kong</option><option value="348">Hungary</option><option value="352">Iceland</option><option value="356">India</option><option value="360">Indonesia</option><option value="364">Iran, Islamic Republic of</option><option value="368">Iraq</option><option value="372">Ireland</option><option value="376">Israel</option><option value="380">Italy</option><option value="388">Jamaica</option><option value="392">Japan</option><option value="400">Jordan</option><option value="398">Kazakhstan</option><option value="404">Kenya</option><option value="296">Kiribati</option><option value="408">Korea, Democratic People's Republic of</option><option value="410">Korea, Republic of</option><option value="414">Kuwait</option><option value="417">Kyrgyzstan</option><option value="418">Laos</option><option value="428">Latvia</option><option value="422">Lebanon</option><option value="426">Lesotho</option><option value="430">Liberia</option><option value="434">Libyan Arab Jamahiriya</option><option value="438">Liechtenstein</option><option value="440">Lithuania</option><option value="442">Luxembourg</option><option value="446">Macao</option><option value="807">Macedonia</option><option value="450">Madagascar</option><option value="454">Malawi</option><option value="458">Malaysia</option><option value="462">Maldives</option><option value="466">Mali</option><option value="470">Malta</option><option value="584">Marshall Islands</option><option value="474">Martinique</option><option value="478">Mauritania</option><option value="480">Mauritius</option><option value="175">Mayotte</option><option value="484">Mexico</option><option value="583">Micronesia, Federated States of</option><option value="498">Moldova, Republic of</option><option value="492">Monaco</option><option value="496">Mongolia</option><option value="499">Montenegro</option><option value="500">Montserrat</option><option value="504">Morocco</option><option value="508">Mozambique</option><option value="104">Myanmar</option><option value="516">Namibia</option><option value="520">Nauru</option><option value="524">Nepal</option><option value="528">Netherlands</option><option value="530">Netherlands Antilles</option><option value="540">New Caledonia</option><option value="554">New Zealand</option><option value="558">Nicaragua</option><option value="562">Niger</option><option value="566">Nigeria</option><option value="570">Niue</option><option value="574">Norfolk Island</option><option value="580">Northern Mariana Islands</option><option value="578">Norway</option><option value="512">Oman</option><option value="586">Pakistan</option><option value="585">Palau</option><option value="591">Panama</option><option value="598">Papua New Guinea</option><option value="600">Paraguay</option><option value="604">Peru</option><option value="608">Philippines</option><option value="612">Pitcairn</option><option value="616">Poland</option><option value="620">Portugal</option><option value="630">Puerto Rico</option><option value="634">Qatar</option><option value="642">Romania</option><option value="643">Russian Federation</option><option value="646">Rwanda</option><option value="654">Saint Helena</option><option value="659">Saint Kitts and Nevis</option><option value="662">Saint Lucia</option><option value="666">Saint Pierre and Miquelon</option><option value="670">Saint Vincent and the Grenadines</option><option value="674">San Marino</option><option value="678">Sao Tome and Principe</option><option value="682">Saudi Arabia</option><option value="686">Senegal</option><option value="688">Serbia</option><option value="690">Seychelles</option><option value="694">Sierra Leone</option><option value="702">Singapore</option><option value="703">Slovakia</option><option value="705">Slovenia</option><option value="090">Solomon Islands</option><option value="706">Somalia</option><option value="710">South Africa</option><option value="724">Spain</option><option value="144">Sri Lanka</option><option value="736">Sudan</option><option value="740">Suriname</option><option value="744">Svalbard and Jan Mayen</option><option value="748">Swaziland</option><option value="752">Sweden</option><option value="756">Switzerland</option><option value="760">Syrian Arab Republic</option><option value="158">Taiwan</option><option value="762">Tajikistan</option><option value="834">Tanzania, United Republic of</option><option value="764">Thailand</option><option value="768">Togo</option><option value="772">Tokelau</option><option value="776">Tonga</option><option value="780">Trinidad and Tobago</option><option value="788">Tunisia</option><option value="792">Turkey</option><option value="795">Turkmenistan</option><option value="796">Turks and Caicos Islands</option><option value="798">Tuvalu</option><option value="800">Uganda</option><option value="804">Ukraine</option><option value="784">United Arab Emirates</option><option value="826">United Kingdom</option><option value="840">United States</option><option value="581">United States Minor Outlying Islands</option><option value="858">Uruguay</option><option value="860">Uzbekistan</option><option value="548">Vanuatu</option><option value="336">Vatican City State</option><option value="862">Venezuela, Bolivarian Republic of</option><option value="704">Viet Nam</option><option value="092">Virgin Islands, British</option><option value="850">Virgin Islands, U.S.</option><option value="876">Wallis and Futuna</option><option value="732">Western Sahara</option><option value="882">Western Samoa</option><option value="887">Yemen</option><option value="894">Zambia</option><option value="716">Zimbabwe</option>
 125+ </select>
 126+ </td>
 127+ </tr>
 128+ </table>
 129+ </div>
 130+ <!-- captcha -->
 131+ @captcha
 132+ <!-- end captcha -->
 133+ <div id="payflowpro_gateway-form-submit">
 134+ <div id="mw-donate-submit-button">
 135+ <input class="button-plain" value="Donate by Credit Card" type="submit" />
 136+ </div>
 137+ <div class="mw-donate-submessage" id="payflowpro_gateway-donate-submessage">
 138+ Your credit card will be securely processed.
 139+ </div>
 140+ </div>
 141+ <input type="hidden" value="@utm_source" name="utm_source" />
 142+ <input type="hidden" value="@utm_medium" name="utm_medium" />
 143+ <input type="hidden" value="@utm_campaign" name="utm_campaign" />
 144+ <input type="hidden" value="@language" name="language" />
 145+ <input type="hidden" value="@referrer" name="referrer" />
 146+ <input type="hidden" value="@comment" name="comment" />
 147+ <input type="hidden" value="@comment-option" name="comment-option" />
 148+ <input type="hidden" value="@email-opt" name="email-opt" />
 149+ <input type="hidden" value="CreditCard" name="process" />
 150+ <input type="hidden" value="processed" name="payment_method" />
 151+ <input type="hidden" value="@token" name="token" />
 152+ <input type="hidden" value="@order_id" name="order_id" />
 153+ <input type="hidden" value="@numAttempt" name="numAttempt" />
 154+ <input type="hidden" value="@contribution_tracking_id" name="contribution_tracking_id" />
 155+ <input type="hidden" value="@data_hash" name="data_hash" />
 156+ <input type="hidden" value="@action" name="action" />
 157+ <input type="hidden" value="@owa_session" name="owa_session" />
 158+ <input type="hidden" value="@owa_ref" name="owa_ref" />
 159+</form>
 160+<div class="payflow-cc-form-section" id="payflowpro_gateway-donate-addl-info"><div id="payflowpro_gateway-donate-addl-info-secure-logos"><p class=""><img src="/extensions/DonationInterface/payflowpro_gateway/includes/rapidssl_ssl_certificate-nonanimated.png"></p></div><div id="payflowpro_gateway-donate-addl-info-text"><p class=""><a href="http://wikimediafoundation.org/wiki/Ways_to_Give/en">Other ways to give</a></p><p class="">We do not store your credit card information, and your personal data is subject to our <a href="http://wikimediafoundation.org/wiki/Donor_Privacy_Policy">privacy policy</a>.</p><p class="">Questions or comments? Contact: <a href="mailto:donate@wikimedia.org">donate@wikimedia.org</a></p></div></div></td></tr></table><div class="printfooter">
 161+
 162+Retrieved from "<a href="https://payments.wikimedia.org/index.php/Special:PayflowProGateway">https://payments.wikimedia.org/index.php/Special:PayflowProGateway</a>"</div>
 163+
\ No newline at end of file
Index: trunk/extensions/DonationInterface/globalcollect_gateway/globalcollect_gateway.body.php
@@ -0,0 +1,232 @@
 2+<?php
 3+
 4+class GlobalCollectGateway extends GatewayForm {
 5+
 6+ /**
 7+ * Constructor - set up the new special page
 8+ */
 9+ public function __construct() {
 10+ $this->adapter = new GlobalCollectAdapter();
 11+ parent::__construct(); //the next layer up will know who we are.
 12+ }
 13+
 14+ /**
 15+ * Show the special page
 16+ *
 17+ * @todo
 18+ * - Add transaction type handler
 19+ * - What should a failure on transaction_type issues do? log & message client
 20+ * - Set up BANK_TRANSFER: Story #308
 21+ *
 22+ * @param $par Mixed: parameter passed to the page or null
 23+ */
 24+ public function execute( $par ) {
 25+ global $wgRequest, $wgOut, $wgExtensionAssetsPath;
 26+ $CSSVersion = $this->adapter->getGlobal( 'CSSVersion' );
 27+
 28+ $wgOut->allowClickjacking();
 29+
 30+ $wgOut->addExtensionStyle(
 31+ $wgExtensionAssetsPath . '/DonationInterface/gateway_forms/css/gateway.css?284' .
 32+ $CSSVersion );
 33+
 34+ // Hide unneeded interface elements
 35+ $wgOut->addModules( 'donationInterface.skinOverride' );
 36+
 37+ $gateway_id = $this->adapter->getIdentifier();
 38+
 39+ $this->addErrorMessageScript();
 40+
 41+ // Make the wiki logo not clickable.
 42+ // @fixme can this be moved into the form generators?
 43+ $js = <<<EOT
 44+<script type="text/javascript">
 45+jQuery(document).ready(function() {
 46+ jQuery("div#p-logo a").attr("href","#");
 47+});
 48+</script>
 49+EOT;
 50+ $wgOut->addHeadItem( 'logolinkoverride', $js );
 51+
 52+ $this->setHeaders();
 53+
 54+ /**
 55+ * handle PayPal redirection
 56+ *
 57+ * if paypal redirection is enabled ($wgPayflowProGatewayPaypalURL must be defined)
 58+ * and the PaypalRedirect form value must be true
 59+ */
 60+ if ( $wgRequest->getText( 'PaypalRedirect', 0 ) ) {
 61+ $this->paypalRedirect();
 62+ return;
 63+ }
 64+
 65+ //TODO: This is short-circuiting what I really want to do here.
 66+ //so stop it.
 67+ $data = $this->adapter->getDisplayData();
 68+
 69+ /*
 70+ * The $transactionType should default to false.
 71+ *
 72+ * This is being introduced after INSERT_ORDERWITHPAYMENT was built.
 73+ * Until all INSERT_ORDERWITHPAYMENT can be set in the proper forms, it
 74+ * will be set as the default.
 75+ */
 76+ $transactionType = false;
 77+ $transactionType = ( isset( $data['transaction_type'] ) && !empty( $data['transaction_type'] ) ) ? $data['transaction_type'] : 'INSERT_ORDERWITHPAYMENT';
 78+
 79+ $this->adapter->log( '$transactionType: Default is set to: INSERT_ORDERWITHPAYMENT, this is a temporary hack for backwards compatibility.' );
 80+ $this->adapter->log( 'Setting transaction type: ' . ( string ) $data['transaction_type'] );
 81+
 82+
 83+ // dispatch forms/handling
 84+ if ( $this->adapter->checkTokens() ) {
 85+ if ( $this->adapter->posted && $data['payment_method'] == 'processed' ) {
 86+ // The form was submitted and the payment method has been set
 87+ $this->adapter->log( "Form posted and payment method set." );
 88+
 89+ // Check form for errors
 90+
 91+ $options = array( );
 92+ switch ( $transactionType ) {
 93+
 94+ case 'BANK_TRANSFER':
 95+ $options['creditCard'] = false;
 96+ break;
 97+
 98+ case 'INSERT_ORDERWITHPAYMENT':
 99+ $options['creditCard'] = false;
 100+ break;
 101+
 102+ default:
 103+ $options['creditCard'] = false;
 104+ }
 105+
 106+ $form_errors = $this->validateForm( $data, $this->errors, $options );
 107+ unset( $options );
 108+
 109+ //$form_errors = $this->fnValidateForm( $data, $this->errors );
 110+ // If there were errors, redisplay form, otherwise proceed to next step
 111+ if ( $form_errors ) {
 112+
 113+ $this->displayForm( $data, $this->errors );
 114+ } else { // The submitted form data is valid, so process it
 115+ // allow any external validators to have their way with the data
 116+ // Execute the proper transaction code:
 117+ switch ( $transactionType ) {
 118+
 119+ case 'BANK_TRANSFER':
 120+ $this->executeBankTransfer();
 121+ break;
 122+
 123+ case 'INSERT_ORDERWITHPAYMENT':
 124+ $this->executeInsertOrderWithPayment();
 125+ break;
 126+
 127+ default:
 128+
 129+ $message = 'The transaction type [ ' . $transactionType . ' ] was not found.';
 130+ throw new Exception( $message );
 131+ }
 132+
 133+
 134+ //TODO: add all the hooks back in.
 135+ }
 136+ } else {
 137+ // Display form for the first time
 138+ $oid = $wgRequest->getText( 'order_id' );
 139+ if ( $oid && !empty( $oid ) ) {
 140+ $wgOut->addHTML( "<pre>CAME BACK FROM SOMETHING.</pre>" );
 141+ $result = $this->adapter->do_transaction( 'GET_ORDERSTATUS' );
 142+ $this->displayResultsForDebug( $result );
 143+ }
 144+ $this->adapter->log( "Not posted, or not processed. Showing the form for the first time." );
 145+ $this->displayForm( $data, $this->errors );
 146+ }
 147+ } else {
 148+ if ( !$this->adapter->isCache() ) {
 149+ // if we're not caching, there's a token mismatch
 150+ $this->errors['general']['token-mismatch'] = wfMsg( $gateway_id . '_gateway-token-mismatch' );
 151+ }
 152+ $this->displayForm( $data, $this->errors );
 153+ }
 154+ }
 155+
 156+ /**
 157+ * Execute BANK_TRANSFER
 158+ */
 159+ public function executeBankTransfer() {
 160+
 161+ //global $wgOut;
 162+
 163+ $result = $this->adapter->do_transaction( 'BANK_TRANSFER' );
 164+ $this->adapter->addDonorDataToSession();
 165+
 166+ $this->displayResultsForDebug( $result );
 167+ }
 168+
 169+ /**
 170+ * Execute INSERT_ORDERWITHPAYMENT
 171+ */
 172+ public function executeInsertOrderWithPayment() {
 173+
 174+ global $wgOut;
 175+
 176+ $result = $this->adapter->do_transaction( 'INSERT_ORDERWITHPAYMENT' );
 177+ $this->adapter->addDonorDataToSession();
 178+ //$result = $this->adapter->do_transaction( 'TEST_CONNECTION' );
 179+
 180+ $this->displayResultsForDebug( $result );
 181+
 182+ if ( !empty( $result['data'] ) ) {
 183+
 184+ if ( array_key_exists( 'FORMACTION', $result['data'] ) ) {
 185+ $paymentFrame = Xml::openElement( 'iframe', array(
 186+ 'id' => 'globalcollectframe',
 187+ 'name' => 'globalcollectframe',
 188+ 'width' => '680',
 189+ 'height' => '300',
 190+ 'frameborder' => '0',
 191+ 'style' => 'display:block;',
 192+ 'src' => $result['data']['FORMACTION']
 193+ )
 194+ );
 195+ $paymentFrame .= Xml::closeElement( 'iframe' );
 196+
 197+ $wgOut->addHTML( $paymentFrame );
 198+ }
 199+ }
 200+ }
 201+
 202+ //TODO: Remember why the heck I decided to leave this here...
 203+ //arguably, it's because it's slightly more "view" related, but... still, shouldn't you get stashed
 204+ //in the new GatewayForm class so we can override in chlidren if we feel like it? Odd.
 205+ function addErrorMessageScript() {
 206+ global $wgOut;
 207+ $gateway_id = $this->adapter->getIdentifier();
 208+
 209+ $scriptVars = array(
 210+ $gateway_id . 'GatewayErrorMsgJs' => wfMsg( $gateway_id . '_gateway-error-msg-js' ),
 211+ $gateway_id . 'GatewayErrorMsgEmail' => wfMsg( $gateway_id . '_gateway-error-msg-email' ),
 212+ $gateway_id . 'GatewayErrorMsgAmount' => wfMsg( $gateway_id . '_gateway-error-msg-amount' ),
 213+ $gateway_id . 'GatewayErrorMsgEmailAdd' => wfMsg( $gateway_id . '_gateway-error-msg-emailAdd' ),
 214+ $gateway_id . 'GatewayErrorMsgFname' => wfMsg( $gateway_id . '_gateway-error-msg-fname' ),
 215+ $gateway_id . 'GatewayErrorMsgLname' => wfMsg( $gateway_id . '_gateway-error-msg-lname' ),
 216+ $gateway_id . 'GatewayErrorMsgStreet' => wfMsg( $gateway_id . '_gateway-error-msg-street' ),
 217+ $gateway_id . 'GatewayErrorMsgCity' => wfMsg( $gateway_id . '_gateway-error-msg-city' ),
 218+ $gateway_id . 'GatewayErrorMsgState' => wfMsg( $gateway_id . '_gateway-error-msg-state' ),
 219+ $gateway_id . 'GatewayErrorMsgZip' => wfMsg( $gateway_id . '_gateway-error-msg-zip' ),
 220+ $gateway_id . 'GatewayErrorMsgCountry' => wfMsg( $gateway_id . '_gateway-error-msg-country' ),
 221+ $gateway_id . 'GatewayErrorMsgCardType' => wfMsg( $gateway_id . '_gateway-error-msg-card_type' ),
 222+ $gateway_id . 'GatewayErrorMsgCardNum' => wfMsg( $gateway_id . '_gateway-error-msg-card_num' ),
 223+ $gateway_id . 'GatewayErrorMsgExpiration' => wfMsg( $gateway_id . '_gateway-error-msg-expiration' ),
 224+ $gateway_id . 'GatewayErrorMsgCvv' => wfMsg( $gateway_id . '_gateway-error-msg-cvv' ),
 225+ $gateway_id . 'GatewayCVVExplain' => wfMsg( $gateway_id . '_gateway-cvv-explain' ),
 226+ );
 227+
 228+ $wgOut->addScript( Skin::makeVariablesScript( $scriptVars ) );
 229+ }
 230+
 231+}
 232+
 233+// end class
Property changes on: trunk/extensions/DonationInterface/globalcollect_gateway/globalcollect_gateway.body.php
___________________________________________________________________
Added: svn:eol-style
1234 + native
Index: trunk/extensions/DonationInterface/globalcollect_gateway/globalcollect.adapter.php
@@ -0,0 +1,367 @@
 2+<?php
 3+
 4+class GlobalCollectAdapter extends GatewayAdapter {
 5+ const GATEWAY_NAME = 'Global Collect';
 6+ const IDENTIFIER = 'globalcollect';
 7+ const COMMUNICATION_TYPE = 'xml';
 8+ const GLOBAL_PREFIX = 'wgGlobalCollectGateway';
 9+
 10+ function defineAccountInfo() {
 11+ $this->accountInfo = array(
 12+ 'MERCHANTID' => self::getGlobal( 'MerchantID' ),
 13+ //'IPADDRESS' => '', //TODO: Not sure if this should be OUR ip, or the user's ip. Hurm.
 14+ 'VERSION' => "1.0",
 15+ );
 16+ }
 17+
 18+ function defineVarMap() {
 19+ $this->var_map = array(
 20+ 'ORDERID' => 'order_id',
 21+ 'AMOUNT' => 'amount',
 22+ 'CURRENCYCODE' => 'currency',
 23+ 'LANGUAGECODE' => 'language',
 24+ 'COUNTRYCODE' => 'country',
 25+ 'MERCHANTREFERENCE' => 'order_id',
 26+ 'RETURNURL' => 'returnto', //TODO: Fund out where the returnto URL is supposed to be coming from.
 27+ 'IPADDRESS' => 'user_ip', //TODO: Not sure if this should be OUR ip, or the user's ip. Hurm.
 28+ 'PAYMENTPRODUCTID' => 'card_type',
 29+ 'CVV' => 'cvv',
 30+ 'EXPIRYDATE' => 'expiration',
 31+ 'CREDITCARDNUMBER' => 'card_num',
 32+ 'FIRSTNAME' => 'fname',
 33+ 'SURNAME' => 'lname',
 34+ 'STREET' => 'street',
 35+ 'CITY' => 'city',
 36+ 'STATE' => 'state',
 37+ 'ZIP' => 'zip',
 38+ 'EMAIL' => 'email',
 39+ );
 40+ }
 41+
 42+ function defineReturnValueMap() {
 43+ $this->return_value_map = array(
 44+ 'OK' => true,
 45+ 'NOK' => false,
 46+ );
 47+ $this->addCodeRange( 'GET_ORDERSTATUS', 'STATUSID', 'pending', 0, 70 );
 48+ $this->addCodeRange( 'GET_ORDERSTATUS', 'STATUSID', 'failed', 100, 180 );
 49+ $this->addCodeRange( 'GET_ORDERSTATUS', 'STATUSID', 'pending', 200 );
 50+ $this->addCodeRange( 'GET_ORDERSTATUS', 'STATUSID', 'failed', 220, 280 );
 51+ $this->addCodeRange( 'GET_ORDERSTATUS', 'STATUSID', 'pending', 300 );
 52+ $this->addCodeRange( 'GET_ORDERSTATUS', 'STATUSID', 'failed', 310, 350 );
 53+ $this->addCodeRange( 'GET_ORDERSTATUS', 'STATUSID', 'revised', 400 );
 54+ $this->addCodeRange( 'GET_ORDERSTATUS', 'STATUSID', 'pending_poke', 525 );
 55+ $this->addCodeRange( 'GET_ORDERSTATUS', 'STATUSID', 'pending', 550, 650 );
 56+ $this->addCodeRange( 'GET_ORDERSTATUS', 'STATUSID', 'complete', 800, 975 ); //these are all post-authorized, but technically pre-settled...
 57+ $this->addCodeRange( 'GET_ORDERSTATUS', 'STATUSID', 'complete', 1000, 1050 );
 58+ $this->addCodeRange( 'GET_ORDERSTATUS', 'STATUSID', 'failed', 1100, 99999 );
 59+ }
 60+
 61+ function defineTransactions() {
 62+ $this->transactions = array( );
 63+
 64+ $this->transactions['BANK_TRANSFER'] = array(
 65+ 'request' => array(
 66+ 'REQUEST' => array(
 67+ 'ACTION',
 68+ 'META' => array(
 69+ 'MERCHANTID',
 70+ // 'IPADDRESS',
 71+ 'VERSION'
 72+ ),
 73+ 'PARAMS' => array(
 74+ 'ORDER' => array(
 75+ 'ORDERID',
 76+ 'AMOUNT',
 77+ 'CURRENCYCODE',
 78+ 'LANGUAGECODE',
 79+ 'COUNTRYCODE',
 80+ 'MERCHANTREFERENCE'
 81+ ),
 82+ 'PAYMENT' => array(
 83+ 'PAYMENTPRODUCTID',
 84+ 'AMOUNT',
 85+ 'CURRENCYCODE',
 86+ 'LANGUAGECODE',
 87+ 'COUNTRYCODE',
 88+ 'HOSTEDINDICATOR',
 89+ 'RETURNURL',
 90+// 'INVOICENUMBER',
 91+// 'CUSTOMERBANKNAME',
 92+// 'CUSTOMERACCOUNTHOLDERNAME',
 93+// 'CUSTOMERBANKACCOUNT',
 94+// 'CUSTOMERBANKCITY',
 95+ 'FIRSTNAME',
 96+ 'SURNAME',
 97+ 'STREET',
 98+ 'CITY',
 99+ 'STATE',
 100+ 'ZIP',
 101+ 'EMAIL',
 102+ )
 103+ )
 104+ )
 105+ ),
 106+ 'values' => array(
 107+ 'ACTION' => 'INSERT_ORDERWITHPAYMENT',
 108+ 'HOSTEDINDICATOR' => '1',
 109+ 'PAYMENTPRODUCTID' => '11',
 110+ ),
 111+ );
 112+
 113+ $this->transactions['INSERT_ORDERWITHPAYMENT'] = array(
 114+ 'request' => array(
 115+ 'REQUEST' => array(
 116+ 'ACTION',
 117+ 'META' => array(
 118+ 'MERCHANTID',
 119+ // 'IPADDRESS',
 120+ 'VERSION'
 121+ ),
 122+ 'PARAMS' => array(
 123+ 'ORDER' => array(
 124+ 'ORDERID',
 125+ 'AMOUNT',
 126+ 'CURRENCYCODE',
 127+ 'LANGUAGECODE',
 128+ 'COUNTRYCODE',
 129+ 'MERCHANTREFERENCE'
 130+ ),
 131+ 'PAYMENT' => array(
 132+ 'PAYMENTPRODUCTID',
 133+ 'AMOUNT',
 134+ 'CURRENCYCODE',
 135+ 'LANGUAGECODE',
 136+ 'COUNTRYCODE',
 137+ 'HOSTEDINDICATOR',
 138+ 'RETURNURL',
 139+// 'CVV',
 140+// 'EXPIRYDATE',
 141+// 'CREDITCARDNUMBER',
 142+ 'FIRSTNAME',
 143+ 'SURNAME',
 144+ 'STREET',
 145+ 'CITY',
 146+ 'STATE',
 147+ 'ZIP',
 148+ 'EMAIL',
 149+ )
 150+ )
 151+ )
 152+ ),
 153+ 'values' => array(
 154+ 'ACTION' => 'INSERT_ORDERWITHPAYMENT',
 155+ 'HOSTEDINDICATOR' => '1',
 156+ //'PAYMENTPRODUCTID' => '11',
 157+ ),
 158+ 'do_validation' => true,
 159+ 'addDonorDataToSession' => true,
 160+ );
 161+
 162+ $this->transactions['TEST_CONNECTION'] = array(
 163+ 'request' => array(
 164+ 'REQUEST' => array(
 165+ 'ACTION',
 166+ 'META' => array(
 167+ 'MERCHANTID',
 168+// 'IPADDRESS',
 169+ 'VERSION'
 170+ ),
 171+ 'PARAMS' => array( )
 172+ )
 173+ ),
 174+ 'values' => array(
 175+ 'ACTION' => 'TEST_CONNECTION'
 176+ )
 177+ );
 178+
 179+ $this->transactions['GET_ORDERSTATUS'] = array(
 180+ 'request' => array(
 181+ 'REQUEST' => array(
 182+ 'ACTION',
 183+ 'META' => array(
 184+ 'MERCHANTID',
 185+// 'IPADDRESS',
 186+ 'VERSION'
 187+ ),
 188+ 'PARAMS' => array(
 189+ 'ORDER' => array(
 190+ 'ORDERID',
 191+ ),
 192+ )
 193+ )
 194+ ),
 195+ 'values' => array(
 196+ 'ACTION' => 'GET_ORDERSTATUS',
 197+ 'VERSION' => '2.0'
 198+ ),
 199+ 'do_processhooks' => true,
 200+ 'pullDonorDataFromSession' => true,
 201+ 'loop_for_status' => array(
 202+ //'pending',
 203+ 'pending_poke',
 204+ 'complete',
 205+ 'failed',
 206+ 'revised',
 207+ )
 208+ );
 209+ }
 210+
 211+ /**
 212+ * Take the entire response string, and strip everything we don't care about.
 213+ * For instance: If it's XML, we only want correctly-formatted XML. Headers must be killed off.
 214+ * return a string.
 215+ */
 216+ function getFormattedResponse( $rawResponse ) {
 217+ $xmlString = $this->stripXMLResponseHeaders( $rawResponse );
 218+ $displayXML = $this->formatXmlString( $xmlString );
 219+ $realXML = new DomDocument( '1.0' );
 220+ self::log( "Here is the Raw XML: " . $displayXML ); //I am apparently a huge fibber.
 221+ $realXML->loadXML( trim( $xmlString ) );
 222+ return $realXML;
 223+ }
 224+
 225+ /**
 226+ * Parse the response to get the status. Not sure if this should return a bool, or something more... telling.
 227+ */
 228+ function getResponseStatus( $response ) {
 229+
 230+ $aok = true;
 231+
 232+ foreach ( $response->getElementsByTagName( 'RESULT' ) as $node ) {
 233+ if ( array_key_exists( $node->nodeValue, $this->return_value_map ) && $this->return_value_map[$node->nodeValue] !== true ) {
 234+ $aok = false;
 235+ }
 236+ }
 237+
 238+ return $aok;
 239+ }
 240+
 241+ /**
 242+ * Parse the response to get the errors in a format we can log and otherwise deal with.
 243+ * return a key/value array of codes (if they exist) and messages.
 244+ */
 245+ function getResponseErrors( $response ) {
 246+ $errors = array( );
 247+ foreach ( $response->getElementsByTagName( 'ERROR' ) as $node ) {
 248+ $code = '';
 249+ $message = '';
 250+ foreach ( $node->childNodes as $childnode ) {
 251+ if ( $childnode->nodeName === "CODE" ) {
 252+ $code = $childnode->nodeValue;
 253+ }
 254+ if ( $childnode->nodeName === "MESSAGE" ) {
 255+ $message = $childnode->nodeValue;
 256+ }
 257+ }
 258+ $errors[$code] = $message;
 259+ }
 260+ return $errors;
 261+ }
 262+
 263+ /**
 264+ * Harvest the data we need back from the gateway.
 265+ * return a key/value array
 266+ */
 267+ function getResponseData( $response ) {
 268+ $data = array( );
 269+
 270+ $transaction = $this->currentTransaction();
 271+
 272+ switch ( $transaction ) {
 273+ case 'BANK_TRANSFER':
 274+ $data = $this->xmlChildrenToArray( $response, 'ROW' );
 275+ $data['ORDER'] = $this->xmlChildrenToArray( $response, 'ORDER' );
 276+ $data['PAYMENT'] = $this->xmlChildrenToArray( $response, 'PAYMENT' );
 277+ break;
 278+ case 'INSERT_ORDERWITHPAYMENT':
 279+ $data = $this->xmlChildrenToArray( $response, 'ROW' );
 280+ $data['ORDER'] = $this->xmlChildrenToArray( $response, 'ORDER' );
 281+ $data['PAYMENT'] = $this->xmlChildrenToArray( $response, 'PAYMENT' );
 282+ break;
 283+ case 'GET_ORDERSTATUS':
 284+ $data = $this->xmlChildrenToArray( $response, 'STATUS' );
 285+ $this->setTransactionWMFStatus( $this->findCodeAction( 'GET_ORDERSTATUS', 'STATUSID', $data['STATUSID'] ) );
 286+ $data['ORDER'] = $this->xmlChildrenToArray( $response, 'ORDER' );
 287+ break;
 288+ }
 289+
 290+
 291+ self::log( "Returned Data: " . print_r( $data, true ) );
 292+ return $data;
 293+ }
 294+
 295+ function processResponse( $response ) {
 296+ //set the transaction result message
 297+ $responseStatus = isset( $response['STATUSID'] ) ? $response['STATUSID'] : '';
 298+ $this->setTransactionResult( "Response Status: " . $responseStatus, 'txn_message' ); //TODO: Translate for GC.
 299+ $this->setTransactionResult( $this->getData( 'order_id' ), 'gateway_txn_id' );
 300+ }
 301+
 302+ /**
 303+ * The default section of the switch will be hit on first time forms. This
 304+ * should be okay, because we are only concerned with staged_vars that have
 305+ * been posted.
 306+ *
 307+ * Credit cards staged_vars are set to ensure form failures on validation in
 308+ * the default case. This should prevent accidental form submission with
 309+ * unknown transaction types.
 310+ */
 311+ function defineStagedVars() {
 312+
 313+ //OUR field names.
 314+ $this->staged_vars = array(
 315+ 'amount',
 316+ 'card_type',
 317+ //'card_num',
 318+ 'returnto',
 319+ 'order_id', //This may or may not oughta-be-here...
 320+ );
 321+ }
 322+
 323+ protected function stage_amount( $type = 'request' ) {
 324+ switch ( $type ) {
 325+ case 'request':
 326+ $this->postdata['amount'] = $this->postdata['amount'] * 100;
 327+ break;
 328+ case 'response':
 329+ $this->postdata['amount'] = $this->postdata['amount'] / 100;
 330+ break;
 331+ }
 332+ }
 333+
 334+ protected function stage_card_type( $type = 'request' ) {
 335+
 336+ $types = array(
 337+ 'visa' => '1',
 338+ 'mastercard' => '3',
 339+ 'american' => '2',
 340+ 'discover' => '128'
 341+ );
 342+
 343+ if ( $type === 'response' ) {
 344+ $types = array_flip( $types );
 345+ }
 346+
 347+ if ( ( array_key_exists( 'card_type', $this->postdata ) ) && array_key_exists( $this->postdata['card_type'], $types ) ) {
 348+ $this->postdata['card_type'] = $types[$this->postdata['card_type']];
 349+ } else {
 350+ //$this->postdata['card_type'] = '';
 351+ //iono: maybe nothing?
 352+ }
 353+ }
 354+
 355+ protected function stage_card_num( $type = 'request' ) {
 356+ //I realize that the $type isn't used. Voodoo.
 357+ if ( array_key_exists( 'card_num', $this->postdata ) ) {
 358+ $this->postdata['card_num'] = str_replace( ' ', '', $this->postdata['card_num'] );
 359+ }
 360+ }
 361+
 362+ protected function stage_returnto( $type = 'request' ) {
 363+ if ( $type === 'request' ) {
 364+ $this->postdata['returnto'] = $this->postdata['returnto'] . "?order_id=" . $this->postdata['order_id'];
 365+ }
 366+ }
 367+
 368+}
\ No newline at end of file
Property changes on: trunk/extensions/DonationInterface/globalcollect_gateway/globalcollect.adapter.php
___________________________________________________________________
Added: svn:eol-style
1369 + native
Index: trunk/extensions/DonationInterface/gateway_common/gateway.adapter.php
@@ -0,0 +1,1266 @@
 2+<?php
 3+
 4+/**
 5+ * Wikimedia Foundation
 6+ *
 7+ * LICENSE
 8+ *
 9+ * This program is free software; you can redistribute it and/or modify
 10+ * it under the terms of the GNU General Public License as published by
 11+ * the Free Software Foundation; either version 2 of the License, or
 12+ * (at your option) any later version.
 13+ *
 14+ * This program is distributed in the hope that it will be useful,
 15+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
 16+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 17+ * GNU General Public License for more details.
 18+ *
 19+ */
 20+
 21+/**
 22+ * GatewayType Interface
 23+ *
 24+ */
 25+interface GatewayType {
 26+ //all the particulars of the child classes. Aaaaall.
 27+
 28+ /**
 29+ * Take the entire response string, and strip everything we don't care about.
 30+ * For instance: If it's XML, we only want correctly-formatted XML. Headers must be killed off.
 31+ * return a string.
 32+ */
 33+ function getFormattedResponse( $rawResponse );
 34+
 35+ /**
 36+ * Parse the response to get the status. Not sure if this should return a bool, or something more... telling.
 37+ */
 38+ function getResponseStatus( $response );
 39+
 40+ /**
 41+ * Parse the response to get the errors in a format we can log and otherwise deal with.
 42+ * return a key/value array of codes (if they exist) and messages.
 43+ */
 44+ function getResponseErrors( $response );
 45+
 46+ /**
 47+ * Harvest the data we need back from the gateway.
 48+ * return a key/value array
 49+ */
 50+ function getResponseData( $response );
 51+
 52+ /**
 53+ * Actually do... stuff. Here.
 54+ * TODO: Better comment.
 55+ * Process the entire response gott'd by the last four functions.
 56+ */
 57+ function processResponse( $response );
 58+
 59+ /**
 60+ * Should be a list of our variables that need special staging.
 61+ * @see $this->staged_vars
 62+ */
 63+ function defineStagedVars();
 64+
 65+ /**
 66+ * defineTransactions will define the $transactions array.
 67+ * The array will contain everything we need to know about the request structure for all the transactions we care about,
 68+ * for the current gateway.
 69+ * First array key: Some way for us to id the transaction. Doesn't actually have to be the gateway's name for it, but I'm going with that until I have a reason not to.
 70+ * Second array key:
 71+ * 'request' contains the structure of that request. Leaves in the array tree will eventually be mapped to actual values of ours,
 72+ * according to the precidence established in the getValue function.
 73+ * 'values' contains default values for the transaction. Things that are typically not overridden should go here.
 74+ */
 75+ function defineTransactions();
 76+
 77+ /**
 78+ * defineVarMap needs to set up the $var_map array.
 79+ * Keys = the name (or node name) value in the gateway transaction
 80+ * Values = the mediawiki field name for the corresponding piece of data.
 81+ */
 82+ function defineVarMap();
 83+
 84+ /**
 85+ * defineAccountInfo needs to set up the $accountInfo array.
 86+ * Keys = the name (or node name) value in the gateway transaction
 87+ * Values = The actual values for those keys. Probably have to access a global or two. (use getGlobal()!)
 88+ */
 89+ function defineAccountInfo();
 90+
 91+ /**
 92+ * defineReturnValueMap sets up the $return_value_map array.
 93+ * Keys = The different constants that may be contained as values in the gateway's response.
 94+ * Values = what that string constant means to mediawiki.
 95+ */
 96+ function defineReturnValueMap();
 97+}
 98+
 99+/**
 100+ * GatewayAdapter
 101+ *
 102+ */
 103+abstract class GatewayAdapter implements GatewayType {
 104+
 105+ //Contains the map of THEIR var names, to OURS.
 106+ //I'd have gone the other way, but we'd run into 1:many pretty quick.
 107+ protected $var_map;
 108+ protected $accountInfo;
 109+ protected $url;
 110+ protected $transactions;
 111+
 112+ /**
 113+ * $transaction_type will be set in the GatewayForm::execute()
 114+ *
 115+ * @var string|false
 116+ *
 117+ * @see GatewayForm::execute()
 118+ */
 119+ protected $transaction_type = false;
 120+
 121+ /**
 122+ * Staged variables. This is affected by the transaction type.
 123+ *
 124+ * @var array $staged_vars
 125+ */
 126+ protected $staged_vars = array( );
 127+ protected $return_value_map;
 128+ protected $postdata;
 129+ protected $postdatadefaults;
 130+ protected $xmlDoc;
 131+ protected $dataObj;
 132+ protected $transaction_results;
 133+ protected $form_class;
 134+ protected $validation_errors;
 135+ public $action; //Currently, hooks need to be able to set this directly.
 136+ public $debugarray; //TODO: Take me out.
 137+
 138+ //ALL OF THESE need to be redefined in the children. Much voodoo depends on the accuracy of these constants.
 139+ const GATEWAY_NAME = 'Donation Gateway';
 140+ const IDENTIFIER = 'donation';
 141+ const COMMUNICATION_TYPE = 'xml'; //this needs to be either 'xml' or 'namevalue'
 142+ const GLOBAL_PREFIX = 'wgDonationGateway'; //...for example.
 143+
 144+ /**
 145+ * Constructor
 146+ *
 147+ * @param array $options
 148+ * OPTIONAL - You may set options for testing
 149+ * - testData - Submit test data
 150+ *
 151+ * @see DonationData
 152+ */
 153+ public function __construct( $options = array( ) ) {
 154+ global $wgRequest;
 155+
 156+ // Extract the options
 157+ extract( $options );
 158+
 159+ $testData = isset( $testData ) ? $testData : false;
 160+ $postDefaults = isset( $postDefaults ) ? $postDefaults : false;
 161+
 162+ if ( !self::getGlobal( 'Test' ) ) {
 163+ $this->url = self::getGlobal( 'URL' );
 164+
 165+ // Only submit test data if we are in test mode.
 166+ $testData = false;
 167+ } else {
 168+ $this->url = self::getGlobal( 'TestingURL' );
 169+ }
 170+
 171+ $this->dataObj = new DonationData( get_called_class(), self::getGlobal( 'Test' ), $testData );
 172+
 173+ $this->postdata = $this->dataObj->getData();
 174+ //TODO: Fix this a bit.
 175+
 176+ $this->posted = $wgRequest->wasPosted();
 177+
 178+ $this->setPostDefaults( $postDefaults );
 179+ $this->defineTransactions();
 180+ $this->defineVarMap();
 181+ $this->defineAccountInfo();
 182+ $this->defineReturnValueMap();
 183+
 184+ //Don't bother setting the transaction type if it's not something.
 185+ if ( $this->dataObj->isSomething( 'transaction_type' ) ) {
 186+ $this->currentTransaction( $this->postdata['transaction_type'] );
 187+ }
 188+
 189+ $this->displaydata = $this->postdata;
 190+ $this->stageData();
 191+ }
 192+
 193+ /**
 194+ * Override this in children if you want different defaults.
 195+ */
 196+ function setPostDefaults( $options = array( ) ) {
 197+
 198+ // Extract the options
 199+ if ( is_array( $options ) ) {
 200+ extract( $options );
 201+ }
 202+
 203+ $returnTitle = isset( $returnTitle ) ? $returnTitle : Title::newFromText( 'Special:GlobalCollectGatewayResult' );
 204+ $returnTo = isset( $returnTo ) ? $returnTo : $returnTitle->getFullURL();
 205+
 206+ $this->postdatadefaults = array(
 207+ 'order_id' => '112358' . rand(),
 208+ 'amount' => '11.38',
 209+ 'currency' => 'USD',
 210+ 'language' => 'en',
 211+ 'country' => 'US',
 212+ 'returnto' => $returnTo,
 213+ 'user_ip' => ( self::getGlobal( 'Test' ) ) ? '12.12.12.12' : wfGetIP(), // current user's IP address
 214+ 'card_type' => 'visa',
 215+ );
 216+ }
 217+
 218+ function getThankYouPage() {
 219+ global $wgLang;
 220+ $language = $wgLang->getCode();
 221+ $page = self::getGlobal( "ThankYouPage" ) . "/$language";
 222+// $returnTitle = Title::newFromText( $page );
 223+// $returnto = $returnTitle->getFullURL();
 224+ return $page;
 225+ }
 226+
 227+ function getFailPage() {
 228+ global $wgLang;
 229+ $language = $wgLang->getCode();
 230+ $page = self::getGlobal( "FailPage" ) . "/$language";
 231+ $returnTitle = Title::newFromText( $page );
 232+ $returnto = $returnTitle->getFullURL();
 233+ return $returnto;
 234+ }
 235+
 236+ function checkTokens() {
 237+ return $this->dataObj->checkTokens();
 238+ }
 239+
 240+ function getData( $val = '' ) {
 241+ if ( empty( $val ) ) {
 242+ return $this->postdata;
 243+ } else {
 244+ if ( array_key_exists( $val, $this->postdata ) ) {
 245+ return $this->postdata[$val];
 246+ } else {
 247+ return null;
 248+ }
 249+ }
 250+ }
 251+
 252+ function getDisplayData() {
 253+ return $this->displaydata;
 254+ }
 255+
 256+ function isCache() {
 257+ return $this->dataObj->isCache();
 258+ }
 259+
 260+ /**
 261+ * This function is important.
 262+ * All the globals in Donation Interface should be accessed in this manner
 263+ * if they are meant to have a default value, but can be overridden by any
 264+ * of the gateways. It will check to see if a gateway-specific global
 265+ * exists, and if one is not set, it will pull the default from the
 266+ * wgDonationInterface definitions. Through this function, it is no longer
 267+ * necessary to define gateway-specific globals in LocalSettings unless you
 268+ * wish to override the default value for all gateways.
 269+ * @staticvar array $gotten A cache of all the globals we've already...
 270+ * gotten.
 271+ * @param type $varname The global value we're looking for. It will first
 272+ * look for a global named for the instantiated gateway's GLOBAL_PREFIX,
 273+ * plus the $varname value. If that doesn't come up with anything that has
 274+ * been set, it will use the default value for all of donation interface,
 275+ * stored in $wgDonationInterface . $varname.
 276+ * @return mixed The configured value for that gateway if it exists. If not,
 277+ * the configured value for Donation Interface if it exists or not.
 278+ */
 279+ static function getGlobal( $varname ) {
 280+ static $gotten = array( ); //cache.
 281+ if ( !array_key_exists( $varname, $gotten ) ) {
 282+ $globalname = self::getGlobalPrefix() . $varname;
 283+ global $$globalname;
 284+ if ( !isset( $$globalname ) ) {
 285+ $globalname = "wgDonationInterface" . $varname;
 286+ global $$globalname; //set or not. This is fine.
 287+ }
 288+ $gotten[$varname] = $$globalname;
 289+ }
 290+ return $gotten[$varname];
 291+ }
 292+
 293+ function getValue( $gateway_field_name, $token = false ) {
 294+ if ( empty( $this->transactions ) ) {
 295+ //TODO: These dies should all throw exceptions or something less completely fatal.
 296+ $msg = self::getGatewayName() . ': Transactions structure is empty! No transaction can be constructed.';
 297+ self::log( $msg, LOG_CRIT );
 298+ throw new MWException( $msg );
 299+ }
 300+ //How do we determine the value of a field asked for in a particular transaction?
 301+ $transaction = $this->currentTransaction();
 302+
 303+ //If there's a hard-coded value in the transaction definition, use that.
 304+ if ( !empty( $transaction ) ) {
 305+ if ( array_key_exists( $transaction, $this->transactions ) && is_array( $this->transactions[$transaction] ) &&
 306+ array_key_exists( 'values', $this->transactions[$transaction] ) &&
 307+ array_key_exists( $gateway_field_name, $this->transactions[$transaction]['values'] ) ) {
 308+ return $this->transactions[$transaction]['values'][$gateway_field_name];
 309+ }
 310+ }
 311+
 312+ //if it's account info, use that.
 313+ //$this->accountInfo;
 314+ if ( array_key_exists( $gateway_field_name, $this->accountInfo ) ) {
 315+ return $this->accountInfo[$gateway_field_name];
 316+ }
 317+
 318+
 319+ //If there's a value in the post data (name-translated by the var_map), use that.
 320+ if ( array_key_exists( $gateway_field_name, $this->var_map ) ) {
 321+ if ( $token === true ) { //we just want the field name to use, so short-circuit all that mess.
 322+ return '@' . $this->var_map[$gateway_field_name];
 323+ }
 324+ if ( array_key_exists( $this->var_map[$gateway_field_name], $this->postdata ) &&
 325+ $this->postdata[$this->var_map[$gateway_field_name]] !== '' ) {
 326+ //if it was sent, use that.
 327+ return $this->postdata[$this->var_map[$gateway_field_name]];
 328+ } else {
 329+ //return the default for that form value
 330+
 331+ $tempField = isset( $this->var_map[ $gateway_field_name ] ) ? $this->var_map[ $gateway_field_name ] : false;
 332+
 333+ $tempValue = '';
 334+
 335+ if ( $tempField && isset( $this->postdatadefaults[ $tempField ] ) ) {
 336+ $tempValue = $this->postdatadefaults[ $tempField ];
 337+ }
 338+
 339+ return $tempValue;
 340+ }
 341+ }
 342+
 343+ //not in the map, or hard coded. What then?
 344+ //Complain furiously, for your code is faulty.
 345+ $msg = self::getGatewayName() . ': Requested value ' . $gateway_field_name . ' cannot be found in the transactions structure.';
 346+ self::log( $msg, LOG_CRIT );
 347+ throw new MWException( $msg );
 348+ }
 349+
 350+ function buildRequestNameValueString() {
 351+ $structure = $this->transactions[$this->currentTransaction()]['request'];
 352+ if ( !is_array( $structure ) ) {
 353+ return '';
 354+ }
 355+
 356+ $queryvals = array( );
 357+
 358+ //we are going to assume a flat array, because... namevalue.
 359+ foreach ( $structure as $fieldname ) {
 360+ $fieldvalue = $this->getValue( $fieldname );
 361+ if ( $fieldvalue !== '' && $fieldvalue !== false ) {
 362+ $queryvals[] = $fieldname . '[' . strlen( $fieldvalue ) . ']=' . $fieldvalue;
 363+ }
 364+ }
 365+
 366+ $ret = implode( '&', $queryvals );
 367+ return $ret;
 368+ }
 369+
 370+ function buildRequestXML() {
 371+ $this->xmlDoc = new DomDocument( '1.0' );
 372+ $node = $this->xmlDoc->createElement( 'XML' );
 373+
 374+ $structure = $this->transactions[$this->currentTransaction()]['request'];
 375+
 376+ $this->buildTransactionNodes( $structure, $node );
 377+ $this->xmlDoc->appendChild( $node );
 378+ return $this->xmlDoc->saveXML();
 379+ }
 380+
 381+ function buildTransactionNodes( $structure, &$node, $js = false ) {
 382+ $transaction = $this->currentTransaction();
 383+
 384+ if ( !is_array( $structure ) ) { //this is a weird case that shouldn't ever happen. I'm just being... thorough. But, yeah: It's like... the base-1 case.
 385+ $this->appendNodeIfValue( $structure, $node, $js );
 386+ } else {
 387+ foreach ( $structure as $key => $value ) {
 388+ if ( !is_array( $value ) ) {
 389+ //do not use $key. $key is meaningless in this case.
 390+ $this->appendNodeIfValue( $value, $node, $js );
 391+ } else {
 392+ $keynode = $this->xmlDoc->createElement( $key );
 393+ $this->buildTransactionNodes( $value, $keynode, $js );
 394+ $node->appendChild( $keynode );
 395+ }
 396+ }
 397+ }
 398+ //not actually returning anything. It's all side-effects. Because I suck like that.
 399+ }
 400+
 401+ function appendNodeIfValue( $value, &$node, $js = false ) {
 402+ $nodevalue = $this->getValue( $value, $js );
 403+ if ( $nodevalue !== '' && $nodevalue !== false ) {
 404+ $temp = $this->xmlDoc->createElement( $value, $nodevalue );
 405+ $node->appendChild( $temp );
 406+ }
 407+ }
 408+
 409+ //TODO: You can actually take this out if we never ever want to use ajax for a gateway.
 410+ function buildTransactionFormat( $transaction ) {
 411+ $this->currentTransaction( $transaction );
 412+ $this->xmlDoc = new DomDocument( '1.0' );
 413+ $node = $this->xmlDoc->createElement( 'XML' );
 414+
 415+ $structure = $this->transactions[$this->currentTransaction()]['request'];
 416+
 417+ $this->buildTransactionNodes( $structure, $node, true );
 418+ $this->xmlDoc->appendChild( $node );
 419+ $xml = $this->xmlDoc->saveXML();
 420+ $xmlStart = strpos( $xml, "<XML>" );
 421+ self::log( "XML START" . $xmlStart );
 422+ $xml = substr( $xml, $xmlStart );
 423+ self::log( "XML stubby thing..." . $xml );
 424+
 425+ return $xml;
 426+ }
 427+
 428+ /**
 429+ * Perform a transaction through the gateway
 430+ *
 431+ * @param $transaction string This is a specific transaction type like 'INSERT_ORDERWITHPAYMENT'
 432+ * that maps to a first-level key in the $transactions array.
 433+ */
 434+ function do_transaction( $transaction ) {
 435+ try {
 436+ $this->currentTransaction( $transaction );
 437+ //update the contribution tracking data
 438+ $this->incrementNumAttempt();
 439+
 440+ //if we're supposed to add the donor data to the session, do that.
 441+ if ( $this->transaction_option( 'addDonorDataToSession' ) ) {
 442+ $this->addDonorDataToSession();
 443+ }
 444+
 445+ $this->runPreProcess(); //many hooks get fired here...
 446+ //TODO: Uhmmm... what if none of the validate hooks are enabled?
 447+ //Currently, I think that means the transaction stops here, and that's not quite right.
 448+ //...is it?
 449+ // if the transaction was NOT flagged for processing by something in runPreProcess()...
 450+ if ( $this->action != 'process' ) {
 451+ self::log( "Transaction failed pre-process checks." . print_r( $this->getData(), true ) );
 452+ return array(
 453+ 'status' => false,
 454+ //TODO: appropriate messages.
 455+ 'message' => "$transaction : Failed failed pre-process checks. Somebody PLEASE override me!",
 456+ 'errors' => array(
 457+ '1000000' => 'pre-process failed you.' //...stupid code.
 458+ ),
 459+ 'action' => $this->action,
 460+ );
 461+ }
 462+
 463+ // expose a hook for external handling of trxns ready for processing
 464+ if ( $this->transaction_option( 'do_processhooks' ) ) {
 465+ wfRunHooks( 'GatewayProcess', array( &$this ) ); //don't think anybody is using this yet, but you could!
 466+ }
 467+
 468+ $this->dataObj->updateContributionTracking( defined( 'OWA' ) );
 469+
 470+ if ( $this->getCommunicationType() === 'xml' ) {
 471+ $this->getStopwatch( "buildRequestXML" );
 472+ $curlme = $this->buildRequestXML();
 473+ $this->saveCommunicationStats( "buildRequestXML", $transaction );
 474+ }
 475+
 476+ if ( $this->getCommunicationType() === 'namevalue' ) {
 477+ //buildRequestNameValueString()
 478+ $this->getStopwatch( "buildRequestNameValueString" );
 479+ $curlme = $this->buildRequestNameValueString();
 480+ $this->saveCommunicationStats( "buildRequestNameValueString", $transaction );
 481+ }
 482+ } catch ( MWException $e ) {
 483+ self::log( "Malformed gateway definition. Cannot continue: Aborting.", LOG_CRIT );
 484+ return array(
 485+ 'status' => false,
 486+ //TODO: appropriate messages.
 487+ 'message' => "$transaction : Malformed gateway definition. Cannot continue: Aborting.",
 488+ 'errors' => array(
 489+ '1000000' => 'Faulty Code! Bad programmer. Bad!' //...please change this.
 490+ ),
 491+ 'action' => $this->action,
 492+ );
 493+ }
 494+
 495+ //start looping here, if we're the sort of transaction that needs to do that.
 496+ $stopflag = false;
 497+ $counter = 0;
 498+ $statuses = $this->transaction_option( 'loop_for_status' );
 499+ $this->getStopwatch( __FUNCTION__, true );
 500+ while ( $stopflag === false ) {
 501+ $stopflag = true;
 502+ $counter += 1;
 503+ $txn_ok = $this->curl_transaction( $curlme );
 504+
 505+ if ( $txn_ok === true ) { //We have something to slice and dice.
 506+ self::log( "RETURNED FROM CURL:" . print_r( $this->getTransactionAllResults(), true ) );
 507+
 508+ //set the status of the response. This is the COMMUNICATION status, and has nothing
 509+ //to do with the result of the transaction.
 510+ $formatted = $this->getFormattedResponse( $this->getTransactionRawResponse() );
 511+ $this->setTransactionResult( $this->getResponseStatus( $formatted ), 'status' );
 512+
 513+ //set errors
 514+ //TODO: This "errors" business is becoming a bit of a misnomer, as the result code and message
 515+ //are frequently packaged togther in the same place, whether the transaction passed or failed.
 516+ $this->setTransactionResult( $this->getResponseErrors( $formatted ), 'errors' );
 517+
 518+ //if we're still okay (hey, even if we're not), get relevent dataz.
 519+ $pulled_data = $this->getResponseData( $formatted );
 520+ $this->setTransactionResult( $pulled_data, 'data' );
 521+
 522+ //TODO: Death to the pulled_data parameter!
 523+ $this->processResponse( $pulled_data ); //now we've set all the transaction results...
 524+ } else {
 525+ self::log( "Transaction Communication failed" . print_r( $this->getTransactionAllResults(), true ) );
 526+ }
 527+
 528+ if ( is_array( $statuses ) ) { //only then will we consider doing this again.
 529+ if ( $this->getStopwatch( __FUNCTION__ ) < self::getGlobal( "RetrySeconds" ) ) {
 530+ if ( $txn_ok === false ) {
 531+ $stopflag = false;
 532+ } else {
 533+ if ( !in_array( $this->getTransactionWMFStatus(), $statuses ) ) {
 534+ $stopflag = false;
 535+ }
 536+ }
 537+ }
 538+ }
 539+ }
 540+
 541+ //Log out how many times we looped, and what the clock is now.
 542+ $this->saveCommunicationStats( __FUNCTION__, $transaction, "counter = $counter" );
 543+
 544+ if ( $txn_ok === false ) { //nothing to process, so we have to build it manually
 545+ return array(
 546+ 'status' => false,
 547+ 'message' => "$transaction Communication Failed!",
 548+ 'errors' => array(
 549+ '1000000' => 'communication failure' //...stupid code.
 550+ ),
 551+ );
 552+ }
 553+
 554+ // expose a hook for any post processing
 555+ if ( $this->transaction_option( 'do_processhooks' ) ) {
 556+ wfRunHooks( 'GatewayPostProcess', array( &$this ) ); //conversion log (at least)
 557+ $this->doStompTransaction();
 558+ }
 559+
 560+ $this->dataObj->unsetEditToken();
 561+
 562+ //TODO: Actually pull these from somewhere legit.
 563+ if ( $this->getTransactionStatus() === true ) {
 564+ $this->setTransactionResult( "$transaction Transaction Successful!", 'message' );
 565+ } elseif ( $this->getTransactionStatus() === false ) {
 566+ $this->setTransactionResult( "$transaction Transaction FAILED!", 'message' );
 567+ } else {
 568+ $this->setTransactionResult( "$transaction Transaction... weird. I have no idea what happened there.", 'message' );
 569+ }
 570+
 571+ // log that the transaction is essentially complete
 572+ self::log( $this->getData( 'order_id' ) . " Transaction complete." );
 573+
 574+ //if we're not actively adding the donor data to the session, kill it.
 575+ if ( !$this->transaction_option( 'addDonorDataToSession' ) ) {
 576+ $this->unsetAllGatewaySessionData();
 577+ }
 578+
 579+ return $this->getTransactionAllResults();
 580+
 581+ //speaking of universal form:
 582+ //$result['status'] = something I wish could be boiled down to a bool, but that's way too optimistic, I think.
 583+ //$result['message'] = whatever we want to display back?
 584+ //$result['errors']['code']['message'] =
 585+ //$result['data'][$whatever] = values they pass back to us for whatever reason. We might... log it, or pieces of it, or something?
 586+ }
 587+
 588+ function getCurlBaseOpts() {
 589+ //I chose to return this as a function so it's easy to override.
 590+ //TODO: probably this for all the junk I currently have stashed in the constructor.
 591+ //...maybe.
 592+ $opts = array(
 593+ CURLOPT_URL => $this->url,
 594+ CURLOPT_USERAGENT => Http::userAgent(),
 595+ CURLOPT_HEADER => 1,
 596+ CURLOPT_RETURNTRANSFER => 1,
 597+ CURLOPT_TIMEOUT => self::getGlobal( 'Timeout' ),
 598+ CURLOPT_FOLLOWLOCATION => 0,
 599+ CURLOPT_SSL_VERIFYPEER => 0,
 600+ CURLOPT_SSL_VERIFYHOST => 2,
 601+ CURLOPT_FORBID_REUSE => true,
 602+ CURLOPT_POST => 1,
 603+ );
 604+
 605+ // set proxy settings if necessary
 606+ if ( self::getGlobal( 'UseHTTPProxy' ) ) {
 607+ $opts[CURLOPT_HTTPPROXYTUNNEL] = 1;
 608+ $opts[CURLOPT_PROXY] = self::getGlobal( 'HTTPProxy' );
 609+ }
 610+ return $opts;
 611+ }
 612+
 613+ function getCurlBaseHeaders() {
 614+ $headers = array(
 615+ 'Content-Type: text/' . $this->getCommunicationType() . '; charset=utf-8',
 616+ 'X-VPS-Client-Timeout: 45',
 617+ 'X-VPS-Request-ID:' . $this->postdatadefaults['order_id'],
 618+ );
 619+ return $headers;
 620+ }
 621+
 622+ protected function currentTransaction( $transaction = '' ) { //get&set in one!
 623+ static $current_transaction;
 624+ if ( $transaction != '' ) {
 625+ $current_transaction = $transaction;
 626+ }
 627+ if ( !isset( $current_transaction ) ) {
 628+ return false;
 629+ }
 630+ if ( empty( $this->transactions ) || !is_array( $this->transactions ) || !array_key_exists( $current_transaction, $this->transactions ) ) {
 631+ $msg = self::getGatewayName() . ': Transactions structure is malformed! ' . $current_transaction . ' transaction cannot be constructed.';
 632+ self::log( $msg, LOG_CRIT );
 633+ throw new MWException( $msg );
 634+ }
 635+ return $current_transaction;
 636+ }
 637+
 638+ /**
 639+ * Sends a name-value pair string to Payflow gateway
 640+ *
 641+ * @param $data String: The exact thing we want to send.
 642+ */
 643+ protected function curl_transaction( $data ) {
 644+ // assign header data necessary for the curl_setopt() function
 645+ $this->getStopwatch( __FUNCTION__, true );
 646+
 647+ $ch = curl_init();
 648+
 649+ $headers = $this->getCurlBaseHeaders();
 650+ $headers[] = 'Content-Length: ' . strlen( $data );
 651+
 652+ if ( $this->getCommunicationType() === 'xml' ) {
 653+ self::log( "Sending Data: " . $this->formatXmlString( $data ) );
 654+ } else {
 655+ self::log( "Sending Data: " . $data );
 656+ }
 657+
 658+ $curl_opts = $this->getCurlBaseOpts();
 659+ $curl_opts[CURLOPT_HTTPHEADER] = $headers;
 660+ $curl_opts[CURLOPT_POSTFIELDS] = $data;
 661+
 662+ foreach ( $curl_opts as $option => $value ) {
 663+ curl_setopt( $ch, $option, $value );
 664+ }
 665+
 666+ // As suggested in the PayPal developer forum sample code, try more than once to get a response
 667+ // in case there is a general network issue
 668+ $i = 1;
 669+
 670+ $results = array( );
 671+
 672+ while ( $i++ <= 3 ) {
 673+ self::log( $this->postdatadefaults['order_id'] . ' Preparing to send transaction to ' . self::getGatewayName() );
 674+ $results['result'] = curl_exec( $ch );
 675+ $results['headers'] = curl_getinfo( $ch );
 676+
 677+ if ( $results['headers']['http_code'] != 200 && $results['headers']['http_code'] != 403 ) {
 678+ self::log( $this->postdatadefaults['order_id'] . ' Failed sending transaction to ' . self::getGatewayName() . ', retrying' );
 679+ sleep( 1 );
 680+ } elseif ( $results['headers']['http_code'] == 200 || $results['headers']['http_code'] == 403 ) {
 681+ self::log( $this->postdatadefaults['order_id'] . ' Finished sending transaction to ' . self::getGatewayName() );
 682+ break;
 683+ }
 684+ }
 685+
 686+ $this->saveCommunicationStats( __FUNCTION__, $this->currentTransaction(), "Request:" . print_r( $data, true ) . "\nResponse" . print_r( $results, true ) );
 687+
 688+ if ( $results['headers']['http_code'] != 200 ) {
 689+ $results['result'] = false;
 690+ //TODO: i18n here!
 691+ //TODO: But also, fire off some kind of "No response from the gateway" thing to somebody so we know right away.
 692+ $results['message'] = 'No response from ' . self::getGatewayName() . '. Please try again later!';
 693+ $when = time();
 694+ self::log( $this->postdatadefaults['order_id'] . ' No response from ' . self::getGatewayName() . ': ' . curl_error( $ch ) );
 695+ curl_close( $ch );
 696+ return false;
 697+ }
 698+
 699+ curl_close( $ch );
 700+
 701+ $this->setTransactionResult( $results );
 702+ return true;
 703+ }
 704+
 705+ function stripXMLResponseHeaders( $rawResponse ) {
 706+ $xmlStart = strpos( $rawResponse, '<?xml' );
 707+ if ( $xmlStart == false ) { //I totally saw this happen one time. No XML, just <RESPONSE>...
 708+ $xmlStart = strpos( $rawResponse, '<RESPONSE' );
 709+ }
 710+ if ( $xmlStart == false ) { //Still false. Your Head Asplode.
 711+ self::log( "Wow, that was so messed up I couldn't even parse the response, so here's the thing in its entirety:\n" . $rawResponse );
 712+ return false;
 713+ }
 714+ $justXML = substr( $rawResponse, $xmlStart );
 715+ $this->setTransactionResult( $justXML, 'unparsed_data' );
 716+ return $justXML;
 717+ }
 718+
 719+ function stripNameValueResponseHeaders( $rawResponse ) {
 720+ $result = strstr( $rawResponse, 'RESULT' );
 721+ $this->setTransactionResult( $result, 'unparsed_data' );
 722+ return $result;
 723+ }
 724+
 725+ public static function log( $msg, $log_level=LOG_INFO, $log_id_suffix = '' ) {
 726+ $identifier = self::getIdentifier() . "_gateway" . $log_id_suffix;
 727+
 728+ // if we're not using the syslog facility, use wfDebugLog
 729+ if ( !self::getGlobal( 'UseSyslog' ) ) {
 730+ wfDebugLog( $identifier, $msg );
 731+ return;
 732+ }
 733+
 734+ // otherwise, use syslogging
 735+ openlog( $identifier, LOG_ODELAY, LOG_SYSLOG );
 736+ syslog( $log_level, $msg );
 737+ closelog();
 738+ }
 739+
 740+ //To avoid reinventing the wheel: taken from http://recursive-design.com/blog/2007/04/05/format-xml-with-php/
 741+ function formatXmlString( $xml ) {
 742+ // add marker linefeeds to aid the pretty-tokeniser (adds a linefeed between all tag-end boundaries)
 743+ $xml = preg_replace( '/(>)(<)(\/*)/', "$1\n$2$3", $xml );
 744+
 745+ // now indent the tags
 746+ $token = strtok( $xml, "\n" );
 747+ $result = ''; // holds formatted version as it is built
 748+ $pad = 0; // initial indent
 749+ $matches = array( ); // returns from preg_matches()
 750+ // scan each line and adjust indent based on opening/closing tags
 751+ while ( $token !== false ) :
 752+
 753+ // test for the various tag states
 754+ // 1. open and closing tags on same line - no change
 755+ if ( preg_match( '/.+<\/\w[^>]*>$/', $token, $matches ) ) :
 756+ $indent = 0;
 757+ // 2. closing tag - outdent now
 758+ elseif ( preg_match( '/^<\/\w/', $token, $matches ) ) :
 759+ $pad--;
 760+ // 3. opening tag - don't pad this one, only subsequent tags
 761+ elseif ( preg_match( '/^<\w[^>]*[^\/]>.*$/', $token, $matches ) ) :
 762+ $indent = 1;
 763+ // 4. no indentation needed
 764+ else :
 765+ $indent = 0;
 766+ endif;
 767+
 768+ // pad the line with the required number of leading spaces
 769+ $line = str_pad( $token, strlen( $token ) + $pad, ' ', STR_PAD_LEFT );
 770+ $result .= $line . "\n"; // add to the cumulative result, with linefeed
 771+ $token = strtok( "\n" ); // get the next token
 772+ $pad += $indent; // update the pad size for subsequent lines
 773+ endwhile;
 774+
 775+ return $result;
 776+ }
 777+
 778+ static function getCommunicationType() {
 779+ $c = get_called_class();
 780+ return $c::COMMUNICATION_TYPE;
 781+ }
 782+
 783+ static function getGatewayName() {
 784+ $c = get_called_class();
 785+ return $c::GATEWAY_NAME;
 786+ }
 787+
 788+ static function getGlobalPrefix() {
 789+ $c = get_called_class();
 790+ return $c::GLOBAL_PREFIX;
 791+ }
 792+
 793+ static function getIdentifier() {
 794+ $c = get_called_class();
 795+ return $c::IDENTIFIER;
 796+ }
 797+
 798+ /**
 799+ * getStopwatch keeps track of how long things take, for logging,
 800+ * output, determining if we should loop on some method again... whatever.
 801+ * @staticvar array $start The microtime at which a stopwatch was started.
 802+ * @param string $string Some identifier for each stopwatch value we want to
 803+ * keep. Each unique $string passed in will get its own value in $start.
 804+ * @param bool $reset If this is set to true, it will reset any $start value
 805+ * recorded for the $string identifier.
 806+ * @return numeric The difference in microtime (rounded to 4 decimal places)
 807+ * between the $start value, and now.
 808+ */
 809+ public function getStopwatch( $string, $reset = false ) {
 810+ static $start = array( );
 811+ $now = microtime( true );
 812+
 813+ if ( empty( $start ) || !array_key_exists( $string, $start ) || $reset === true ) {
 814+ $start[$string] = $now;
 815+ }
 816+ $clock = round( $now - $start[$string], 4 );
 817+ self::log( "\nClock at $string: $clock ($now)" );
 818+ return $clock;
 819+ }
 820+
 821+ /**
 822+ *
 823+ * @param type $function
 824+ * @param type $additional
 825+ * @param type $vars
 826+ */
 827+ function saveCommunicationStats( $function = '', $additional = '', $vars = '' ) {
 828+ $params = array( );
 829+ if ( self::getGlobal( 'SaveCommStats' ) ) {
 830+ $db = ContributionTrackingProcessor::contributionTrackingConnection();
 831+
 832+ //TODO: Actually define this table somewhere in the code, once we
 833+ //are reasonably certain we know what we want to see in it.
 834+ if ( !( $db->tableExists( 'communication_stats' ) ) ) {
 835+ return;
 836+ }
 837+
 838+ $params['contribution_id'] = $this->dataObj->getVal( 'contribution_tracking_id' );
 839+ $params['ts'] = $db->timestamp();
 840+ $params['duration'] = $this->getStopwatch( __FUNCTION__ );
 841+ $params['gateway'] = self::getGatewayName();
 842+ $params['function'] = $function;
 843+ $params['vars'] = $vars;
 844+ $params['additional'] = $additional;
 845+
 846+ $db->insert( 'communication_stats', $params );
 847+ }
 848+ }
 849+
 850+ function xmlChildrenToArray( $xml, $nodename ) {
 851+ $data = array( );
 852+ foreach ( $xml->getElementsByTagName( $nodename ) as $node ) {
 853+ foreach ( $node->childNodes as $childnode ) {
 854+ if ( trim( $childnode->nodeValue ) != '' ) {
 855+ $data[$childnode->nodeName] = $childnode->nodeValue;
 856+ }
 857+ }
 858+ }
 859+ return $data;
 860+ }
 861+
 862+ /**
 863+ * DO NOT DEFINE OVERLAPPING RANGES!
 864+ * TODO: Make sure it won't let you add overlapping ranges. That would probably necessitate the sort moving to here, too.
 865+ * @param type $transaction
 866+ * @param type $key
 867+ * @param type $action
 868+ * @param type $lower
 869+ * @param type $upper
 870+ */
 871+ function addCodeRange( $transaction, $key, $action, $lower, $upper = null ) {
 872+ if ( $upper === null ) {
 873+ $this->return_value_map[$transaction][$key][$lower] = $action;
 874+ } else {
 875+ $this->return_value_map[$transaction][$key][$upper] = array( 'action' => $action, 'lower' => $lower );
 876+ }
 877+ }
 878+
 879+ function findCodeAction( $transaction, $key, $code ) {
 880+ $this->getStopwatch( __FUNCTION__, true );
 881+ if ( !array_key_exists( $transaction, $this->return_value_map ) || !array_key_exists( $key, $this->return_value_map[$transaction] ) ) {
 882+ return null;
 883+ }
 884+ if ( !is_array( $this->return_value_map[$transaction][$key] ) ) {
 885+ return null;
 886+ }
 887+ //sort the array so we can do this quickly.
 888+ ksort( $this->return_value_map[$transaction][$key], SORT_NUMERIC );
 889+
 890+ $ranges = $this->return_value_map[$transaction][$key];
 891+ //so, you have a code, which is a number. You also have a numerically sorted array.
 892+ //loop through until you find an upper >= your code.
 893+ //make sure it's in the range, and return the action.
 894+ foreach ( $ranges as $upper => $val ) {
 895+ if ( $upper >= $code ) { //you've arrived. It's either here or it's nowhere.
 896+ if ( is_array( $val ) ) {
 897+ if ( $val['lower'] <= $code ) {
 898+ $this->saveCommunicationStats( __FUNCTION__, $transaction, "code = $code" );
 899+ return $val['action'];
 900+ } else {
 901+ return null;
 902+ }
 903+ } else {
 904+ if ( $upper === $code ) {
 905+ $this->saveCommunicationStats( __FUNCTION__, $transaction, "code = $code" );
 906+ return $val;
 907+ } else {
 908+ return null;
 909+ }
 910+ }
 911+ }
 912+ }
 913+ //if we walk straight off the end...
 914+ return null;
 915+ }
 916+
 917+ function addDonorDataToSession() {
 918+ $this->dataObj->addDonorDataToSession();
 919+ }
 920+
 921+ function unsetAllGatewaySessionData() {
 922+ $this->dataObj->unsetAllDDSessionData();
 923+ }
 924+
 925+ function doStompTransaction() {
 926+ $this->debugarray[] = "Attempting Stomp Transaction!";
 927+ $hook = '';
 928+
 929+ $status = $this->getTransactionWMFStatus();
 930+ switch ( $status ) {
 931+ case 'complete':
 932+ $hook = 'gwStomp';
 933+ break;
 934+ case 'pending':
 935+ case 'pending-poke':
 936+ $hook = 'gwPendingStomp';
 937+ break;
 938+ }
 939+ if ( $hook === '' ) {
 940+ $this->debugarray[] = "No Stomp Hook Found for WMF_Status $status";
 941+ return;
 942+ }
 943+
 944+ $data = $this->getTransactionData();
 945+
 946+ //Gah. I might want to move all this data prep business upstream more than somewhat.
 947+ //...but that's for later.
 948+ // Add the session vars to the data object
 949+ if ( $this->transaction_option( 'pullDonorDataFromSession' ) ) {
 950+ $this->dataObj->populateDonorFromSession();
 951+ }
 952+
 953+ // refresh our data
 954+ $this->postdata = $this->dataObj->getData();
 955+
 956+ // stage the gateway data
 957+ $this->stageData( 'response' );
 958+
 959+ // send the thing.
 960+ $transaction = array(
 961+ 'response' => $this->getTransactionMessage(),
 962+ 'date' => time(),
 963+ 'gateway_txn_id' => $this->getTransactionGatewayTxnID(),
 964+ 'language' => '',
 965+ );
 966+ $transaction += $this->getData();
 967+
 968+ self::log( "Intended STOMP transaction: " . print_r( $transaction, true ) );
 969+
 970+ try {
 971+ wfRunHooks( $hook, array( $transaction ) );
 972+ } catch ( Exception $e ) {
 973+ self::log( "STOMP ERROR. Could not add message. " . $e->getMessage() , LOG_CRIT );
 974+ }
 975+
 976+ }
 977+
 978+ function smooshVarsForStaging() {
 979+
 980+ foreach ( $this->staged_vars as $field ) {
 981+ if ( !array_key_exists( $field, $this->postdata ) || empty( $this->postdata[$field] ) ) {
 982+ if ( array_key_exists( $field, $this->postdatadefaults ) ) {
 983+ $this->postdata[$field] = $this->postdatadefaults[$field];
 984+ }
 985+ }
 986+ //what do we do in the event that we're still nothing? (just move on.)
 987+ }
 988+ }
 989+
 990+ /**
 991+ *
 992+ * @param type $type Whatever types of staging you feel like having in your child class.
 993+ * ...but usually request and response. I think.
 994+ */
 995+ function stageData( $type = 'request' ) {
 996+ $this->defineStagedVars();
 997+ $this->smooshVarsForStaging(); //yup, we do need to do this seperately.
 998+ //If we tried to piggyback off the same loop, all the vars wouldn't be ready, and some staging functions will require
 999+ //multiple variables.
 1000+ foreach ( $this->staged_vars as $field ) {
 1001+ $function_name = 'stage_' . $field;
 1002+ if ( method_exists( $this, $function_name ) ) {
 1003+ $this->{$function_name}( $type );
 1004+ }
 1005+ }
 1006+ }
 1007+
 1008+ function getPaypalRedirectURL() {
 1009+ $utm_source = $this->getData( 'utm_source' );
 1010+
 1011+ // update the utm source to set the payment instrument to pp rather than cc
 1012+ $utm_source_parts = explode( ".", $utm_source );
 1013+ $utm_source_parts[2] = 'pp';
 1014+ $data['utm_source'] = implode( ".", $utm_source_parts );
 1015+ $data['gateway'] = 'paypal';
 1016+ $data['currency_code'] = $data['currency'];
 1017+
 1018+ // Add our response vars to the data object.
 1019+ $this->dataObj->addData( $data );
 1020+ // refresh our data
 1021+ $this->postdata = $this->dataObj->getData();
 1022+
 1023+ //update contribution tracking
 1024+ $this->dataObj->updateContributionTracking( true );
 1025+
 1026+ $ret = self::getGlobal( "PaypalURL" ) . "/" . $this->postdata['language'] . "?gateway=paypal&" . http_build_query( $this->getPaypalData() );
 1027+ self::log( $ret );
 1028+ return $ret;
 1029+ }
 1030+
 1031+ protected function getPaypalData() {
 1032+ $paypalkeys = array(
 1033+ 'gateway',
 1034+ 'contribution_tracking_id',
 1035+ 'comment',
 1036+ 'referrer',
 1037+ 'comment-option',
 1038+ 'utm_source',
 1039+ 'utm_medium',
 1040+ 'utm_campaign',
 1041+ 'email-opt',
 1042+ 'language',
 1043+ 'owa_session',
 1044+ 'owa_ref',
 1045+ 'tshirt',
 1046+ 'returnto',
 1047+ 'currency_code',
 1048+ 'fname',
 1049+ 'lname',
 1050+ 'email',
 1051+ 'address1',
 1052+ 'city',
 1053+ 'state',
 1054+ 'zip',
 1055+ 'country',
 1056+ 'address_override',
 1057+ 'recurring_paypal',
 1058+ 'amount',
 1059+ 'amountGiven',
 1060+ 'size',
 1061+ 'premium_language',
 1062+ );
 1063+ $ret = array();
 1064+ foreach ( $paypalkeys as $key ){
 1065+ $val = $this->getData( $key );
 1066+ if (!is_null( $val )){
 1067+ $ret[$key] = $this->getData( $key );
 1068+ }
 1069+ }
 1070+ return $ret;
 1071+ }
 1072+
 1073+ public function getTransactionAllResults() {
 1074+ if ( !empty( $this->transaction_results ) && is_array( $this->transaction_results ) ) {
 1075+ return $this->transaction_results;
 1076+ } else {
 1077+ return false;
 1078+ }
 1079+ }
 1080+
 1081+ /**
 1082+ * SetTransactionResult sets the gateway adapter object's
 1083+ * $transaction_results value.
 1084+ * If a $key is specified, it only sets the specified key's value. If no
 1085+ * $key is specified, it resets the value of the entire array.
 1086+ * @param mixed $value The value to set in $transaction_results
 1087+ * @param mixed $key Optional: A specific key to set, or false (default) to
 1088+ * reset the entire result array.
 1089+ */
 1090+ public function setTransactionResult( $value, $key = false ) {
 1091+ if ( $key === false ) {
 1092+ $this->transaction_results = $value;
 1093+ } else {
 1094+ $this->transaction_results[$key] = $value;
 1095+ }
 1096+ }
 1097+
 1098+ public function getTransactionRawResponse() {
 1099+ if ( array_key_exists( 'result', $this->transaction_results ) ) {
 1100+ return $this->transaction_results['result'];
 1101+ } else {
 1102+ return false;
 1103+ }
 1104+ }
 1105+
 1106+ /**
 1107+ * If it has been set: returns the Transaction Status in the
 1108+ * $transaction_results array. Otherwise, returns false.
 1109+ * @return mixed Transaction results status, or false if not set.
 1110+ */
 1111+ public function getTransactionStatus() {
 1112+ if ( array_key_exists( 'status', $this->transaction_results ) ) {
 1113+ return $this->transaction_results['status'];
 1114+ } else {
 1115+ return false;
 1116+ }
 1117+ }
 1118+
 1119+ /**
 1120+ * If it has been set: returns the WMF Transaction Status in the
 1121+ * $transaction_results array. This is the one we care about for switching
 1122+ * on overall behavior. Otherwise, returns false.
 1123+ * @return mixed WMF Transaction results status, or false if not set.
 1124+ */
 1125+ public function getTransactionWMFStatus() {
 1126+ if ( array_key_exists( 'WMF_STATUS', $this->transaction_results ) ) {
 1127+ return $this->transaction_results['WMF_STATUS'];
 1128+ } else {
 1129+ return false;
 1130+ }
 1131+ }
 1132+
 1133+ /**
 1134+ * Sets the WMF Transaction Status. This is the one we care about for
 1135+ * switching on behavior.
 1136+ */
 1137+ public function setTransactionWMFStatus( $status ) {
 1138+ $this->transaction_results['WMF_STATUS'] = $status;
 1139+ }
 1140+
 1141+ public function getTransactionMessage() {
 1142+ if ( array_key_exists( 'txn_message', $this->transaction_results ) ) {
 1143+ return $this->transaction_results['txn_message'];
 1144+ } else {
 1145+ return false;
 1146+ }
 1147+ }
 1148+
 1149+ public function getTransactionGatewayTxnID() {
 1150+ if ( array_key_exists( 'gateway_txn_id', $this->transaction_results ) ) {
 1151+ return $this->transaction_results['gateway_txn_id'];
 1152+ } else {
 1153+ return false;
 1154+ }
 1155+ }
 1156+
 1157+ /**
 1158+ * Returns the FORMATTED data harvested from the reply, or false if it is not set.
 1159+ * @return mixed An array of returned data, or false.
 1160+ */
 1161+ public function getTransactionData() {
 1162+ if ( array_key_exists( 'data', $this->transaction_results ) ) {
 1163+ return $this->transaction_results['data'];
 1164+ } else {
 1165+ return false;
 1166+ }
 1167+ }
 1168+
 1169+ public function setFormClass( $formClassName ) {
 1170+ //I'm adding this because Captcha needs it, and we're gonna fire the hook inside. Nothing else really needs it as far as I know.
 1171+ $this->form_class = $formClassName;
 1172+ }
 1173+
 1174+ public function getFormClass() {
 1175+ if ( isset( $this->form_class ) && class_exists( $this->form_class ) ) {
 1176+ return $this->form_class;
 1177+ } else {
 1178+ return false;
 1179+ }
 1180+ }
 1181+
 1182+ public function getGatewayAdapterClass() {
 1183+ return get_called_class();
 1184+ }
 1185+
 1186+ public function setValidationErrors( $errors ) {
 1187+ $this->validation_errors = $errors;
 1188+ }
 1189+
 1190+ public function getValidationErrors() {
 1191+ if ( !empty( $this->validation_errors ) ) {
 1192+ return $this->validation_errors;
 1193+ } else {
 1194+ return false;
 1195+ }
 1196+ }
 1197+
 1198+ public function incrementNumAttempt() {
 1199+ $this->dataObj->incrementNumAttempt();
 1200+ }
 1201+
 1202+ public function setHash( $hashval ) {
 1203+ $this->dataObj->setVal( 'data_hash', $hashval );
 1204+ }
 1205+
 1206+ public function unsetHash() {
 1207+ $this->dataObj->expunge( 'data_hash' );
 1208+ }
 1209+
 1210+ public function setActionHash( $hashval ) {
 1211+ $this->dataObj->setVal( 'action', $hashval );
 1212+ }
 1213+
 1214+ public function unsetActionHash() {
 1215+ $this->dataObj->expunge( 'action' );
 1216+ }
 1217+
 1218+ function runPreProcess() {
 1219+ if ( $this->transaction_option( 'do_validation' ) ) {
 1220+ if ( !isset( $wgHooks['GatewayValidate'] ) ) {
 1221+ //if there ARE no validate hooks, we're okay.
 1222+ $this->action = 'process';
 1223+ return;
 1224+ }
 1225+ // allow any external validators to have their way with the data
 1226+ self::log( $this->getData( 'order_id' ) . " Preparing to query MaxMind" );
 1227+ wfRunHooks( 'GatewayValidate', array( &$this ) );
 1228+ self::log( $this->getData( 'order_id' ) . ' Finished querying Maxmind' );
 1229+
 1230+ // if the transaction was flagged for review
 1231+ if ( $this->action == 'review' ) {
 1232+ // expose a hook for external handling of trxns flagged for review
 1233+ wfRunHooks( 'GatewayReview', array( &$this ) );
 1234+ }
 1235+
 1236+ // if the transaction was flagged to be 'challenged'
 1237+ if ( $this->action == 'challenge' ) {
 1238+ // expose a hook for external handling of trxns flagged for challenge (eg captcha)
 1239+ wfRunHooks( 'GatewayChallenge', array( &$this ) );
 1240+ }
 1241+
 1242+ // if the transaction was flagged for rejection
 1243+ if ( $this->action == 'reject' ) {
 1244+ // expose a hook for external handling of trxns flagged for rejection
 1245+ wfRunHooks( 'GatewayReject', array( &$this ) );
 1246+ $this->dataObj->unsetEditToken();
 1247+ }
 1248+ } else {
 1249+ $this->action = 'process'; //we have to do this so do_transaction doesn't kick out.
 1250+ }
 1251+ }
 1252+
 1253+ function transaction_option( $option_value ) {
 1254+ //ooo, ugly.
 1255+ if ( array_key_exists( $option_value, $this->transactions[$this->currentTransaction()] ) ) {
 1256+ if ( $this->transactions[$this->currentTransaction()][$option_value] === true ) {
 1257+ return true;
 1258+ }
 1259+ if ( is_array( $this->transactions[$this->currentTransaction()][$option_value] ) &&
 1260+ !empty( $this->transactions[$this->currentTransaction()][$option_value] ) ) {
 1261+ return $this->transactions[$this->currentTransaction()][$option_value];
 1262+ }
 1263+ }
 1264+ return false;
 1265+ }
 1266+
 1267+}
Property changes on: trunk/extensions/DonationInterface/gateway_common/gateway.adapter.php
___________________________________________________________________
Added: svn:eol-style
11268 + native
Index: trunk/extensions/DonationInterface/gateway_common/donation.api.php
@@ -0,0 +1,164 @@
 2+<?php
 3+/**
 4+ * Generic Donation API
 5+ * This API should be able to accept donation submissions for any gateway or payment type
 6+ * Call with api.php?action=donate
 7+ */
 8+class DonationApi extends ApiBase {
 9+ public function execute() {
 10+ global $wgRequest, $wgParser;
 11+
 12+ $params = $this->extractRequestParams();
 13+ $options = array();
 14+
 15+ $gateway = $params['gateway'];
 16+
 17+ // If you want to test with fake data, pass a 'test' param set to true.
 18+ // You still have to set the gateway you are testing though.
 19+ // It looks like this only works if the Test global variable for that gateway is true.
 20+ if ( array_key_exists( 'test', $params ) && $params['test'] ) {
 21+ $params = $this->getTestData( $gateway );
 22+ $options['testData'] = $params;
 23+ }
 24+
 25+ $method = $params['payment_method'];
 26+
 27+ if ( $gateway == 'payflowpro' ) {
 28+ $gatewayObj = new PayflowProAdapter( $options );
 29+ switch ( $method ) {
 30+ // TODO: add other payment methods
 31+ default:
 32+ $result = $gatewayObj->do_transaction( 'Card' );
 33+ }
 34+ } else if ( $gateway == 'globalcollect' ) {
 35+ $gatewayObj = new GlobalCollectAdapter( $options );
 36+ switch ( $method ) {
 37+ // TODO: add other payment methods
 38+ case 'card':
 39+ $result = $gatewayObj->do_transaction( 'INSERT_ORDERWITHPAYMENT' );
 40+ break;
 41+ default:
 42+ $result = $gatewayObj->do_transaction( 'TEST_CONNECTION' );
 43+ }
 44+ } else {
 45+ $this->dieUsage( "Invalid gateway <<<$gateway>>> passed to Donation API.", 'unknown_gateway' );
 46+ }
 47+
 48+ //$normalizedData = $gatewayObj->getData();
 49+ $outputResult = array();
 50+ $outputResult['message'] = $result['message'];
 51+ $outputResult['status'] = $result['status'];
 52+ $outputResult['returnurl'] = $result['data']['PAYMENT']['RETURNURL'];
 53+ $outputResult['errors'] = implode( '; ', $result['errors'] );
 54+
 55+ $this->getResult()->addValue( 'data', 'request', $params );
 56+ $this->getResult()->addValue( 'data', 'result', $outputResult );
 57+
 58+ /*
 59+ $this->getResult()->setIndexedTagName( $result, 'response' );
 60+ $this->getResult()->addValue( 'data', 'result', $result );
 61+ */
 62+ }
 63+
 64+ public function getAllowedParams() {
 65+ return array(
 66+ 'gateway' => $this->defineParam( true ),
 67+ 'test' => $this->defineParam( false ),
 68+ 'amount' => $this->defineParam( false ),
 69+ 'currency' => $this->defineParam( false ),
 70+ 'fname' => $this->defineParam( false ),
 71+ 'mname' => $this->defineParam( false ),
 72+ 'lname' => $this->defineParam( false ),
 73+ 'street' => $this->defineParam( false ),
 74+ 'city' => $this->defineParam( false ),
 75+ 'state' => $this->defineParam( false ),
 76+ 'zip' => $this->defineParam( false ),
 77+ 'emailAdd' => $this->defineParam( false ),
 78+ 'country' => $this->defineParam( false ),
 79+ 'card_num' => $this->defineParam( false ),
 80+ 'card_type' => $this->defineParam( false ),
 81+ 'expiration' => $this->defineParam( false ),
 82+ 'cvv' => $this->defineParam( false ),
 83+ 'payment_method' => $this->defineParam( false ),
 84+ 'language' => $this->defineParam( false ),
 85+ );
 86+ }
 87+
 88+ private function defineParam( $required = false, $type = 'string' ) {
 89+ if ( $required ) {
 90+ $param = array( ApiBase::PARAM_TYPE => $type, ApiBase::PARAM_REQUIRED => true );
 91+ } else {
 92+ $param = array( ApiBase::PARAM_TYPE => $type );
 93+ }
 94+ return $param;
 95+ }
 96+
 97+ private function getTestData( $gateway ) {
 98+ $params = array(
 99+ 'gateway' => $gateway,
 100+ 'amount' => "35",
 101+ 'currency' => 'USD',
 102+ 'fname' => 'Tester',
 103+ 'mname' => 'T.',
 104+ 'lname' => 'Testington',
 105+ 'street' => '548 Market St.',
 106+ 'city' => 'San Francisco',
 107+ 'state' => 'CA',
 108+ 'zip' => '94104',
 109+ 'emailAdd' => 'test@example.com',
 110+ 'country' => 'US',
 111+ 'payment_method' => 'card',
 112+ 'language' => 'en',
 113+ 'card_type' => '1', // Is this valid for PayflowPro?
 114+ );
 115+ if ( $gateway != 'globalcollect' ) {
 116+ $params += array(
 117+ 'card_num' => '378282246310005',
 118+ 'expiration' => date( 'my', strtotime( '+1 year 1 month' ) ),
 119+ 'cvv' => '001',
 120+ );
 121+ }
 122+ return $params;
 123+ }
 124+
 125+ public function getParamDescription() {
 126+ return array(
 127+ 'gateway' => 'Which payment gateway to use - payflowpro, globalcollect, etc.',
 128+ 'test' => 'Set to true if you want to use bogus test data instead of supplying your own',
 129+ 'amount' => 'The amount donated',
 130+ 'currency' => 'Currency code',
 131+ 'fname' => 'First name',
 132+ 'mname' => 'Middle name',
 133+ 'lname' => 'Last name',
 134+ 'street' => 'First line of street address',
 135+ 'city' => 'City',
 136+ 'state' => 'State abbreviation',
 137+ 'zip' => 'Postal code',
 138+ 'emailAdd' => 'Email address',
 139+ 'country' => 'Country code',
 140+ 'card_num' => 'Credit card number',
 141+ 'card_type' => 'Credit card type',
 142+ 'expiration' => 'Expiration date',
 143+ 'cvv' => 'CVV security code',
 144+ 'payment_method' => 'Payment method to use',
 145+ 'language' => 'Language code',
 146+ );
 147+ }
 148+
 149+ public function getDescription() {
 150+ return array(
 151+ 'This API allow you to submit a donation to the Wikimedia Foundation using a',
 152+ 'variety of payment processors.',
 153+ );
 154+ }
 155+
 156+ public function getExamples() {
 157+ return array(
 158+ 'api.php?action=donate&gateway=payflowpro&amount=2.00&currency=USD',
 159+ );
 160+ }
 161+
 162+ public function getVersion() {
 163+ return __CLASS__ . ': $Id: DonationApi.php 1.0 kaldari $';
 164+ }
 165+}
Index: trunk/extensions/DonationInterface/gateway_common/DonationData.php
@@ -0,0 +1,718 @@
 2+<?php
 3+
 4+/**
 5+ * Description of DonationData
 6+ *
 7+ * @author khorn
 8+ */
 9+class DonationData {
 10+
 11+ protected $normalized = array( );
 12+ public $boss;
 13+
 14+ function __construct( $owning_class, $test = false, $data = false ) {
 15+ //TODO: Actually think about this bit.
 16+ // ...and keep in mind we can re-populate if it's a test or whatever. (But that may not be a good idea either)
 17+ $this->boss = $owning_class;
 18+ $this->gatewayID = $this->getGatewayIdentifier();
 19+ $this->populateData( $test, $data );
 20+ }
 21+
 22+ function populateData( $test = false, $testdata = false ) {
 23+ global $wgRequest;
 24+ $this->normalized = array( );
 25+ if ( $test ) {
 26+ $this->populateData_Test( $testdata );
 27+ } else {
 28+ $this->normalized = array(
 29+ 'posted' => 'true', //moderately sneaky.
 30+ 'amount' => $wgRequest->getText( 'amount', null ),
 31+ 'amountGiven' => $wgRequest->getText( 'amountGiven', null ),
 32+ 'amountOther' => $wgRequest->getText( 'amountOther', null ),
 33+ 'email' => $wgRequest->getText( 'emailAdd' ),
 34+ 'fname' => $wgRequest->getText( 'fname' ),
 35+ 'mname' => $wgRequest->getText( 'mname' ),
 36+ 'lname' => $wgRequest->getText( 'lname' ),
 37+ 'street' => $wgRequest->getText( 'street' ),
 38+ 'city' => $wgRequest->getText( 'city' ),
 39+ 'state' => $wgRequest->getText( 'state' ),
 40+ 'zip' => $wgRequest->getText( 'zip' ),
 41+ 'country' => $wgRequest->getText( 'country' ),
 42+ 'fname2' => $wgRequest->getText( 'fname' ),
 43+ 'lname2' => $wgRequest->getText( 'lname' ),
 44+ 'street2' => $wgRequest->getText( 'street' ),
 45+ 'city2' => $wgRequest->getText( 'city' ),
 46+ 'state2' => $wgRequest->getText( 'state' ),
 47+ 'zip2' => $wgRequest->getText( 'zip' ),
 48+ /**
 49+ * For legacy reasons, we might get a 0-length string passed into the form for country2. If this happens, we need to set country2
 50+ * to be 'country' for downstream processing (until we fully support passing in two separate addresses). I thought about completely
 51+ * disabling country2 support in the forms, etc but realized there's a chance it'll be resurrected shortly. Hence this silly hack.
 52+ */
 53+ 'country2' => ( strlen( $wgRequest->getText( 'country2' ) ) ) ? $wgRequest->getText( 'country2' ) : $wgRequest->getText( 'country' ),
 54+ 'size' => $wgRequest->getText( 'size' ),
 55+ 'premium_language' => $wgRequest->getText( 'premium_language', "en" ),
 56+ 'card_num' => str_replace( ' ', '', $wgRequest->getText( 'card_num' ) ),
 57+ 'card_type' => $wgRequest->getText( 'card_type' ),
 58+ 'expiration' => $wgRequest->getText( 'mos' ) . substr( $wgRequest->getText( 'year' ), 2, 2 ),
 59+ 'cvv' => $wgRequest->getText( 'cvv' ),
 60+ 'currency' => $wgRequest->getText( 'currency_code' ),
 61+ 'payment_method' => $wgRequest->getText( 'payment_method' ),
 62+ 'order_id' => $wgRequest->getText( 'order_id', null ), //as far as I know, this won't actually ever pull anything back.
 63+ 'i_order_id' => $wgRequest->getText( 'i_order_id', null ), //internal id for each contribution attempt
 64+ 'numAttempt' => $wgRequest->getVal( 'numAttempt', 0 ),
 65+ 'referrer' => ( $wgRequest->getVal( 'referrer' ) ) ? $wgRequest->getVal( 'referrer' ) : $wgRequest->getHeader( 'referer' ),
 66+ 'utm_source' => self::getUtmSource(), //TODO: yes. That.
 67+ 'utm_medium' => $wgRequest->getText( 'utm_medium' ),
 68+ 'utm_campaign' => $wgRequest->getText( 'utm_campaign' ),
 69+ // try to honor the user-set language (uselang), otherwise the language set in the URL (language)
 70+ 'language' => $wgRequest->getText( 'uselang', $wgRequest->getText( 'language' ) ),
 71+ 'comment-option' => $wgRequest->getText( 'comment-option' ),
 72+ 'comment' => $wgRequest->getText( 'comment' ),
 73+ 'email-opt' => $wgRequest->getText( 'email-opt' ),
 74+ 'test_string' => $wgRequest->getText( 'process' ), // for showing payflow string during testing
 75+ '_cache_' => $wgRequest->getText( '_cache_', null ),
 76+ 'token' => $wgRequest->getText( 'token', null ),
 77+ 'contribution_tracking_id' => $wgRequest->getText( 'contribution_tracking_id' ),
 78+ 'data_hash' => $wgRequest->getText( 'data_hash' ),
 79+ 'action' => $wgRequest->getText( 'action' ),
 80+ 'gateway' => $wgRequest->getText( 'gateway' ), //likely to be reset shortly by setGateway();
 81+ 'owa_session' => $wgRequest->getText( 'owa_session', null ),
 82+ 'owa_ref' => $wgRequest->getText( 'owa_ref', null ),
 83+ 'transaction_type' => $wgRequest->getText( 'transaction_type', null ), // Used by GlobalCollect for payment types
 84+ );
 85+ if ( !$wgRequest->wasPosted() ) {
 86+ $this->setVal( 'posted', false );
 87+ }
 88+ }
 89+ $this->doCacheStuff();
 90+
 91+ $this->normalizeAndSanitize();
 92+
 93+ //TODO: determine if _nocache_ is still a thing anywhere.
 94+ if ( !empty( $this->normalized ) && ( $this->getVal( 'numAttempt' ) == '0' && ((!$this->getVal( 'utm_source_id' ) == false ) || $this->getVal( '_nocache_' ) == 'true' ) ) ) {
 95+ $this->saveContributionTracking();
 96+ }
 97+ }
 98+
 99+ function getData() {
 100+ return $this->normalized;
 101+ }
 102+
 103+ function isCache() {
 104+ return $this->cache;
 105+ }
 106+
 107+ function populateData_Test( $testdata = false ) {
 108+ if ( is_array( $testdata ) ) {
 109+ $this->normalized = $testdata;
 110+ } else {
 111+ // define arrays of cc's and cc #s for random selection
 112+ $cards = array( 'american' );
 113+ $card_nums = array(
 114+ 'american' => array(
 115+ 378282246310005
 116+ ),
 117+ );
 118+
 119+ // randomly select a credit card
 120+ $card_index = array_rand( $cards );
 121+
 122+ // randomly select a credit card #
 123+ $card_num_index = array_rand( $card_nums[$cards[$card_index]] );
 124+
 125+ global $wgRequest; //TODO: ARRRGHARGHARGH. That is all.
 126+
 127+ $this->normalized = array(
 128+ 'amount' => "35",
 129+ 'amountOther' => '',
 130+ 'email' => 'test@example.com',
 131+ 'fname' => 'Tester',
 132+ 'mname' => 'T.',
 133+ 'lname' => 'Testington',
 134+ 'street' => '548 Market St.',
 135+ 'city' => 'San Francisco',
 136+ 'state' => 'CA',
 137+ 'zip' => '94104',
 138+ 'country' => 'US',
 139+ 'fname2' => 'Testy',
 140+ 'lname2' => 'Testerson',
 141+ 'street2' => '123 Telegraph Ave.',
 142+ 'city2' => 'Berkeley',
 143+ 'state2' => 'CA',
 144+ 'zip2' => '94703',
 145+ 'country2' => 'US',
 146+ 'size' => 'small',
 147+ 'premium_language' => 'es',
 148+ 'card_num' => $card_nums[$cards[$card_index]][$card_num_index],
 149+ 'card_type' => $cards[$card_index],
 150+ 'expiration' => date( 'my', strtotime( '+1 year 1 month' ) ),
 151+ 'cvv' => '001',
 152+ 'currency' => 'USD',
 153+ 'payment_method' => $wgRequest->getText( 'payment_method' ),
 154+ 'order_id' => '1234567890',
 155+ 'i_order_id' => '1234567890',
 156+ 'numAttempt' => 0,
 157+ 'referrer' => 'http://www.baz.test.com/index.php?action=foo&action=bar',
 158+ 'utm_source' => self::getUtmSource(),
 159+ 'utm_medium' => $wgRequest->getText( 'utm_medium' ),
 160+ 'utm_campaign' => $wgRequest->getText( 'utm_campaign' ),
 161+ 'language' => 'en',
 162+ 'comment-option' => $wgRequest->getText( 'comment-option' ),
 163+ 'comment' => $wgRequest->getText( 'comment' ),
 164+ 'email-opt' => $wgRequest->getText( 'email-opt' ),
 165+ 'test_string' => $wgRequest->getText( 'process' ),
 166+ 'token' => '',
 167+ 'contribution_tracking_id' => $wgRequest->getText( 'contribution_tracking_id' ),
 168+ 'data_hash' => $wgRequest->getText( 'data_hash' ),
 169+ 'action' => $wgRequest->getText( 'action' ),
 170+ 'gateway' => 'payflowpro',
 171+ 'owa_session' => $wgRequest->getText( 'owa_session', null ),
 172+ 'owa_ref' => 'http://localhost/defaultTestData',
 173+ 'transaction_type' => '', // Used by GlobalCollect for payment types
 174+ );
 175+ }
 176+ }
 177+
 178+ function isSomething( $key ) {
 179+ if ( array_key_exists( $key, $this->normalized ) && !empty( $this->normalized[$key] ) ) {
 180+ return true;
 181+ } else {
 182+ return false;
 183+ }
 184+ }
 185+
 186+ function getVal( $key ) {
 187+ if ( $this->isSomething( $key ) ) {
 188+ return $this->normalized[$key];
 189+ } else {
 190+ return null;
 191+ }
 192+ }
 193+
 194+ function setVal( $key, $val ) {
 195+ $this->normalized[$key] = $val;
 196+ }
 197+
 198+ function expunge( $key ) {
 199+ if ( array_key_exists( $key, $this->normalized ) ) {
 200+ unset( $this->normalized[$key] );
 201+ }
 202+ }
 203+
 204+ function normalizeAndSanitize() {
 205+ if ( !empty( $this->normalized ) ) {
 206+ $this->setNormalizedAmount();
 207+ $this->setNormalizedOrderIDs();
 208+ $this->setGateway();
 209+ $this->setNormalizedOptOuts();
 210+ array_walk( $this->normalized, array( $this, 'sanitizeInput' ) );
 211+ }
 212+ }
 213+
 214+ function setNormalizedAmount() {
 215+ if ( !($this->isSomething( 'amount' )) || !(preg_match( '/^\d+(\.(\d+)?)?$/', $this->getVal( 'amount' ) ) ) ) {
 216+ if ( $this->isSomething( 'amountGiven' ) && preg_match( '/^\d+(\.(\d+)?)?$/', $this->getVal( 'amountGiven' ) ) ) {
 217+ $this->setVal( 'amount', number_format( $this->getVal( 'amountGiven' ), 2, '.', '' ) );
 218+ } elseif ( $this->isSomething( 'amount' ) && $this->getVal( 'amount' ) == '-1' ) {
 219+ $this->setVal( 'amount', $this->getVal( 'amountOther' ) );
 220+ } else {
 221+ $this->setVal( 'amount', '0.00' );
 222+ }
 223+ }
 224+ }
 225+
 226+ function setOwaRefId() {
 227+ //Our data should already be pulled and whatever.
 228+ if ( $this->isSomething( 'owa_ref' ) && !is_numeric( $this->normalized['owa_ref'] ) ) {
 229+ $owa_ref = $this->get_owa_ref_id( $owa_ref );
 230+ }
 231+ }
 232+
 233+ function setNormalizedOrderIDs() {
 234+ //basically, we need a new order_id every time we come through here, but if there's an internal already there,
 235+ //we want to use that one internally. So.
 236+ //Exception: If we pass in an order ID in the querystring: Don't mess with it.
 237+ //TODO: I'm pretty sure I'm not supposed to do this directly.
 238+ if ( array_key_exists( 'order_id', $_GET ) ) {
 239+ $this->setVal( 'order_id', $_GET['order_id'] );
 240+ $this->setVal( 'i_order_id', $_GET['order_id'] );
 241+ return;
 242+ }
 243+
 244+ $this->setVal( 'order_id', $this->generateOrderId() );
 245+ if ( !$this->isSomething( 'i_order_id' ) ) {
 246+ $this->setVal( 'i_order_id', $this->generateOrderId() );
 247+ }
 248+ }
 249+
 250+ /**
 251+ * Generate an order id exactly once for this go-round.
 252+ */
 253+ function generateOrderId() {
 254+ static $order_id = null;
 255+ if ( $order_id === null ) {
 256+ $order_id = ( double ) microtime() * 1000000 . mt_rand( 1000, 9999 );
 257+ }
 258+ return $order_id;
 259+ }
 260+
 261+ /**
 262+ * Sanitize user input
 263+ *
 264+ * Intended to be used with something like array_walk
 265+ *
 266+ * @param $value The value of the array
 267+ * @param $key The key of the array
 268+ * @param $flags The flag constant for htmlspecialchars
 269+ * @param $double_encode Whether or not to double-encode strings
 270+ */
 271+ public function sanitizeInput( &$value, $key, $flags=ENT_COMPAT, $double_encode=false ) {
 272+ $value = htmlspecialchars( $value, $flags, 'UTF-8', $double_encode );
 273+ }
 274+
 275+ function log( $message, $log_level=LOG_INFO ) {
 276+ $c = $this->getAdapterClass();
 277+ if ( $c && is_callable( array( $c, 'log' ) )){
 278+ $c::log( $message, $log_level );
 279+ }
 280+ }
 281+
 282+ function getGatewayIdentifier() {
 283+ $c = $this->getAdapterClass();
 284+ if ( $c && is_callable( array( $c, 'getIdentifier' ) ) ){
 285+ return $c::getIdentifier();
 286+ } else {
 287+ return 'DonationData';
 288+ }
 289+ }
 290+
 291+ function getGatewayGlobal( $varname ) {
 292+ $c = $this->getAdapterClass();
 293+ if ( $c && is_callable( array( $c, 'getGlobal' ) ) ){
 294+ return $c::getGlobal( $varname );
 295+ } else {
 296+ return false;
 297+ }
 298+ }
 299+
 300+ function setGateway() {
 301+ //TODO: Hum. If we have some other gateway in the form data, should we go crazy here? (Probably)
 302+ $gateway = $this->gatewayID;
 303+ $this->setVal( 'gateway', $gateway );
 304+ }
 305+
 306+ function doCacheStuff() {
 307+ //TODO: Wow, name.
 308+ global $wgRequest;
 309+
 310+ // if _cache_ is requested by the user, do not set a session/token; dynamic data will be loaded via ajax
 311+ if ( $this->isSomething( '_cache_' ) ) {
 312+ self::log( $this->getAnnoyingOrderIDLogLinePrefix() . ' Cache requested', LOG_DEBUG );
 313+ $this->cache = true; //TODO: If we don't need this, kill it in the face.
 314+ $this->setVal( 'token', 'cache' );
 315+
 316+ // if we have squid caching enabled, set the maxage
 317+ global $wgUseSquid, $wgOut;
 318+ $maxAge = $this->getGatewayGlobal( 'SMaxAge' );
 319+
 320+ if ( $wgUseSquid && ( $maxAge !== false ) ) {
 321+ self::log( $this->getAnnoyingOrderIDLogLinePrefix() . ' Setting s-max-age: ' . $maxAge, LOG_DEBUG );
 322+ $wgOut->setSquidMaxage( $maxAge );
 323+ }
 324+ } else {
 325+ $this->cache = false; //TODO: Kill this one in the face, too. (see above)
 326+ }
 327+ }
 328+
 329+ function getAnnoyingOrderIDLogLinePrefix() {
 330+ //TODO: ...aww. But it's so descriptive.
 331+ return $this->getVal( 'order_id' ) . ' ' . $this->getVal( 'i_order_id' ) . ': ';
 332+ }
 333+
 334+ /**
 335+ * Establish an 'edit' token to help prevent CSRF, etc
 336+ *
 337+ * We use this in place of $wgUser->editToken() b/c currently
 338+ * $wgUser->editToken() is broken (apparently by design) for
 339+ * anonymous users. Using $wgUser->editToken() currently exposes
 340+ * a security risk for non-authenticated users. Until this is
 341+ * resolved in $wgUser, we'll use our own methods for token
 342+ * handling.
 343+ *
 344+ * @var mixed $salt
 345+ * @return string
 346+ */
 347+ public function getEditToken( $salt = '' ) {
 348+
 349+ // make sure we have a session open for tracking a CSRF-prevention token
 350+ self::ensureSession();
 351+
 352+ $gateway_ident = $this->gatewayID;
 353+
 354+ if ( !isset( $_SESSION[$gateway_ident . 'EditToken'] ) ) {
 355+ // generate unsalted token to place in the session
 356+ $token = self::generateToken();
 357+ $_SESSION[$gateway_ident . 'EditToken'] = $token;
 358+ } else {
 359+ $token = $_SESSION[$gateway_ident . 'EditToken'];
 360+ }
 361+
 362+ if ( is_array( $salt ) ) {
 363+ $salt = implode( "|", $salt );
 364+ }
 365+ return md5( $token . $salt ) . EDIT_TOKEN_SUFFIX;
 366+ }
 367+
 368+ /**
 369+ * Generate a token string
 370+ *
 371+ * @var mixed $salt
 372+ * @return string
 373+ */
 374+ public static function generateToken( $salt = '' ) {
 375+ $token = dechex( mt_rand() ) . dechex( mt_rand() );
 376+ return md5( $token . $salt );
 377+ }
 378+
 379+ /**
 380+ * Determine the validity of a token
 381+ *
 382+ * @var string $val
 383+ * @var mixed $salt
 384+ * @return bool
 385+ */
 386+ function matchEditToken( $val, $salt = '' ) {
 387+ // fetch a salted version of the session token
 388+ $sessionToken = $this->getEditToken( $salt );
 389+ if ( $val != $sessionToken ) {
 390+ wfDebug( "DonationData::matchEditToken: broken session data\n" );
 391+ }
 392+ $this->log( "Val = $val" );
 393+ $this->log( "Session Token = $sessionToken" );
 394+ return $val == $sessionToken;
 395+ }
 396+
 397+ /**
 398+ * Unset the payflow edit token from a user's session
 399+ */
 400+ function unsetEditToken() {
 401+ $gateway_ident = $this->gatewayID;
 402+
 403+ if ( isset( $_SESSION ) && isset( $_SESSION[$gateway_ident . 'EditToken'] ) ){
 404+ unset( $_SESSION[$gateway_ident . 'EditToken'] );
 405+ }
 406+ }
 407+
 408+ /**
 409+ * Ensure that we have a session set for the current user
 410+ *
 411+ * If we do not have a session set for the current user,
 412+ * start the session.
 413+ */
 414+ public static function ensureSession() {
 415+ // if the session is already started, do nothing
 416+ if ( session_id() )
 417+ return;
 418+
 419+ // otherwise, fire it up using global mw function wfSetupSession
 420+ wfSetupSession();
 421+ }
 422+
 423+ public function checkTokens() {
 424+ global $wgRequest;
 425+ static $match = null;
 426+
 427+ if ( $match === null ) {
 428+ $salt = $this->getGatewayGlobal( 'Salt' );
 429+ if ( $salt === false ){
 430+ $salt = 'gotToBeInAUnitTest';
 431+ }
 432+
 433+ // establish the edit token to prevent csrf
 434+ $token = $this->getEditToken( $salt );
 435+
 436+ $this->log( $this->getAnnoyingOrderIDLogLinePrefix() . ' editToken: ' . $token, LOG_DEBUG );
 437+
 438+ // match token
 439+ $token_check = ( $this->isSomething( 'token' ) ) ? $this->getVal( 'token' ) : $token; //TODO: does this suck as much as it looks like it does?
 440+ $match = $this->matchEditToken( $token_check, $salt );
 441+ if ( $wgRequest->wasPosted() ) {
 442+ $this->log( $this->getAnnoyingOrderIDLogLinePrefix() . ' Submitted edit token: ' . $this->getVal( 'token' ), LOG_DEBUG );
 443+ $this->log( $this->getAnnoyingOrderIDLogLinePrefix() . ' Token match: ' . ($match ? 'true' : 'false' ), LOG_DEBUG );
 444+ }
 445+ }
 446+
 447+ return $match;
 448+ }
 449+
 450+ /**
 451+ * Get the utm_source string
 452+ *
 453+ * Checks to see if the utm_source is set properly for the credit card
 454+ * form including any cc form variants (identified by utm_source_id). If
 455+ * anything cc form related is out of place for the utm_source, this
 456+ * will fix it.
 457+ *
 458+ * the utm_source is structured as: banner.landing_page.payment_instrument
 459+ *
 460+ * @param string $utm_source The utm_source for tracking - if not passed directly,
 461+ * we try to figure it out from the request object
 462+ * @param int $utm_source_id The utm_source_id for tracking - if not passed directly,
 463+ * we try to figure it out from the request object
 464+ * @return string The full utm_source
 465+ */
 466+ public static function getUtmSource( $utm_source = null, $utm_source_id = null ) {
 467+ global $wgRequest;
 468+
 469+ /**
 470+ * fetch whatever was passed in as the utm_source
 471+ *
 472+ * if utm_source was not passed in as a param, we try to divine it from
 473+ * the request. if it's not set there, no big deal, we'll just be
 474+ * missing some tracking data.
 475+ */
 476+ if ( is_null( $utm_source ) ) {
 477+ $utm_source = $wgRequest->getText( 'utm_source' );
 478+ }
 479+
 480+ /**
 481+ * if we have a utm_source_id, then the user is on a single-step credit card form.
 482+ * if that's the case, we treat the single-step credit card form as a landing page,
 483+ * which we label as cc#, where # = the utm_source_id
 484+ */
 485+ if ( is_null( $utm_source_id ) ) {
 486+ $utm_source_id = $wgRequest->getVal( 'utm_source_id', 0 );
 487+ }
 488+
 489+ // this is how the CC portion of the utm_source should be defined
 490+ $correct_cc_source = ( $utm_source_id ) ? 'cc' . $utm_source_id . '.cc' : 'cc';
 491+
 492+ // check to see if the utm_source is already correct - if so, return
 493+ if ( preg_match( '/' . str_replace( ".", "\.", $correct_cc_source ) . '$/', $utm_source ) ) {
 494+ return $utm_source;
 495+ }
 496+
 497+ // split the utm_source into its parts for easier manipulation
 498+ $source_parts = explode( ".", $utm_source );
 499+
 500+ // if there are no sourceparts element, then the banner portion of the string needs to be set.
 501+ // since we don't know what it is, set it to an empty string
 502+ if ( !count( $source_parts ) )
 503+ $source_parts[0] = '';
 504+
 505+ // if the utm_source_id is set, set the landing page portion of the string to cc#
 506+ $source_parts[1] = ( $utm_source_id ) ? 'cc' . $utm_source_id : ( isset( $source_parts[1] ) ? $source_parts[1] : '' );
 507+
 508+ // the payment instrument portion should always be 'cc' if this method is being accessed
 509+ $source_parts[2] = 'cc';
 510+
 511+ // return a reconstructed string
 512+ return implode( ".", $source_parts );
 513+ }
 514+
 515+ /**
 516+ * Determine proper opt-out settings for contribution tracking
 517+ *
 518+ * because the form elements for comment anonymization and email opt-out
 519+ * are backwards (they are really opt-in) relative to contribution_tracking
 520+ * (which is opt-out), we need to reverse the values.
 521+ * NOTE: If you prune here, and there is a paypal redirect, you will have
 522+ * problems with the email-opt/optout and comment-option/anonymous.
 523+ */
 524+ function setNormalizedOptOuts( $prune = false ) {
 525+ $optout['optout'] = ( $this->isSomething( 'email-opt' ) && $this->getVal( 'email-opt' ) == "1" ) ? '0' : '1';
 526+ $optout['anonymous'] = ( $this->isSomething( 'comment-option' ) && $this->getVal( 'comment-option' ) == "1" ) ? '0' : '1';
 527+ foreach ( $optout as $thing => $stuff ) {
 528+ $this->setVal( $thing, $stuff );
 529+ }
 530+ if ( $prune ) {
 531+ $this->expunge( 'email-opt' );
 532+ $this->expunge( 'comment-option' );
 533+ }
 534+ }
 535+
 536+ /**
 537+ * Clean array of tracking data to contain valid fields
 538+ *
 539+ * Compares tracking data array to list of valid tracking fields and
 540+ * removes any extra tracking fields/data. Also sets empty values to
 541+ * 'null' values.
 542+ * @param bool $clean_opouts
 543+ */
 544+ public function getCleanTrackingData() {
 545+
 546+ // define valid tracking fields
 547+ $tracking_fields = array(
 548+ 'note',
 549+ 'referrer',
 550+ 'anonymous',
 551+ 'utm_source',
 552+ 'utm_medium',
 553+ 'utm_campaign',
 554+ 'optout',
 555+ 'language',
 556+ 'ts'
 557+ );
 558+
 559+ foreach ( $tracking_fields as $value ) {
 560+ if ( $this->isSomething( $value ) ) {
 561+ $tracking_data[$value] = $this->getVal( $value );
 562+ } else {
 563+ $tracking_data[$value] = null;
 564+ }
 565+ }
 566+
 567+ return $tracking_data;
 568+ }
 569+
 570+ //if ( !empty($data) && ( $data[ 'numAttempt' ] == '0' && ( !$wgRequest->getText( 'utm_source_id', false ) || $wgRequest->getText( '_nocache_' ) == 'true' ) ) ) {
 571+ //so, basically, if this is the first attempt. This seems to get called nowhere else.
 572+ function saveContributionTracking() {
 573+
 574+ $tracked_contribution = $this->getCleanTrackingData();
 575+
 576+ // insert tracking data and get the tracking id
 577+ $result = self::insertContributionTracking( $tracked_contribution );
 578+
 579+ $this->setVal( 'contribution_tracking_id', $result );
 580+
 581+ if ( !$result ) {
 582+ return false;
 583+ }
 584+ return true;
 585+ }
 586+
 587+ /**
 588+ * Insert a record into the contribution_tracking table
 589+ *
 590+ * @param array $tracking_data The array of tracking data to insert to contribution_tracking
 591+ * @return mixed Contribution tracking ID or false on failure
 592+ */
 593+ public static function insertContributionTracking( $tracking_data ) {
 594+ $db = ContributionTrackingProcessor::contributionTrackingConnection();
 595+
 596+ if ( !$db ) {
 597+ return false;
 598+ }
 599+
 600+ // set the time stamp if it's not already set
 601+ if ( !isset( $tracking_data['ts'] ) || !strlen( $tracking_data['ts'] ) ) {
 602+ $tracking_data['ts'] = $db->timestamp();
 603+ }
 604+
 605+ // Store the contribution data
 606+ if ( $db->insert( 'contribution_tracking', $tracking_data ) ) {
 607+ return $db->insertId();
 608+ } else {
 609+ return false;
 610+ }
 611+ }
 612+
 613+ /**
 614+ * Update contribution_tracking table
 615+ *
 616+ * @param array $data Form data
 617+ * @param bool $force If set to true, will ensure that contribution tracking is updated
 618+ */
 619+ //Looks like two places: Either right before a paypal redirect (if that's still a thing) or
 620+ //just prior to curling something up to some server somewhere.
 621+ //I took care of the one just prior to curling.
 622+ public function updateContributionTracking( $force = false ) {
 623+ // ony update contrib tracking if we're coming from a single-step landing page
 624+ // which we know with cc# in utm_source or if force=true or if contribution_tracking_id is not set
 625+ if ( !$force &&
 626+ !preg_match( "/cc[0-9]/", $this->getVal( 'utm_source' ) ) &&
 627+ is_numeric( $this->getVal( 'contribution_tracking_id' ) ) ) {
 628+ return;
 629+ }
 630+
 631+ $db = ContributionTrackingProcessor::contributionTrackingConnection();
 632+
 633+ if ( !$db ) {
 634+ return true;
 635+ } ///wait, what? TODO: This line was straight copied from the _gateway.body. Find out if there's a good reason we're not returning false here.
 636+
 637+ $tracked_contribution = $this->getCleanTrackingData();
 638+
 639+ // if contrib tracking id is not already set, we need to insert the data, otherwise update
 640+ if ( !$this->getVal( 'contribution_tracking_id' ) ) {
 641+ $this->setVal( 'contribution_tracking_id', $this->insertContributionTracking( $tracked_contribution ) );
 642+ } else {
 643+ $db->update( 'contribution_tracking', $tracked_contribution, array( 'id' => $this->getVal( 'contribution_tracking_id' ) ) );
 644+ }
 645+ }
 646+
 647+ public function addDonorDataToSession() {
 648+ self::ensureSession();
 649+ $donordata = array(
 650+ 'email',
 651+ 'fname',
 652+ 'mname',
 653+ 'lname',
 654+ 'street',
 655+ 'city',
 656+ 'state',
 657+ 'zip',
 658+ 'country',
 659+ 'contribution_tracking_id',
 660+ 'referrer'
 661+ );
 662+
 663+ foreach ( $donordata as $item ) {
 664+ if ( $this->isSomething( $item ) ) {
 665+ $_SESSION['Donor'][$item] = $this->getVal( $item );
 666+ }
 667+ }
 668+ }
 669+
 670+ public function populateDonorFromSession() {
 671+ if ( array_key_exists( 'Donor', $_SESSION ) ) {
 672+ $this->addData( $_SESSION['Donor'] );
 673+ }
 674+ }
 675+
 676+ /**
 677+ * TODO: Consider putting all the session data for a gateway under something like
 678+ * $_SESSION[$gateway_identifier]
 679+ * so we can kill it all with one stroke.
 680+ */
 681+ public function unsetAllDDSessionData() {
 682+ unset( $_SESSION['Donor'] );
 683+ $this->unsetEditToken();
 684+ }
 685+
 686+ public function addData( $newdata ) {
 687+ if ( is_array( $newdata ) && !empty( $newdata ) ) {
 688+ foreach ( $newdata as $key => $val ) {
 689+ if ( !is_array( $val ) ) {
 690+ $this->setVal( $key, $val );
 691+ }
 692+ }
 693+ }
 694+ $this->normalizeAndSanitize();
 695+ }
 696+
 697+ public function incrementNumAttempt() {
 698+ if ( $this->isSomething( 'numAttempt' ) ) {
 699+ $attempts = $this->getVal( 'numAttempt' );
 700+ if ( is_numeric( $attempts ) ) {
 701+ $this->setVal( 'numAttempt', $attempts + 1 );
 702+ } else {
 703+ //assume garbage = 0, so...
 704+ $this->setVal( 'numAttempt', 1 );
 705+ }
 706+ }
 707+ }
 708+
 709+ function getAdapterClass(){
 710+ if ( class_exists( $this->boss ) ) {
 711+ return $this->boss;
 712+ } else {
 713+ return false;
 714+ }
 715+ }
 716+
 717+}
 718+
 719+?>
Property changes on: trunk/extensions/DonationInterface/gateway_common/DonationData.php
___________________________________________________________________
Added: svn:eol-style
1720 + native
Index: trunk/extensions/DonationInterface/gateway_common/GatewayForm.php
@@ -0,0 +1,542 @@
 2+<?php
 3+
 4+class GatewayForm extends UnlistedSpecialPage {
 5+
 6+ /**
 7+ * Defines the action to take on a transaction.
 8+ *
 9+ * Possible values include 'process', 'challenge',
 10+ * 'review', 'reject'. These values can be set during
 11+ * data processing validation, for instance.
 12+ *
 13+ * Hooks are exposed to handle the different actions.
 14+ *
 15+ * Defaults to 'process'.
 16+ * @var string
 17+ */
 18+ public $action = 'process';
 19+
 20+ /**
 21+ * A container for the form class
 22+ *
 23+ * Used to loard the form object to display the CC form
 24+ * @var object
 25+ */
 26+ public $form_class;
 27+
 28+ /**
 29+ * An array of form errors
 30+ * @var array
 31+ */
 32+ public $errors = array( );
 33+ public $adapter; //the adapter goes here.
 34+
 35+ /**
 36+ * The form is assumed to be successful. Errors in the form must set this to
 37+ * false.
 38+ *
 39+ * @var boolean
 40+ */
 41+ public $validateFormResult = true;
 42+
 43+ function __construct() {
 44+ $me = get_called_class();
 45+ parent::__construct( $me );
 46+ $this->errors = $this->getPossibleErrors();
 47+ $this->setFormClass();
 48+ }
 49+
 50+ /**
 51+ * Checks posted form data for errors and returns array of messages
 52+ *
 53+ * This is update to GatewayForm::fnValidateForm().
 54+ *
 55+ * @param array $data Reference to the data of the form
 56+ * @param array $error Reference to the error messages of the form
 57+ * @param array $options
 58+ * OPTIONAL - You may require certain field groups to be validated
 59+ * - address - Validates: street, city, state, zip
 60+ * - amount - Validates: amount
 61+ * - creditCard - Validates: card_num, cvv, expiration and sets the card
 62+ * - email - Validates: email
 63+ * - name - Validates: fname, lname
 64+ *
 65+ * @return 0|1 Returns 0 on success and 1 on failure
 66+ *
 67+ * @see GatewayForm::fnValidateForm()
 68+ */
 69+ public function validateForm( &$data, &$error, $options = array( ) ) {
 70+
 71+ extract( $options );
 72+
 73+ // Set which items will be validated
 74+ $address = isset( $address ) ? ( boolean ) $address : true;
 75+ $amount = isset( $amount ) ? ( boolean ) $amount : true;
 76+ $creditCard = isset( $creditCard ) ? ( boolean ) $creditCard : false;
 77+ $email = isset( $email ) ? ( boolean ) $email : true;
 78+ $name = isset( $name ) ? ( boolean ) $name : true;
 79+
 80+ // These are set in the order they will most likely appear on the form.
 81+
 82+ if ( $name ) {
 83+ $this->validateName( $data, $error );
 84+ }
 85+
 86+ if ( $address ) {
 87+ $this->validateAddress( $data, $error );
 88+ }
 89+
 90+ if ( $amount ) {
 91+ $this->validateAmount( $data, $error );
 92+ }
 93+
 94+ if ( $email ) {
 95+ $this->validateEmail( $data, $error );
 96+ }
 97+
 98+ if ( $creditCard ) {
 99+ $this->validateCreditCard( $data, $error );
 100+ }
 101+
 102+ /*
 103+ * $error_result would return 0 on success, 1 on failure.
 104+ *
 105+ * This is done for backward compatibility.
 106+ */
 107+ return $this->getValidateFormResult() ? 0 : 1;
 108+ }
 109+
 110+ /**
 111+ * Validates the address
 112+ *
 113+ * @param array $data Reference to the data of the form
 114+ * @param array $error Reference to the error messages of the form
 115+ *
 116+ * @see GatewayForm::validateForm()
 117+ */
 118+ public function validateAddress( &$data, &$error ) {
 119+
 120+ if ( empty( $data['street'] ) ) {
 121+
 122+ $error['street'] = wfMsg( $this->adapter->getIdentifier() . '_gateway-error-msg-street' );
 123+
 124+ $this->setValidateFormResult( false );
 125+ }
 126+
 127+ if ( empty( $data['city'] ) ) {
 128+
 129+ $error['city'] = wfMsg( $this->adapter->getIdentifier() . '_gateway-error-msg-city' );
 130+
 131+ $this->setValidateFormResult( false );
 132+ }
 133+
 134+ if ( empty( $data['state'] ) ) {
 135+
 136+ $error['state'] = wfMsg( $this->adapter->getIdentifier() . '_gateway-error-msg-state' );
 137+
 138+ $this->setValidateFormResult( false );
 139+ }
 140+
 141+ if ( empty( $data['zip'] ) && $data['state'] != 'XX') {
 142+
 143+ $error['zip'] = wfMsg( $this->adapter->getIdentifier() . '_gateway-error-msg-zip' );
 144+
 145+ $this->setValidateFormResult( false );
 146+ }
 147+ }
 148+
 149+ /**
 150+ * Validates the amount contributed
 151+ *
 152+ * @param array $data Reference to the data of the form
 153+ * @param array $error Reference to the error messages of the form
 154+ *
 155+ * @see GatewayForm::validateForm()
 156+ */
 157+ public function validateAmount( &$data, &$error ) {
 158+
 159+ if ( empty( $data['amount'] ) ) {
 160+
 161+ $error['amount'] = wfMsg( $this->adapter->getIdentifier() . '_gateway-error-msg-amount' );
 162+
 163+ $this->setValidateFormResult( false );
 164+ }
 165+
 166+ // check amount
 167+ $priceFloor = $this->adapter->getGlobal( 'PriceFloor' );
 168+ $priceCeiling = $this->adapter->getGlobal( 'PriceCeiling' );
 169+ if ( !preg_match( '/^\d+(\.(\d+)?)?$/', $data['amount'] ) ||
 170+ ( ( float ) $this->convert_to_usd( $data['currency'], $data['amount'] ) < ( float ) $priceFloor ||
 171+ ( float ) $this->convert_to_usd( $data['currency'], $data['amount'] ) > ( float ) $priceCeiling ) ) {
 172+
 173+ $error['invalidamount'] = wfMsg( $this->adapter->getIdentifier() . '_gateway-error-msg-invalid-amount' );
 174+
 175+ $this->setValidateFormResult( false );
 176+ }
 177+ }
 178+
 179+ /**
 180+ * Validates a credit card
 181+ *
 182+ * @param array $data Reference to the data of the form
 183+ * @param array $error Reference to the error messages of the form
 184+ *
 185+ * @see GatewayForm::validateForm()
 186+ */
 187+ public function validateCreditCard( &$data, &$error ) {
 188+
 189+ if ( empty( $data['card_num'] ) ) {
 190+
 191+ $error['card_num'] = wfMsg( $this->adapter->getIdentifier() . '_gateway-error-msg-card_num' );
 192+
 193+ $this->setValidateFormResult( false );
 194+ }
 195+
 196+ if ( empty( $data['cvv'] ) ) {
 197+
 198+ $error['cvv'] = wfMsg( $this->adapter->getIdentifier() . '_gateway-error-msg-cvv' );
 199+
 200+ $this->setValidateFormResult( false );
 201+ }
 202+
 203+ if ( empty( $data['expiration'] ) ) {
 204+
 205+ $error['expiration'] = wfMsg( $this->adapter->getIdentifier() . '_gateway-error-msg-expiration' );
 206+
 207+ $this->setValidateFormResult( false );
 208+ }
 209+
 210+ // validate that credit card number entered is correct and set the card type
 211+ if ( preg_match( '/^3[47][0-9]{13}$/', $data['card_num'] ) ) { // american express
 212+ $data['card'] = 'american';
 213+ } elseif ( preg_match( '/^5[1-5][0-9]{14}$/', $data['card_num'] ) ) { // mastercard
 214+ $data['card'] = 'mastercard';
 215+ } elseif ( preg_match( '/^4[0-9]{12}(?:[0-9]{3})?$/', $data['card_num'] ) ) {// visa
 216+ $data['card'] = 'visa';
 217+ } elseif ( preg_match( '/^6(?:011|5[0-9]{2})[0-9]{12}$/', $data['card_num'] ) ) { // discover
 218+ $data['card'] = 'discover';
 219+ } else { // an invalid credit card number was entered
 220+ $error['card_num'] = wfMsg( $this->adapter->getIdentifier() . '_gateway-error-msg-card-num' );
 221+
 222+ $this->setValidateFormResult( false );
 223+ }
 224+ }
 225+
 226+ /**
 227+ * Validates an email address.
 228+ *
 229+ * @param array $data Reference to the data of the form
 230+ * @param array $error Reference to the error messages of the form
 231+ *
 232+ * @see GatewayForm::validateForm()
 233+ */
 234+ public function validateEmail( &$data, &$error ) {
 235+
 236+ if ( empty( $data['email'] ) ) {
 237+
 238+ $error['email'] = wfMsg( $this->adapter->getIdentifier() . '_gateway-error-email-empty' );
 239+
 240+ $this->setValidateFormResult( false );
 241+ }
 242+
 243+ // is email address valid?
 244+ $isEmail = User::isValidEmailAddr( $data['email'] );
 245+
 246+ // create error message (supercedes empty field message)
 247+ if ( !$isEmail ) {
 248+ $error['email'] = wfMsg( $this->adapter->getIdentifier() . '_gateway-error-msg-email' );
 249+
 250+ $this->setValidateFormResult( false );
 251+ }
 252+ }
 253+
 254+ /**
 255+ * Validates the name
 256+ *
 257+ * @param array $data Reference to the data of the form
 258+ * @param array $error Reference to the error messages of the form
 259+ *
 260+ * @see GatewayForm::validateForm()
 261+ */
 262+ public function validateName( &$data, &$error ) {
 263+
 264+ if ( empty( $data['fname'] ) ) {
 265+
 266+ $error['fname'] = wfMsg( $this->adapter->getIdentifier() . '_gateway-error-msg-fname' );
 267+
 268+ $this->setValidateFormResult( false );
 269+ }
 270+
 271+ if ( empty( $data['lname'] ) ) {
 272+
 273+ $error['lname'] = wfMsg( $this->adapter->getIdentifier() . '_gateway-error-msg-lname' );
 274+
 275+ $this->setValidateFormResult( false );
 276+ }
 277+ }
 278+
 279+ /**
 280+ * Build and display form to user
 281+ *
 282+ * @param $data Array: array of posted user input
 283+ * @param $error Array: array of error messages returned by validate_form function
 284+ *
 285+ * The message at the top of the form can be edited in the payflow_gateway.i18n.php file
 286+ */
 287+ public function displayForm( &$data, &$error ) {
 288+ global $wgOut, $wgRequest;
 289+
 290+ $form_class = $this->getFormClass();
 291+ $form_obj = new $form_class( $data, $error, $this->adapter );
 292+ $form = $form_obj->getForm();
 293+ $wgOut->addHTML( $form );
 294+ }
 295+
 296+ /**
 297+ * Set the form class to use to generate the CC form
 298+ *
 299+ * @param string $class_name The class name of the form to use
 300+ */
 301+ public function setFormClass( $class_name = NULL ) {
 302+ if ( !$class_name ) {
 303+ global $wgRequest;
 304+ $defaultForm = $this->adapter->getGlobal( 'DefaultForm' );
 305+ $form_class = $wgRequest->getText( 'form_name', $defaultForm );
 306+
 307+ // make sure our form class exists before going on, if not try loading default form class
 308+ $class_name = "Gateway_Form_" . $form_class;
 309+ if ( !class_exists( $class_name ) ) {
 310+ $class_name_orig = $class_name;
 311+ $class_name = "Gateway_Form_" . $defaultForm;
 312+ if ( !class_exists( $class_name ) ) {
 313+ throw new MWException( 'Could not load form ' . $class_name_orig . ' nor default form ' . $class_name );
 314+ }
 315+ }
 316+ }
 317+ $this->form_class = $class_name;
 318+
 319+ //this should... maybe replace the other thing? I need it in the adapter so reCaptcha can get to it.
 320+ $this->adapter->setFormClass( $class_name );
 321+ }
 322+
 323+ /**
 324+ * Get the currently set form class
 325+ *
 326+ * Will set the form class if the form class not already set
 327+ * Using logic in setFormClass()
 328+ * @return string
 329+ */
 330+ public function getFormClass() {
 331+ if ( !isset( $this->form_class ) ) {
 332+ $this->setFormClass();
 333+ }
 334+ return $this->form_class;
 335+ }
 336+
 337+ function displayResultsForDebug( $results ) {
 338+ global $wgOut;
 339+ if ( $this->adapter->getGlobal( 'DisplayDebug' ) !== true ){
 340+ return;
 341+ }
 342+ $wgOut->addHTML( HTML::element( 'span', null, $results['message'] ) );
 343+
 344+ if ( !empty( $results['errors'] ) ) {
 345+ $wgOut->addHTML( "<ul>" );
 346+ foreach ( $results['errors'] as $code => $value ) {
 347+ $wgOut->addHTML( HTML::element('li', null, "Error $code: $value" ) );
 348+ }
 349+ $wgOut->addHTML( "</ul>" );
 350+ }
 351+
 352+ if ( !empty( $results['data'] ) ) {
 353+ $wgOut->addHTML( "<ul>" );
 354+ foreach ( $results['data'] as $key => $value ) {
 355+ if ( is_array( $value ) ) {
 356+ $wgOut->addHTML( HTML::element('li', null, $key ) . '<ul>' );
 357+ foreach ( $value as $key2 => $val2 ) {
 358+ $wgOut->addHTML( HTML::element('li', null, "$key2: $val2" ) );
 359+ }
 360+ $wgOut->addHTML( "</ul>" );
 361+ } else {
 362+ $wgOut->addHTML( HTML::element('li', null, "$key: $value" ) );
 363+ }
 364+ }
 365+ $wgOut->addHTML( "</ul>" );
 366+ } else {
 367+ $wgOut->addHTML( "Empty Results" );
 368+ }
 369+ if ( array_key_exists( 'Donor', $_SESSION ) ) {
 370+ $wgOut->addHTML( "Session Donor Vars:<ul>" );
 371+ foreach ( $_SESSION['Donor'] as $key => $val ) {
 372+ $wgOut->addHTML( HTML::element('li', null, "$key: $val" ) );
 373+ }
 374+ $wgOut->addHTML( "</ul>" );
 375+ } else {
 376+ $wgOut->addHTML( "No Session Donor Vars:<ul>" );
 377+ }
 378+
 379+ if ( is_array( $this->adapter->debugarray ) ) {
 380+ $wgOut->addHTML( "Debug Array:<ul>" );
 381+ foreach ( $this->adapter->debugarray as $val ) {
 382+ $wgOut->addHTML( HTML::element('li', null, $val ) );
 383+ }
 384+ $wgOut->addHTML( "</ul>" );
 385+ } else {
 386+ $wgOut->addHTML( "No Debug Array<ul>" );
 387+ }
 388+ }
 389+
 390+ public function getPossibleErrors() {
 391+ return array(
 392+ 'general' => '',
 393+ 'retryMsg' => '',
 394+ 'invalidamount' => '',
 395+ 'card_num' => '',
 396+ 'card_type' => '',
 397+ 'cvv' => '',
 398+ 'fname' => '',
 399+ 'lname' => '',
 400+ 'city' => '',
 401+ 'country' => '',
 402+ 'street' => '',
 403+ 'state' => '',
 404+ 'zip' => '',
 405+ 'emailAdd' => '',
 406+ );
 407+ }
 408+
 409+ /**
 410+ * Convert an amount for a particular currency to an amount in USD
 411+ *
 412+ * This is grosley rudimentary and likely wildly inaccurate.
 413+ * This mimicks the hard-coded values used by the WMF to convert currencies
 414+ * for validatoin on the front-end on the first step landing pages of their
 415+ * donation process - the idea being that we can get a close approximation
 416+ * of converted currencies to ensure that contributors are not going above
 417+ * or below the price ceiling/floor, even if they are using a non-US currency.
 418+ *
 419+ * In reality, this probably ought to use some sort of webservice to get real-time
 420+ * conversion rates.
 421+ *
 422+ * @param $currency_code
 423+ * @param $amount
 424+ * @return unknown_type
 425+ */
 426+ public function convert_to_usd( $currency_code, $amount ) {
 427+ switch ( strtoupper( $currency_code ) ) {
 428+ case 'USD':
 429+ $usd_amount = $amount / 1;
 430+ break;
 431+ case 'GBP':
 432+ $usd_amount = $amount / 1;
 433+ break;
 434+ case 'EUR':
 435+ $usd_amount = $amount / 1;
 436+ break;
 437+ case 'AUD':
 438+ $usd_amount = $amount / 2;
 439+ break;
 440+ case 'CAD':
 441+ $usd_amount = $amount / 1;
 442+ break;
 443+ case 'CHF':
 444+ $usd_amount = $amount / 1;
 445+ break;
 446+ case 'CZK':
 447+ $usd_amount = $amount / 20;
 448+ break;
 449+ case 'DKK':
 450+ $usd_amount = $amount / 5;
 451+ break;
 452+ case 'HKD':
 453+ $usd_amount = $amount / 10;
 454+ break;
 455+ case 'HUF':
 456+ $usd_amount = $amount / 200;
 457+ break;
 458+ case 'JPY':
 459+ $usd_amount = $amount / 100;
 460+ break;
 461+ case 'NZD':
 462+ $usd_amount = $amount / 2;
 463+ break;
 464+ case 'NOK':
 465+ $usd_amount = $amount / 10;
 466+ break;
 467+ case 'PLN':
 468+ $usd_amount = $amount / 5;
 469+ break;
 470+ case 'SGD':
 471+ $usd_amount = $amount / 2;
 472+ break;
 473+ case 'SEK':
 474+ $usd_amount = $amount / 10;
 475+ break;
 476+ case 'ILS':
 477+ $usd_amount = $amount / 5;
 478+ break;
 479+ default:
 480+ $usd_amount = $amount;
 481+ break;
 482+ }
 483+
 484+ return $usd_amount;
 485+ }
 486+
 487+ public function log( $msg, $log_level=LOG_INFO ) {
 488+ $this->adapter->log( $msg, $log_level );
 489+ }
 490+
 491+ /**
 492+ * Handle redirection of form content to PayPal
 493+ *
 494+ * @fixme If we can update contrib tracking table in ContributionTracking
 495+ * extension, we can probably get rid of this method and just submit the form
 496+ * directly to the paypal URL, and have all processing handled by ContributionTracking
 497+ * This would make this a lot less hack-ish
 498+ */
 499+ public function paypalRedirect() {
 500+ global $wgOut;
 501+
 502+ // if we don't have a URL enabled throw a graceful error to the user
 503+ if ( !strlen( $this->adapter->getGlobal( 'PaypalURL' ) ) ) {
 504+ $gateway_identifier = $this->adapter->getIdentifier();
 505+ $this->errors['general']['nopaypal'] = wfMsg( $gateway_identifier . '_gateway-error-msg-nopaypal' );
 506+ return;
 507+ }
 508+ // submit the data to the paypal redirect URL
 509+ $wgOut->redirect( $this->adapter->getPaypalRedirectURL() );
 510+ }
 511+
 512+ /**
 513+ * Fetch the array of iso country codes => country names
 514+ * @return array
 515+ */
 516+ public static function getCountries() {
 517+ require_once( dirname( __FILE__ ) . '/../gateway_forms/includes/countryCodes.inc' );
 518+ return countryCodes();
 519+ }
 520+
 521+ /**
 522+ * Get validate form result
 523+ *
 524+ * @return boolean
 525+ */
 526+ public function getValidateFormResult() {
 527+
 528+ return ( boolean ) $this->validateFormResult;
 529+ }
 530+
 531+ /**
 532+ * Set validate form result
 533+ *
 534+ * @param boolean $validateFormResult
 535+ */
 536+ public function setValidateFormResult( $validateFormResult ) {
 537+
 538+ $this->validateFormResult = empty( $validateFormResult ) ? false : ( boolean ) $validateFormResult;
 539+ }
 540+
 541+}
 542+
 543+//end of GatewayForm class definiton
Property changes on: trunk/extensions/DonationInterface/gateway_common/GatewayForm.php
___________________________________________________________________
Added: svn:eol-style
1544 + native

Status & tagging log