* Additional general updates and improvements

* Fix php lint errors
This commit is contained in:
louwie17 2022-02-23 13:04:59 -04:00 committed by GitHub
parent ed2a1eaae2
commit e652b0b93f
14 changed files with 150 additions and 35 deletions

View File

@ -44,7 +44,7 @@ class MerchantEmailNotifications {
$note = Notes::get_note( $note_id );
if ( ! $note ) {
if ( ! $note || Note::E_WC_ADMIN_NOTE_EMAIL !== $note->get_type() ) {
return;
}

View File

@ -119,12 +119,17 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
*/
protected function add_order_by_params( $query_args, $from_arg, $id_cell ) {
global $wpdb;
// Sanitize input: guarantee that the id cell in the join is quoted with backticks.
$id_cell_segments = explode( '.', str_replace( '`', '', $id_cell ) );
$id_cell_identifier = '`' . implode( '`.`', $id_cell_segments ) . '`';
$lookup_table = self::get_db_table_name();
$order_by_clause = $this->add_order_by_clause( $query_args, $this );
$this->add_orderby_order_clause( $query_args, $this );
if ( false !== strpos( $order_by_clause, '_terms' ) ) {
$join = "JOIN {$wpdb->terms} AS _terms ON {$id_cell} = _terms.term_id";
$join = "JOIN {$wpdb->terms} AS _terms ON {$id_cell_identifier} = _terms.term_id";
if ( 'inner' === $from_arg ) {
// Even though this is an (inner) JOIN, we're adding it as a `left_join` to
// affect its order in the query statement. The SqlQuery::$sql_filters variable

View File

@ -115,9 +115,14 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
*/
protected function add_order_by_params( $query_args, $from_arg, $id_cell ) {
global $wpdb;
// Sanitize input: guarantee that the id cell in the join is quoted with backticks.
$id_cell_segments = explode( '.', str_replace( '`', '', $id_cell ) );
$id_cell_identifier = '`' . implode( '`.`', $id_cell_segments ) . '`';
$lookup_table = self::get_db_table_name();
$order_by_clause = $this->add_order_by_clause( $query_args, $this );
$join = "JOIN {$wpdb->posts} AS _coupons ON {$id_cell} = _coupons.ID";
$join = "JOIN {$wpdb->posts} AS _coupons ON {$id_cell_identifier} = _coupons.ID";
$this->add_orderby_order_clause( $query_args, $this );
if ( 'inner' === $from_arg ) {

View File

@ -1338,6 +1338,8 @@ class DataStore extends SqlQuery {
* @return string
*/
protected function get_filtered_ids( $query_args, $field, $separator = ',' ) {
global $wpdb;
$ids_str = '';
$ids = isset( $query_args[ $field ] ) && is_array( $query_args[ $field ] ) ? $query_args[ $field ] : array();
@ -1354,7 +1356,10 @@ class DataStore extends SqlQuery {
$ids = apply_filters( 'woocommerce_analytics_' . $field, $ids, $query_args, $field, $this->context );
if ( ! empty( $ids ) ) {
$ids_str = implode( $separator, $ids );
$placeholders = implode( $separator, array_fill( 0, count( $ids ), '%d' ) );
/* phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared */
$ids_str = $wpdb->prepare( "{$placeholders}", $ids );
/* phpcs:enable */
}
return $ids_str;
}

View File

@ -188,10 +188,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
* @return string
*/
protected function get_included_ip_addresses( $query_args ) {
if ( isset( $query_args['ip_address_includes'] ) && is_array( $query_args['ip_address_includes'] ) && count( $query_args['ip_address_includes'] ) > 0 ) {
$query_args['ip_address_includes'] = array_map( 'esc_sql', $query_args['ip_address_includes'] );
}
return self::get_filtered_ids( $query_args, 'ip_address_includes', "','" );
return $this->get_filtered_ip_addresses( $query_args, 'ip_address_includes' );
}
/**
@ -201,10 +198,35 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
* @return string
*/
protected function get_excluded_ip_addresses( $query_args ) {
if ( isset( $query_args['ip_address_excludes'] ) && is_array( $query_args['ip_address_excludes'] ) && count( $query_args['ip_address_excludes'] ) > 0 ) {
$query_args['ip_address_excludes'] = array_map( 'esc_sql', $query_args['ip_address_excludes'] );
return $this->get_filtered_ip_addresses( $query_args, 'ip_address_excludes' );
}
/**
* Returns filtered comma separated ids, based on query arguments from the user.
*
* @param array $query_args Parameters supplied by the user.
* @param string $field Query field to filter.
* @return string
*/
protected function get_filtered_ip_addresses( $query_args, $field ) {
if ( isset( $query_args[ $field ] ) && is_array( $query_args[ $field ] ) && count( $query_args[ $field ] ) > 0 ) {
$ip_addresses = array_map( 'esc_sql', $query_args[ $field ] );
/**
* Filter the IDs before retrieving report data.
*
* Allows filtering of the objects included or excluded from reports.
*
* @param array $ids List of object Ids.
* @param array $query_args The original arguments for the request.
* @param string $field The object type.
* @param string $context The data store context.
*/
$ip_addresses = apply_filters( 'woocommerce_analytics_' . $field, $ip_addresses, $query_args, $field, $this->context );
return implode( "','", $ip_addresses );
}
return self::get_filtered_ids( $query_args, 'ip_address_excludes', "','" );
return '';
}
/**
@ -260,7 +282,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
$this->clear_sql_clause( 'order_by' );
$order_by = '';
if ( isset( $query_args['orderby'] ) ) {
$order_by = $this->normalize_order_by( $query_args['orderby'] );
$order_by = $this->normalize_order_by( esc_sql( $query_args['orderby'] ) );
$this->add_sql_clause( 'order_by', $order_by );
}
@ -315,11 +337,13 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
$selections = $this->selected_columns( $query_args );
$this->add_sql_query_params( $query_args );
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$db_records_count = (int) $wpdb->get_var(
"SELECT COUNT(*) FROM (
{$this->subquery->get_query_statement()}
) AS tt"
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
);
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$params = $this->get_limit_params( $query_args );
$total_pages = (int) ceil( $db_records_count / $params['per_page'] );
@ -333,9 +357,9 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
$this->subquery->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
$download_data = $wpdb->get_results(
$this->subquery->get_query_statement(),
$this->subquery->get_query_statement(), // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
ARRAY_A
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
);
if ( null === $download_data ) {
return $data;

View File

@ -169,8 +169,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
$where_subquery[] = 'variation_lookup.order_id IS NULL';
}
$included_tax_rates = ! empty( $query_args['tax_rate_includes'] ) ? implode( ',', $query_args['tax_rate_includes'] ) : false;
$excluded_tax_rates = ! empty( $query_args['tax_rate_excludes'] ) ? implode( ',', $query_args['tax_rate_excludes'] ) : false;
$included_tax_rates = ! empty( $query_args['tax_rate_includes'] ) ? implode( ',', array_map( 'esc_sql', $query_args['tax_rate_includes'] ) ) : false;
$excluded_tax_rates = ! empty( $query_args['tax_rate_excludes'] ) ? implode( ',', array_map( 'esc_sql', $query_args['tax_rate_excludes'] ) ) : false;
if ( $included_tax_rates || $excluded_tax_rates ) {
$this->subquery->add_sql_clause( 'join', "LEFT JOIN {$order_tax_lookup_table} ON {$order_stats_lookup_table}.order_id = {$order_tax_lookup_table}.order_id" );
}

View File

@ -71,12 +71,16 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
* @param array $query_args Query arguments supplied by the user.
*/
protected function update_sql_query_params( $query_args ) {
global $wpdb;
$taxes_where_clause = '';
$order_tax_lookup_table = self::get_db_table_name();
if ( isset( $query_args['taxes'] ) && ! empty( $query_args['taxes'] ) ) {
$allowed_taxes = implode( ',', $query_args['taxes'] );
$taxes_where_clause .= " AND {$order_tax_lookup_table}.tax_rate_id IN ({$allowed_taxes})";
$tax_id_placeholders = implode( ',', array_fill( 0, count( $query_args['taxes'] ), '%d' ) );
/* phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared */
$taxes_where_clause .= $wpdb->prepare( " AND {$order_tax_lookup_table}.tax_rate_id IN ({$tax_id_placeholders})", $query_args['taxes'] );
/* phpcs:enable */
}
$order_status_filter = $this->get_status_subquery( $query_args );
@ -111,8 +115,10 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
FROM {$wpdb->prefix}woocommerce_tax_rates
";
if ( ! empty( $args['include'] ) ) {
$included_taxes = implode( ',', $args['include'] );
$query .= " WHERE tax_rate_id IN ({$included_taxes})";
/* phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared */
$tax_placeholders = implode( ',', array_fill( 0, count( $args['include'] ), '%d' ) );
$query .= $wpdb->prepare( " WHERE tax_rate_id IN ({$tax_placeholders})", $args['include'] );
/* phpcs:enable */
}
return $wpdb->get_results( $query, ARRAY_A ); // WPCS: cache ok, DB call ok, unprepared SQL ok.
}

View File

@ -55,6 +55,7 @@ class Segmenter extends ReportsSegmenter {
// Can't get all the numbers from one query, so split it into one query for product-level numbers and one for order-level numbers (which first need to have orders uniqued).
// Product-level numbers.
/* phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared */
$segments_products = $wpdb->get_results(
"SELECT
$segmenting_groupby AS $segmenting_dimension_name
@ -71,7 +72,8 @@ class Segmenter extends ReportsSegmenter {
GROUP BY
$segmenting_groupby",
ARRAY_A
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
);
/* phpcs:enable */
$totals_segments = $this->merge_segment_totals_results( $segmenting_dimension_name, $segments_products, array() );
return $totals_segments;
@ -105,6 +107,7 @@ class Segmenter extends ReportsSegmenter {
// Can't get all the numbers from one query, so split it into one query for product-level numbers and one for order-level numbers (which first need to have orders uniqued).
// Product-level numbers.
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$segments_products = $wpdb->get_results(
"SELECT
{$intervals_query['select_clause']} AS time_interval,
@ -122,8 +125,9 @@ class Segmenter extends ReportsSegmenter {
GROUP BY
time_interval, $segmenting_groupby
$segmenting_limit",
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
ARRAY_A
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
);
$intervals_segments = $this->merge_segment_intervals_results( $segmenting_dimension_name, $segments_products, array() );
return $intervals_segments;

View File

@ -40,7 +40,7 @@ class PaymentGatewaysController {
? $gateway->get_settings_url()
: admin_url( 'admin.php?page=wc-settings&tab=checkout&section=' . strtolower( $gateway->id ) );
$return_url = wc_admin_url( '&task=payments&connection-return=' . strtolower( $gateway->id ) );
$return_url = wc_admin_url( '&task=payments&connection-return=' . strtolower( $gateway->id ) . '&_wpnonce=' . wp_create_nonce( 'connection-return' ) );
$data['connection_url'] = method_exists( $gateway, 'get_connection_url' )
? $gateway->get_connection_url( $return_url )
: null;
@ -85,21 +85,20 @@ class PaymentGatewaysController {
* Call an action after a gating has been successfully returned.
*/
public static function possibly_do_connection_return_action() {
// phpcs:disable WordPress.Security.NonceVerification
if (
! isset( $_GET['page'] ) ||
'wc-admin' !== $_GET['page'] ||
! isset( $_GET['task'] ) ||
'payments' !== $_GET['task'] ||
! isset( $_GET['connection-return'] )
! isset( $_GET['connection-return'] ) ||
! isset( $_GET['_wpnonce'] ) ||
! wp_verify_nonce( wc_clean( wp_unslash( $_GET['_wpnonce'] ) ), 'connection-return' )
) {
return;
}
$gateway_id = sanitize_text_field( wp_unslash( $_GET['connection-return'] ) );
// phpcs:enable WordPress.Security.NonceVerification
do_action( 'woocommerce_admin_payment_gateway_connection_return', $gateway_id );
}

View File

@ -50,6 +50,7 @@ class DeactivatePlugin {
Note::E_WC_ADMIN_NOTE_UNACTIONED,
true
);
$note->add_nonce_to_action( 'deactivate-feature-plugin', 'deactivate-plugin_' . WC_ADMIN_PLUGIN_FILE, '' );
return $note;
}
@ -64,7 +65,6 @@ class DeactivatePlugin {
* Deactivate feature plugin.
*/
public function deactivate_feature_plugin() {
/* phpcs:disable WordPress.Security.NonceVerification */
if (
! isset( $_GET['page'] ) ||
'wc-admin' !== $_GET['page'] ||
@ -74,10 +74,35 @@ class DeactivatePlugin {
) {
return;
}
/* phpcs:enable */
$deactivate_url = admin_url( 'plugins.php?action=deactivate&plugin=' . rawurlencode( WC_ADMIN_PLUGIN_FILE ) . '&plugin_status=all&paged=1&_wpnonce=' . wp_create_nonce( 'deactivate-plugin_' . WC_ADMIN_PLUGIN_FILE ) );
$note = self::get_note();
$action = $note->get_action( 'deactivate-feature-plugin' );
// Preserve compatability with notes populated before nonce implementation.
if ( ! isset( $_GET['_wpnonce'] ) && ( ! $action || ! isset( $action->nonce_action ) ) ) {
self::deactivate_redirect( wp_create_nonce( 'deactivate-plugin_' . WC_ADMIN_PLUGIN_FILE ) );
return;
}
$nonce = isset( $_GET['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ) : '';
if ( ! wp_verify_nonce( $nonce, 'deactivate-plugin_' . WC_ADMIN_PLUGIN_FILE ) ) {
return;
}
self::deactivate_redirect( $nonce );
}
/**
* Deactivation redirect
*
* @param string $nonce The nonce.
*/
public static function deactivate_redirect( $nonce ) {
$deactivate_url = admin_url( 'plugins.php?action=deactivate&plugin=' . rawurlencode( WC_ADMIN_PLUGIN_FILE ) . '&plugin_status=all&paged=1&_wpnonce=' . $nonce );
wp_safe_redirect( $deactivate_url );
exit;
}
}

View File

@ -295,6 +295,25 @@ class Note extends \WC_Data {
return $this->get_prop( 'actions', $context );
}
/**
* Get action by action name on the note.
*
* @param string $action_name The action name.
* @param string $context What the value is for. Valid values are 'view' and 'edit'.
* @return array the action.
*/
public function get_action( $action_name, $context = 'view' ) {
$actions = $this->get_prop( 'actions', $context );
$matching_action = null;
foreach ( $actions as $i => $action ) {
if ( $action->name === $action_name ) {
$matching_action =& $actions[ $i ];
}
}
return $matching_action;
}
/**
* Get note layout (the old notes won't have one).
*

View File

@ -116,6 +116,7 @@ class WooCommercePayments {
$note->set_source( 'woocommerce-admin' );
$note->add_action( 'learn-more', __( 'Learn more', 'woocommerce-admin' ), 'https://woocommerce.com/payments/?utm_medium=product', Note::E_WC_ADMIN_NOTE_UNACTIONED );
$note->add_action( 'get-started', __( 'Get started', 'woocommerce-admin' ), wc_admin_url( '&action=setup-woocommerce-payments' ), Note::E_WC_ADMIN_NOTE_ACTIONED, true );
$note->add_nonce_to_action( 'get-started', 'setup-woocommerce-payments', '' );
// Create the note as "actioned" if the plugin is already installed.
if ( self::is_installed() ) {
@ -165,7 +166,6 @@ class WooCommercePayments {
*/
public function install_on_action() {
// TODO: Need to validate this request more strictly since we're taking install actions directly?
/* phpcs:disable WordPress.Security.NonceVerification */
if (
! isset( $_GET['page'] ) ||
'wc-admin' !== $_GET['page'] ||
@ -174,7 +174,19 @@ class WooCommercePayments {
) {
return;
}
/* phpcs:enable */
$note = self::get_note();
$action = $note->get_action( 'get-started' );
if ( ! $action ||
( isset( $action->nonce_action ) &&
(
empty( $_GET['_wpnonce'] ) ||
! wp_verify_nonce( wp_unslash( $_GET['_wpnonce'] ), $action->nonce_action ) // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
)
)
) {
return;
}
if ( ! current_user_can( 'install_plugins' ) ) {
return;

View File

@ -29,10 +29,21 @@ class PluginsInstaller {
*/
public static function possibly_install_activate_plugins() {
/* phpcs:disable WordPress.Security.NonceVerification.Recommended */
if ( ! isset( $_GET['plugin_action'] ) || ! isset( $_GET['plugins'] ) || ! current_user_can( 'install_plugins' ) ) {
if (
! isset( $_GET['plugin_action'] ) ||
! isset( $_GET['plugins'] ) ||
! current_user_can( 'install_plugins' ) ||
! isset( $_GET['nonce'] )
) {
return;
}
$nonce = sanitize_text_field( wp_unslash( $_GET['nonce'] ) );
if ( ! wp_verify_nonce( $nonce, 'install-plugin' ) ) {
wp_nonce_ays( 'install-plugin' );
}
$plugins = sanitize_text_field( wp_unslash( $_GET['plugins'] ) );
$plugin_action = sanitize_text_field( wp_unslash( $_GET['plugin_action'] ) );
/* phpcs:enable WordPress.Security.NonceVerification.Recommended */

View File

@ -116,7 +116,7 @@ class WC_Tests_PaymentGatewaySuggestions_PaymentGatewaysController extends WC_RE
public function test_connection_url() {
$response = $this->get_mock_gateway_response();
$this->assertEquals(
'http://testconnection.com?return=' . wc_admin_url( '&task=payments&connection-return=mock-enhanced' ),
'http://testconnection.com?return=' . wc_admin_url( '&task=payments&connection-return=mock-enhanced&_wpnonce=' . wp_create_nonce( 'connection-return' ) ),
$response['connection_url']
);
}