jenkins-bot has submitted this change and it was merged.

Change subject: Lots of validation cleanup
......................................................................


Lots of validation cleanup

Simplify the instructions->results loop by reducing the special-casing.

Bug: T86945
Change-Id: If1b4bcd1cbb513f13be698a1b3f7a0852ac9ed00
---
M gateway_common/DataValidator.php
M tests/GatewayValidationTest.php
2 files changed, 110 insertions(+), 266 deletions(-)

Approvals:
  Ejegg: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/gateway_common/DataValidator.php b/gateway_common/DataValidator.php
index 281955a..13f08c6 100644
--- a/gateway_common/DataValidator.php
+++ b/gateway_common/DataValidator.php
@@ -12,66 +12,6 @@
  * @author awight
  */
 class DataValidator {
-       
-       /**
-        * $boolean_fields
-        * @var array All fields that should validate as boolean values
-        */
-       protected static $boolean_fields = array(
-               '_cache_',
-               'anonymous',
-               'optout',
-               'recurring',
-               'posted',
-       ); 
-       
-       /**
-        * $numeric_fields
-        * @var array All fields that should validate as numeric
-        */
-       protected static $numeric_fields = array(
-               'amount',
-               'amountGiven',
-               'amountOther',
-               'cvv',
-               'contribution_tracking_id',
-               'account_number',
-               'expiration',
-               'order_id',
-               'numAttempt'
-       );
-
-       /**
-        * $card_types
-        * @var array A list of SOME card types we recognize
-        */
-       protected static $card_types = array( 
-               'amex',
-               'mc',
-               'visa',
-               'discover'
-       );
-       
-       /**
-        * getNumericFields returns a list of DonationInterface fields that are 
-        * expected to contain numeric values. 
-        * @return array A non-ordered array of field names. 
-        */
-       public static function getNumericFields(){
-               return self::$numeric_fields;
-       }
-       
-       
-       /**
-        * getBooleanFields returns a list of DonationInterface fields that are 
-        * expected to contain boolean values. 
-        * @return array A non-ordered array of field names. 
-        */
-       public static function getBooleanFields(){
-               return self::$boolean_fields;
-       }
-       
-       
        /**
         * getErrorToken, intended to be used by classes that exist relatively 
close 
         * to the form classes, returns the error token (defined on the forms) 
that 
@@ -82,7 +22,6 @@
         * RapidHTML. 
         */
        public static function getErrorToken( $field ){
-               $error_token = 'general';
                switch ( $field ) {
                        case 'amountGiven' :
                        case 'amountOther' :
@@ -104,6 +43,9 @@
                        case 'state':
                        case 'zip':
                                $error_token = $field;
+                               break;
+                       default:
+                               $error_token = 'general';
                                break;
                }
                return $error_token;
@@ -143,7 +85,7 @@
         * that is causing the error.
         * @param string $type - The type of error being caused, from a set.
         *    Possible values are:
-        *        'non_empty' - the value is required and not currently present
+        *        'not_empty' - the value is required and not currently present
         *        'valid_type' - in general, the wrong format
         *        'calculated' - fields that failed some kind of multiple-field 
data
         * integrity check.
@@ -172,7 +114,7 @@
                //Empty messages should get: 
                //'donate_interface-error-msg' => 'Please enter your $1';
                //If they have no defined error message, give 'em the default. 
-               if ($type === 'non_empty'){
+               if ($type === 'not_empty'){
                        if ( $message_field != 'general' && 
self::wmfMessageExists( $error_message_field_string, $language ) ) {
                                return WmfFramework::formatMessage(
                                        'donate_interface-error-msg',
@@ -190,9 +132,6 @@
                        switch ($token){
                                case 'amount': 
                                        $suffix = 'invalid-amount';
-                                       break;
-                               case 'emailAdd': 
-                                       $suffix = 'email';
                                        break;
                                case 'card_num': //god damn it.
                                        $suffix = 'card_num'; //more 
defaultness.
@@ -236,6 +175,7 @@
         * wmfMessageExists returns true if a translatable message has been 
defined 
         * for the string and language that have been passed in, false if none 
is 
         * present. 
+        * TODO: move
         * @param string $msg_key The message string to look up.
         * @param string $language A valid mediawiki language code.
         * @return boolean - true if message exists, otherwise false.
@@ -331,149 +271,116 @@
                 * look at it to make sure it's complete, and in order...
                 * ...and do it. 
                 */
-               
-               $instructions = array(
-                       'non_empty' => array(),
-                       'valid_type' => array(), //simple 'valid_type' check 
functions only have one parameter.
-                       'calculated' => array(), //'calculated' check functions 
depend on (or optionally have) more than one value.
+
+               // Define all default validations.
+               $validations = array(
+                       'not_empty' => array(
+                               'amount',
+                               'country',
+                               'currency_code',
+                               'gateway',
+                       ),
+                       'valid_type' => array(
+                               '_cache_' => 'validate_boolean',
+                               'account_number' => 'validate_numeric',
+                               'amount' => 'validate_numeric',
+                               'amountGiven' => 'validate_numeric',
+                               'amountOther' => 'validate_numeric',
+                               'anonymous' => 'validate_boolean',
+                               'contribution_tracking_id' => 
'validate_numeric',
+                               'currency_code' => 'validate_alphanumeric',
+                               'gateway' => 'validate_alphanumeric',
+                               'numAttempt' => 'validate_numeric',
+                               'optout' => 'validate_boolean',
+                               'order_id' => 'validate_numeric',
+                               'posted' => 'validate_boolean',
+                               'recurring' => 'validate_boolean',
+                       ),
+                       // Note that order matters for this group, dependencies 
must come first.
+                       'calculated' => array(
+                               'address' => 'validate_address',
+                               'city' => 'validate_address',
+                               'country' => 'validate_country_allowed',
+                               'email' => 'validate_email',
+                               'street' => 'validate_address',
+                               'currency_code' => 'validate_currency_code',
+                               'gateway' => 'validate_gateway',
+                               'fname' => 'validate_name',
+                               'lname' => 'validate_name',
+                               'name' => 'validate_name',
+
+                               // Depends on currency_code and gateway.
+                               'amount' => 'validate_amount',
+                       ),
                );
-               
-               if ( !is_array( $check_not_empty ) ){
-                       $check_not_empty = array( $check_not_empty );
+
+               // Additional fields we should check for emptiness.
+               if ( $check_not_empty ) {
+                       $validations['not_empty'] = array_unique( array_merge(
+                               $check_not_empty, $validations['not_empty']
+                       ) );
                }
-               
-               foreach ( $check_not_empty as $field ){ 
-                       $instructions['non_empty'][$field] = 
'validate_not_empty';
-               }               
-               
-               foreach ( $data as $field => $value ){
-                       //first, unset everything that's an empty string, or 
null, as there's nothing to validate. 
-                       if ( $value !== '' && !is_null( $value ) ){
-                       
-                               $function_name = self::getValidationFunction( 
$field );
-                               $check_type = 'valid_type';
-                               switch ( $function_name ) {
-                                       case 'validate_amount':
-                                               //Note: We could do something 
like also validate amount not empty, and then that it's numeric
-                                               //That way we'd get more 
precisely granular error messages. 
-                                               $check_type = 'calculated';
-                                               
$instructions['non_empty']['amount'] = 'validate_non_zero';
-                                               
$instructions['valid_type']['amount'] = 'validate_numeric';
-                                               
$instructions['non_empty']['currency_code'] = 'validate_not_empty';
-                                               
$instructions['valid_type']['currency_code'] = 'validate_alphanumeric';
-                                               
$instructions['non_empty']['gateway'] = 'validate_not_empty';
-                                               
$instructions['valid_type']['gateway'] = self::getValidationFunction( 'gateway' 
);
-                                               break;
-                                       case 'validate_currency_code':
-                                               $check_type = 'calculated';
-                                               break;
-                                       case 'validate_card_type':
-                                               $check_type = 'calculated';
-                                               break;
-                                       case 'validate_country_allowed':
-                                               $check_type = 'calculated';
-                                               
$instructions['non_empty']['country'] = 'validate_not_empty';
-                                               //@TODO: generic country 
validate here. I'm not too
-                                               //worried, as the purpose of 
this check is to blacklist
-                                               //specific values... but we 
should eventually be
-                                               //checking with the gateway to 
see if this country and
-                                               //payment type is valid.
-                                               //Or maybe that's more payment 
type validation territory...
-                                               //@TODO: Insert More Think Here
-                                               break;
-                                       case 'validate_name':
-                                               $check_type = 'calculated';
-                                               break;
-                                       case 'validate_address':
-                                               $check_type = 'calculated';
-                                               break;
-                               }
-                               $instructions[$check_type][$field] = 
$function_name;
-                       }
-               }
-               
+
                $errors = array();
-               
-               $self = get_called_class();
-               $language = self::getLanguage( $data );
-               
-               foreach ( $instructions['non_empty'] as $field => $function ){
-                       if ( method_exists( $self, $function ) ) {
-                               if ( $self::$function( $field, $data ) ){
-                                       $results['non_empty'][$field] = true;
+
+               $language = DataValidator::guessLanguage( $data );
+
+               foreach ( $validations as $phase => $fields ) {
+                       foreach ( $fields as $key => $custom ) {
+                               // Here we decode list vs map elements.
+                               if ( is_numeric( $key ) ) {
+                                       $field = $custom;
+                                       $validation_function = 
"validate_{$phase}";
                                } else {
-                                       $results['non_empty'][$field] = false;
-                                       $errors[ self::getErrorToken( $field ) 
] = self::getErrorMessage( $field, 'non_empty', $language );
+                                       $field = $key;
+                                       $validation_function = $custom;
                                }
-                       } else {
-                               $errors[ self::getErrorToken( $field ) ] = 
self::getErrorMessage( $field, 'non_empty', $language );
-                               throw new MWException( __FUNCTION__ . " BAD 
PROGRAMMER. No $function function. ('non_empty' rule for $field )" );
-                       }
-               }
-               
-               foreach ( $instructions['valid_type'] as $field => $function ){
-                       $errorToken = self::getErrorToken( $field );
-                       if ( array_key_exists( $errorToken, $errors ) ) {
-                               continue;
-                       }
-                       if ( method_exists( $self, $function ) ) {
-                               if ( $self::$function( $data[$field] ) ){
-                                       $results['valid_type'][$field] = true;
-                               } else {
-                                       $results['valid_type'][$field] = false;
-                                       $errors[ $errorToken ] = 
self::getErrorMessage( $field, 'valid_type', $language );
+
+                               if ( !isset( $data[$field] ) ) {
+                                       if ( $phase !== 'not_empty' ) {
+                                               // Skip if not required and 
nothing to validate.
+                                               continue;
+                                       } else {
+                                               // Stuff with nothing.
+                                               $data[$field] = null;
+                                       }
                                }
-                       } else {
-                               $errors[ $errorToken ] = self::getErrorMessage( 
$field, 'valid_type', $language );
-                               throw new MWException( __FUNCTION__ . " BAD 
PROGRAMMER. No $function function. ('valid_type' rule for $field)" );
-                       }
-               }
-               
-               //don't bail out now. Just don't set errors for calculated 
fields that 
-               //have failures in their dependencies. 
-               foreach ( $instructions['calculated'] as $field => $function ){
-                       $errorToken = self::getErrorToken( $field );
-                       if ( array_key_exists( $errorToken, $errors ) ) {
-                               continue;
-                       }
-                       if ( method_exists( $self, $function ) ) {
-                               //each of these is going to have its own set of 
overly 
-                               //complicated rules and things to check, or we 
wouldn't be down 
-                               //here in the calculated section. 
+
+                               // Skip if we've already determined this field 
group is invalid.
+                               $errorToken = self::getErrorToken( $field );
+                               if ( array_key_exists( $errorToken, $errors ) ) 
{
+                                       continue;
+                               }
+
+                               // Prepare to call the thing.
+                               $callable = array( 'DataValidator', 
$validation_function );
+                               if ( !is_callable( $callable ) ) {
+                                       throw new Exception( __FUNCTION__ . " 
BAD PROGRAMMER. No function {$validation_function} for $field" );
+                               }
                                $result = null;
-                               switch ( $function ){
+                               // Handle special cases.
+                               switch ( $validation_function ) {
                                        case 'validate_amount':
-                                               if ( 
self::checkValidationPassed( array( 'currency_code', 'gateway' ), 
$instructions, $results ) ){
+                                               if ( 
self::checkValidationPassed( array( 'currency_code', 'gateway' ), $results ) ){
                                                        $priceFloor = 
$gateway->getGlobal( 'PriceFloor' );
                                                        $priceCeiling = 
$gateway->getGlobal( 'PriceCeiling' );
-                                                       $result = 
$self::$function( $data[$field], $data['currency_code'], $priceFloor, 
$priceCeiling );
+                                                       $result = 
call_user_func( $callable, $data[$field], $data['currency_code'], $priceFloor, 
$priceCeiling );
                                                } //otherwise, just don't do 
the validation. The other stuff will be complaining already. 
                                                break;
                                        case 'validate_currency_code':
-                                               $result = $self::$function( 
$data[$field], $gateway->getCurrencies() );
-                                               break;
-                                       case 'validate_card_type':
-                                               //the contingent field in this 
case isn't strictly required, so this is going to look funny. 
-                                               if ( array_key_exists( 
'card_number', $instructions['valid_type'] ) && 
$instructions['valid_type']['card_number'] === true ){
-                                                       //if it's there, it had 
better match up.
-                                                       $result = 
$self::$function( $data[$field], $data['card_number'] );
-                                               } else {
-                                                       $result = 
$self::$function( $data[$field] );
-                                               }
+                                               $result = call_user_func( 
$callable, $data[$field], $gateway->getCurrencies() );
                                                break;
                                        default:
-                                               $result = $self::$function( 
$data[$field] );
+                                               $result = call_user_func( 
$callable, $data[$field] );
+                                               break;
                                }
-                               
-                               $results['calculated'][$field] = $result;
-                               if ($result === false){
+
+                               // Store results.
+                               $results[$phase][$field] = $result;
+                               if ( $result === false ) {
                                        // We did the check, and it failed.
-                                       $errors[ $errorToken ] = 
self::getErrorMessage( $field, 'calculated', $language, $data[$field] );
+                                       $errors[$errorToken] = 
self::getErrorMessage( $field, $phase, $language );
                                }
-                               
-                       } else {
-                               $errors[ $errorToken ] = self::getErrorMessage( 
$field, 'calculated', $language, $data[$field] );
-                               throw new MWException( __FUNCTION__ . " BAD 
PROGRAMMER. No $function function. ('calculated' rule for $field)" );
                        }
                }
 
@@ -488,17 +395,15 @@
         * all fields required to validate the original have, themselves, 
passed 
         * validation. 
         * @param array $fields An array of field names to check.
-        * @param array $instructions The validation instructions.
         * @param array $results Intermediate result of validation.
         * @return boolean true if all fields specified in $fields passed their 
-        * non_empty and valid_type validation. Otherwise, false.
+        * not_empty and valid_type validation. Otherwise, false.
         */
-       protected static function checkValidationPassed( $fields, 
$instructions, $results ){
+       protected static function checkValidationPassed( $fields, $results ){
                foreach ( $fields as $field ){
-                       foreach ( $instructions as $validation_type => 
$instruction_fields ) {
-                               if ( array_key_exists( $field, 
$instruction_fields )
-                                       && array_key_exists( $validation_type, 
$results )
-                                       && $results[$validation_type][$field] 
!== true
+                       foreach ( $results as $phase => $results_fields ) {
+                               if ( array_key_exists( $field, $results_fields )
+                                       && $results_fields[$field] !== true
                                ) {
                                        return false;
                                }
@@ -506,48 +411,7 @@
                }
                return true;
        }
-       
-       
-       /**
-        * getValidationFunction returns the function to use to validate the 
given field. 
-        * @param string $field The name of the field we need to validate. 
-        */
-       static function getValidationFunction( $field ){
-               switch ( $field ){
-                       case 'email':
-                               return 'validate_email';
-                       case 'amount': //we only have to do the one: It will 
have been normalized by now. 
-                               return 'validate_amount'; //this one is 
interesting. Needs two params. 
-                       case 'card_num':
-                               return 'validate_credit_card';
-                       case 'card_type':
-                               return 'validate_card_type';
-                       case 'gateway':
-                               return 'validate_gateway';
-                       case 'country':
-                               return 'validate_country_allowed';
-                       case 'fname':
-                       case 'lname':
-                               return 'validate_name';
-                       case 'currency_code':
-                               return 'validate_currency_code';
-                       case 'city':
-                       case 'street':
-                               return 'validate_address';
-               }
 
-               if ( in_array( $field, self::getNumericFields() ) ){
-                       return 'validate_numeric';
-               }
-               
-               if ( in_array( $field, self::getBooleanFields() ) ){
-                       return 'validate_boolean';
-               }
-               
-               return 'validate_alphanumeric'; //Yeah, this won't work.  
-       }
-       
-       
        /**
         * validate_email
         * Determines if the $value passed in is a valid email address. 
@@ -694,25 +558,8 @@
         * @param array $data The whole data set. 
         * @return boolean True if the $value is not missing or empty, 
otherwise false.
         */
-       protected static function validate_not_empty( $value, $data ){
-               if ( !array_key_exists( $value, $data ) || is_null( 
$data[$value] ) || $data[$value] === '' ){
-                       return false;
-               }
-               return true;
-       }
-       
-       /**
-        * Checks to make sure that the $value is present in the $data array, 
and not equivalent to zero.
-        * @param string $value The value to check for non-zeroness.
-        * @param array $data The whole data set.
-        * @return boolean True if the $value is not missing or zero, otherwise 
false.
-        */
-       protected static function validate_non_zero( $value, $data ){
-               if ( !array_key_exists( $value, $data ) || is_null( 
$data[$value] ) || $data[$value] === '' 
-                       || preg_match('/^0+(\.0+)?$/', $data[$value])){
-                       return false;
-               }
-               return true;
+       protected static function validate_not_empty( $value ){
+               return ( $value !== null && $value !== '' );
        }
 
        /**
@@ -756,9 +603,9 @@
                global $wgDonationInterfaceForbiddenCountries;
                if ( in_array( strtoupper($value), 
$wgDonationInterfaceForbiddenCountries ) ){
                        return false;
-               } else {
-                       return true;
                }
+               // TODO: return DataValidator::is_valid_iso_country_code( 
$value );
+               return true;
        }
 
        /**
@@ -921,16 +768,15 @@
        }
        
        /**
-        * getLanguage
-        * Returns a valid mediawiki language code to use for all the 
-        * DonationInterface translations.
+        * Returns a valid mediawiki language code to use for all the 
DonationInterface translations.
+        *
         * Will only look at the currently configured language if the 
'language' key 
         * doesn't exist in the data set: Users may not have a language 
preference 
         * set if we're bouncing between mediawiki instances for payments.
         * @param array $data A normalized DonationInterface data set. 
         * @return string A valid mediawiki language code. 
         */
-       public static function getLanguage( $data ) {
+       public static function guessLanguage( $data ) {
                if ( array_key_exists( 'language', $data )
                        && WmfFramework::isValidBuiltInLanguageCode( 
$data['language'] ) ) {
                        return $data['language'];
diff --git a/tests/GatewayValidationTest.php b/tests/GatewayValidationTest.php
index 2d3580e..77315a4 100644
--- a/tests/GatewayValidationTest.php
+++ b/tests/GatewayValidationTest.php
@@ -87,8 +87,6 @@
        }
 
        public function testCurrencyCodeError() {
-               $this->markTestSkipped( 'Currency validation is currently 
untestable, because order cannot be controlled.' );
-
                $this->adapter->addData( array(
                        'amount' => '2.99',
                        'country' => 'US',

-- 
To view, visit https://gerrit.wikimedia.org/r/185082
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: If1b4bcd1cbb513f13be698a1b3f7a0852ac9ed00
Gerrit-PatchSet: 14
Gerrit-Project: mediawiki/extensions/DonationInterface
Gerrit-Branch: master
Gerrit-Owner: Awight <[email protected]>
Gerrit-Reviewer: AndyRussG <[email protected]>
Gerrit-Reviewer: Awight <[email protected]>
Gerrit-Reviewer: Ejegg <[email protected]>
Gerrit-Reviewer: Katie Horn <[email protected]>
Gerrit-Reviewer: Ssmith <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to