diff --git a/includes/regions-api.php b/includes/regions-api.php index 7b70be631..e926de9ef 100644 --- a/includes/regions-api.php +++ b/includes/regions-api.php @@ -47,15 +47,21 @@ public function find_by_parent_name($parent_name, $parent_type, $type) { return $this->db->get_col( $this->db->prepare( $sql, $parent_name ) ); } - public function save($region) { - if ( ! isset( $region['ad_id'] ) || empty( $region['ad_id'] ) ) { + public function save( $region ) { + $region = $this->filter_region_columns( stripslashes_deep( $region ) ); + + $region['ad_id'] = absint( isset( $region['ad_id'] ) ? $region['ad_id'] : 0 ); + + if ( empty( $region['ad_id'] ) ) { return false; } - $region = stripslashes_deep( $region ); + $region_id = intval( awpcp_array_data( 'id', 0, $region ) ); + + unset( $region['id'] ); - if ( intval( awpcp_array_data( 'id', null, $region ) ) > 0 ) { - $result = $this->db->update( AWPCP_TABLE_AD_REGIONS, $region, array( 'id' => $region['id'] ) ); + if ( $region_id > 0 ) { + $result = $this->db->update( AWPCP_TABLE_AD_REGIONS, $region, array( 'id' => $region_id ) ); } else { $result = $this->db->insert( AWPCP_TABLE_AD_REGIONS, $region ); } @@ -63,6 +69,47 @@ public function save($region) { return $result !== false; } + /** + * Allowlist region array keys to valid DB columns only. + * + * @since x.x + * + * @param mixed $region Region data. + * + * @return array + */ + private function filter_region_columns( $region ) { + if ( ! is_array( $region ) ) { + return array(); + } + + $allowed = array( 'id', 'ad_id', 'country', 'county', 'state', 'city', 'region_id' ); + + return array_intersect_key( $region, array_flip( $allowed ) ); + } + + /** + * Sanitise a user-submitted region to editable fields only. + * + * @since x.x + * + * @param mixed $region Region data. + * + * @return array + */ + private function prepare_submitted_region( $region ) { + if ( ! is_array( $region ) ) { + return array(); + } + + $allowed = array( 'country', 'county', 'state', 'city', 'region_id' ); + $region = array_intersect_key( $region, array_flip( $allowed ) ); + + $region = array_filter( $region, 'is_scalar' ); + + return array_map( 'trim', $region ); + } + public function delete_by_ad_id($ad_id) { $result = $this->db->query( $this->db->prepare( "DELETE FROM " . AWPCP_TABLE_AD_REGIONS . " WHERE ad_id = %s", $ad_id ) ); return $result !== false; @@ -79,18 +126,21 @@ public function find_by_ad_id($ad_id) { } public function update_ad_regions( $ad, $regions, $max_regions = 1 ) { - // remove existing regions before adding the new ones $this->delete_by_ad_id( $ad->ID ); $count = 0; - foreach ($regions as $region) { - if ( empty( implode( $region ) ) ) { + + foreach ( $regions as $region ) { + $data = $this->prepare_submitted_region( $region ); + + if ( empty( implode( $data ) ) ) { continue; } - if ($count < $max_regions) { - $data = array_map( 'trim', $region ); - $this->save( array_merge( array( 'ad_id' => $ad->ID ), $data ) ); + + if ( $count < $max_regions ) { + $this->save( array_merge( $data, array( 'ad_id' => $ad->ID ) ) ); } + ++$count; } } diff --git a/tests/suite/Payments/Gateways/test-PayPalStandard.php b/tests/suite/Payments/Gateways/test-PayPalStandard.php index eb6bdcfe6..39fd50ec0 100644 --- a/tests/suite/Payments/Gateways/test-PayPalStandard.php +++ b/tests/suite/Payments/Gateways/test-PayPalStandard.php @@ -117,6 +117,7 @@ public function test_verified_payment_with_mismatched_transaction_id_is_invalid( */ private function create_transaction() { Functions\when( 'awpcp_current_user_is_admin' )->justReturn( false ); + Functions\when( 'maybe_unserialize' )->returnArg(); $transaction = new AWPCP_Payment_Transaction( array( 'id' => 'transaction-id' ) ); $transaction->add_item( 1, 'Listing', 'Listing fee', AWPCP_Payment_Transaction::PAYMENT_TYPE_MONEY, 9.99 ); diff --git a/tests/suite/Payments/test-payment-transaction.php b/tests/suite/Payments/test-payment-transaction.php index 44b81d9f4..fb0457b17 100644 --- a/tests/suite/Payments/test-payment-transaction.php +++ b/tests/suite/Payments/test-payment-transaction.php @@ -45,8 +45,19 @@ public function test_pending_payment_can_complete_transaction() { * @return AWPCP_Payment_Transaction */ private function create_transaction() { + $payments = Mockery::mock( 'AWPCP_PaymentsAPI' ); + $payments->shouldReceive( 'get_transaction_payment_method' ) + ->andReturn( (object) array( 'slug' => 'paypal-standard' ) ); + Functions\when( 'awpcp_current_user_is_admin' )->justReturn( false ); + Functions\when( 'awpcp_user_is_admin' )->justReturn( false ); + Functions\when( 'maybe_unserialize' )->returnArg(); + Functions\when( 'get_awpcp_option' )->justReturn( false ); + Functions\when( 'awpcp_payments_api' )->justReturn( $payments ); + + $transaction = new AWPCP_Payment_Transaction( array( 'id' => 'transaction-id' ) ); + $transaction->add_item( 1, 'Listing', 'Listing fee', AWPCP_Payment_Transaction::PAYMENT_TYPE_MONEY, 9.99 ); - return new AWPCP_Payment_Transaction( array( 'id' => 'transaction-id' ) ); + return $transaction; } }