Ejegg has uploaded a new change for review. ( https://gerrit.wikimedia.org/r/364339 )
Change subject: buildRequestArray handles recursion, used for name/value ...................................................................... buildRequestArray handles recursion, used for name/value Add a new function for building array structures for API requests, similar to the XML builders. Change-Id: I9f0775720408a0eff685cd311295c33e327c4b01 --- M gateway_common/ArrayHelper.php M gateway_common/gateway.adapter.php A tests/phpunit/ArrayHelperTest.php 3 files changed, 233 insertions(+), 39 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/DonationInterface refs/changes/39/364339/1 diff --git a/gateway_common/ArrayHelper.php b/gateway_common/ArrayHelper.php index 7256e91..c0cece8 100644 --- a/gateway_common/ArrayHelper.php +++ b/gateway_common/ArrayHelper.php @@ -19,5 +19,59 @@ return array_combine( $keys, $values ); } + + /** + * Create an array mirroring the given structure, but with values obtained + * from a callback. Like array_map, but with a few differences + * - recurses when elements are themselves arrays + * - if the callback returns '' or false, the element is skipped in the output + * - if the element is a key with value null, the output element has no key + * + * @param callable $callback + * @param array $structure + * @return array + */ + public static function buildRequestArray( $callback, $structure ) { + $data = array(); + foreach ( $structure as $key => $value ) { + self::addArrayElement( $data, $structure, $key, $value, $callback ); + } + return $data; + } + + protected static function addArrayElement( &$targetElement, $structureElement, $key, $value, $callback ) { + if ( is_numeric( $key ) ) { + // it's just a value, not an associative array key + $fieldName = $value; + $fieldValue = $callback( $fieldName ); + if ( self::includeElement( $fieldValue ) ) { + $targetElement[$fieldName] = $fieldValue; + } + return; + } + if ( is_array( $structureElement[$key] ) ) { + $targetElement[$key] = array(); + foreach ( $structureElement[$key] as $subKey => $subValue ) { + self::addArrayElement( + $targetElement[$key], + $structureElement[$key], + $subKey, + $subValue, + $callback + ); + } + // TODO: If all children are skipped, remove $targetElement[$key] ? + } else if ( $structureElement[$key] === null ) { + // HACK: needed a way to specify non-associative arrays in the output + $fieldValue = $callback( $key ); + if ( self::includeElement( $fieldValue ) ) { + $targetElement[] = $fieldValue; + } + } + } + + protected static function includeElement( $value ) { + return ( $value !== '' && $value !== false ); + } } diff --git a/gateway_common/gateway.adapter.php b/gateway_common/gateway.adapter.php index e34988d..5c73969 100644 --- a/gateway_common/gateway.adapter.php +++ b/gateway_common/gateway.adapter.php @@ -742,22 +742,19 @@ * curl'd off to the remote server. */ protected function buildRequestNameValueString() { + $data = $this->buildRequestArray(); + $ret = http_build_query( $data ); + return $ret; + } + + protected function buildRequestArray() { // Look up the request structure for our current transaction type in the transactions array $structure = $this->getTransactionRequestStructure(); if ( !is_array( $structure ) ) { - return ''; + return array(); } - - $data = array(); - foreach ( $structure as $fieldname ) { - $fieldvalue = $this->getTransactionSpecificValue( $fieldname ); - if ( $fieldvalue !== '' && $fieldvalue !== false ) { - $data[$fieldname] = $fieldvalue; - } - } - - $ret = http_build_query( $data ); - return $ret; + $callback = array( $this, 'getTransactionSpecificValue' ); + return ArrayHelper::buildRequestArray( $callback, $structure ); } /** @@ -1002,40 +999,45 @@ // TODO: Maybe move this to the pre_process functions? $this->dataObj->saveContributionTrackingData(); } - $commType = $this->getCommunicationType(); - if ( $commType === 'redirect' ) { + switch( $commType ) { + case 'redirect': - //in the event that we have a redirect transaction that never displays the form, - //save this most recent one before we leave. - $this->session_pushFormName( $this->getData_Unstaged_Escaped( 'ffname' ) ); + //in the event that we have a redirect transaction that never displays the form, + //save this most recent one before we leave. + $this->session_pushFormName( $this->getData_Unstaged_Escaped( 'ffname' ) ); - $this->transaction_response->setCommunicationStatus( true ); + $this->transaction_response->setCommunicationStatus( true ); - // Build the redirect URL. - $redirectUrl = $this->getProcessorUrl(); - $redirectParams = $this->buildRequestParams(); - if ( $redirectParams ) { - // Add GET parameters, if provided. - $redirectUrl .= '?' . http_build_query( $redirectParams ); - } + // Build the redirect URL. + $redirectUrl = $this->getProcessorUrl(); + $redirectParams = $this->buildRequestParams(); + if ( $redirectParams ) { + // Add GET parameters, if provided. + $redirectUrl .= '?' . http_build_query( $redirectParams ); + } - $this->transaction_response->setRedirect( $redirectUrl ); + $this->transaction_response->setRedirect( $redirectUrl ); - return $this->transaction_response; + return $this->transaction_response; - } elseif ( $commType === 'xml' ) { - $this->profiler->getStopwatch( "buildRequestXML", true ); // begin profiling - $curlme = $this->buildRequestXML(); // build the XML - $this->profiler->saveCommunicationStats( "buildRequestXML", $transaction ); // save profiling data - - } elseif ( $commType === 'namevalue' ) { - $this->profiler->getStopwatch( "buildRequestNameValueString", true ); // begin profiling - $curlme = $this->buildRequestNameValueString(); // build the name/value pairs - $this->profiler->saveCommunicationStats( "buildRequestNameValueString", $transaction ); // save profiling data - - } else { - throw new UnexpectedValueException( "Communication type of '{$commType}' unknown" ); + case 'xml': + $this->profiler->getStopwatch( "buildRequestXML", true ); // begin profiling + $curlme = $this->buildRequestXML(); // build the XML + $this->profiler->saveCommunicationStats( "buildRequestXML", $transaction ); // save profiling data + break; + case 'namevalue': + $this->profiler->getStopwatch( "buildRequestNameValueString", true ); // begin profiling + $curlme = $this->buildRequestNameValueString(); // build the name/value pairs + $this->profiler->saveCommunicationStats( "buildRequestNameValueString", $transaction ); + break; + case 'array': + $this->profiler->getStopwatch( "buildRequestNameValueString", true ); // begin profiling + $curlme = $this->buildRequestArray(); // build the name/value pairs + $this->profiler->saveCommunicationStats( "buildRequestNameValueString", $transaction ); + break; + default: + throw new UnexpectedValueException( "Communication type of '{$commType}' unknown" ); } } catch ( Exception $e ) { $this->logger->critical( 'Malformed gateway definition. Cannot continue: Aborting.\n' . $e->getMessage() ); diff --git a/tests/phpunit/ArrayHelperTest.php b/tests/phpunit/ArrayHelperTest.php new file mode 100644 index 0000000..979fd28 --- /dev/null +++ b/tests/phpunit/ArrayHelperTest.php @@ -0,0 +1,138 @@ +<?php +/** + * Wikimedia Foundation + * + * LICENSE + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/** + * @group Fundraising + * @group DonationInterface + * @group ArrayHelper + */ +class ArrayHelperTest extends PHPUnit_Framework_TestCase { + + /** + * @dataProvider getTestData + */ + public function testBuildRequestArray( $description, $input, $expected ) { + $actual = ArrayHelper::buildRequestArray( $this->getCallback(), $input ); + $message = "buildRequestArray failed for $description"; + $this->assertEquals( $expected, $actual, $message ); + } + + public function getTestData() { + return array( + // 1st test case + array( + 'flat array', + // input + array( + 'double', + 'toil', + 'trouble' + ), + // output + array( + 'double' => 'value for -double-', + 'toil' => 'value for -toil-', + 'trouble' => 'value for -trouble-', + ), + ), + // 2nd test case + array( + 'nested array', + // input + array( + 'fire' => array( + 'burn', + ), + 'cauldron' => array( + 'bubble', + ) + ), + // output + array( + 'fire' => array( + 'burn' => 'value for -burn-', + ), + 'cauldron' => array( + 'bubble' => 'value for -bubble-', + ) + ), + ), + // 3rd test case + array( + 'mixed array', + // input + array( + 'poisoned entrails', + 'toad' => array( + 'under_cold_stone', + 'days_and_nights', + 'thirty_one' + ), + ), + // output + array( + 'poisoned entrails' => 'value for -poisoned entrails-', + 'toad' => array( + 'under_cold_stone' => 'value for -under_cold_stone-', + 'days_and_nights' => 'value for -days_and_nights-', + 'thirty_one' => 'value for -thirty_one-' + ) + ), + ), + // 4th test case + array( + 'omit empty', + // input + array( + 'eye_of_newt', + 'toe_of_frog', + 'wool_of_bat', // callback returns '' for this value + 'tongue_of_dog', + ), + // output + array( + 'eye_of_newt' => 'value for -eye_of_newt-', + 'toe_of_frog' => 'value for -toe_of_frog-', + 'tongue_of_dog' => 'value for -tongue_of_dog-' + ), + ), + // 5th test case + array( + 'non-associative output', + // input + array( + 'scale_of_dragon' => null, + 'tooth_of_wolf' => null + ), + // output + array( + 'value for -scale_of_dragon-', + 'value for -tooth_of_wolf-' + ), + ) + ); + } + + protected function getCallback() { + return function( $key ) { + if ( $key === 'wool_of_bat' ) { + return ''; + } + return "value for -$key-"; + }; + } +} -- To view, visit https://gerrit.wikimedia.org/r/364339 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I9f0775720408a0eff685cd311295c33e327c4b01 Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/DonationInterface Gerrit-Branch: master Gerrit-Owner: Ejegg <ej...@ejegg.com> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits