Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/groundhogg/db/db.php
+++ b/groundhogg/db/db.php
@@ -1107,7 +1107,7 @@
$where[] = [
'col' => $this->get_primary_key(),
'compare' => 'IN',
- 'val' => $wpdb->prepare( "SELECT primary_object_id FROM {$relationships->table_name} WHERE secondary_object_id = %d AND secondary_object_type = '%s' AND primary_object_type = '%s'", $val['ID'], $val['type'], $this->get_object_type() )
+ 'val' => $wpdb->prepare( "SELECT primary_object_id FROM %i WHERE secondary_object_id = %d AND secondary_object_type = %s AND primary_object_type = %s", $relationships->table_name, $val['ID'], $val['type'], $this->get_object_type() )
];
break;
@@ -1124,7 +1124,7 @@
$where[] = [
'col' => $this->get_primary_key(),
'compare' => 'IN',
- 'val' => $wpdb->prepare( "SELECT secondary_object_id FROM {$relationships->table_name} WHERE primary_object_id = %d AND primary_object_type = '%s' AND secondary_object_type = '%s'", $val['ID'], $val['type'], $this->get_object_type() )
+ 'val' => $wpdb->prepare( "SELECT secondary_object_id FROM %i WHERE primary_object_id = %d AND primary_object_type = %s AND secondary_object_type = %s", $relationships->table_name, $val['ID'], $val['type'], $this->get_object_type() )
];
break;
case 'count':
@@ -1177,7 +1177,7 @@
// Select Clause
if ( is_string( $val ) && strpos( $val, 'SELECT' ) !== false ) {
- $where[] = [ 'col' => $key, 'val' => $val, 'compare' => 'IN' ];
+ _doing_it_wrong( __METHOD__, 'Use the GroundhoggQuery class for sub queries', '4.5.1' );
break;
}
@@ -1561,7 +1561,7 @@
$this->parse_filters( $val, $exclude_query->where() );
if ( ! $exclude_query->where->isEmpty() ) {
- $query->where()->notIn( $this->get_primary_key(), "$exclude_query" );
+ $query->where()->notIn( $this->get_primary_key(), $exclude_query );
}
break;
@@ -1609,7 +1609,7 @@
// Select Clause
if ( is_string( $val ) && strpos( $val, 'SELECT' ) !== false ) {
- $query->whereIn( $key, $val );
+ _doing_it_wrong( __METHOD__, 'Use the GroundhoggQuery class for sub queries', '4.5.1' );
break;
}
--- a/groundhogg/db/query/table-query.php
+++ b/groundhogg/db/query/table-query.php
@@ -100,7 +100,7 @@
$exclude_query->parseFilters( $value );
if ( ! $exclude_query->where->isEmpty() ) {
- $this->where()->notIn( $this->db_table->get_primary_key(), "$exclude_query" );
+ $this->where()->notIn( $this->db_table->get_primary_key(), $exclude_query );
}
break;
--- a/groundhogg/db/query/where.php
+++ b/groundhogg/db/query/where.php
@@ -218,7 +218,7 @@
$column = substr( $column, strpos( $column, '.' ) + 1 );
}
- return get_array_var( $column_formats, $column, is_numeric( $value ) ? '%d' : '%s' );
+ return get_array_var( $column_formats, $column, self::guessPlaceholderFormat( $value ) );
}
return self::guessPlaceholderFormat( $value );
@@ -438,24 +438,28 @@
*/
public function in( $column, $values ) {
+ if ( is_string( $values ) && str_starts_with( $values, 'SELECT' ) ) {
+ _doing_it_wrong( __METHOD__, 'Use the GroundhoggQuery class for sub queries', '4.5' );
+ return $this;
+ }
+
$column = $this->sanitize_column( $column );
- if ( ( is_string( $values ) && str_starts_with( $values, 'SELECT' ) ) || is_a( $values, Query::class ) ) {
+ if ( is_a( $values, Query::class ) ) {
$this->addCondition( "$column IN ( $values )" );
return $this;
}
$values = array_values( ensure_array( $values ) );
- $values = map_deep( $values, 'sanitize_text_field' );
if ( count( $values ) === 1 ) {
return $this->equals( $column, $values[0] );
}
- $values = maybe_implode_in_quotes( $values );
+ $placeholders = implode( ',', array_map( fn( $value ) => $this->getColumnFormat( $column, $value ), $values ) );
- return $this->addCondition( "$column IN ( $values )" );
+ return $this->addCondition( $this->prepare( "$column IN ( $placeholders )", $values ) );
}
/**
@@ -468,24 +472,28 @@
*/
public function notIn( $column, $values ) {
+ if ( is_string( $values ) && str_starts_with( $values, 'SELECT' ) ) {
+ _doing_it_wrong( __METHOD__, 'Use the GroundhoggQuery class for sub queries', '4.5' );
+ return $this;
+ }
+
$column = $this->sanitize_column( $column );
- if ( is_string( $values ) && str_starts_with( $values, 'SELECT' ) ) {
+ if ( is_a( $values, Query::class ) ) {
$this->addCondition( "$column NOT IN ( $values )" );
return $this;
}
$values = array_values( ensure_array( $values ) );
- $values = map_deep( $values, 'sanitize_text_field' );
if ( count( $values ) === 1 ) {
return $this->notEquals( $column, $values[0] );
}
- $values = maybe_implode_in_quotes( $values );
+ $placeholders = implode( ',', array_map( fn( $value ) => $this->getColumnFormat( $column, $value ), $values ) );
- return $this->addCondition( "$column NOT IN ( $values )" );
+ return $this->addCondition( $this->prepare( "$column NOT IN ( $placeholders )", $values ) );
}
/**
--- a/groundhogg/groundhogg.php
+++ b/groundhogg/groundhogg.php
@@ -3,7 +3,7 @@
* Plugin Name: Groundhogg
* Plugin URI: https://www.groundhogg.io/?utm_source=wp-plugins&utm_campaign=plugin-uri&utm_medium=wp-dash
* Description: CRM and marketing automation for WordPress
- * Version: 4.5
+ * Version: 4.5.1
* Author: Groundhogg Inc.
* Author URI: https://www.groundhogg.io/?utm_source=wp-plugins&utm_campaign=author-uri&utm_medium=wp-dash
* Text Domain: groundhogg
@@ -24,8 +24,8 @@
if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
-define( 'GROUNDHOGG_VERSION', '4.5' );
-define( 'GROUNDHOGG_PREVIOUS_STABLE_VERSION', '4.4.2' );
+define( 'GROUNDHOGG_VERSION', '4.5.1' );
+define( 'GROUNDHOGG_PREVIOUS_STABLE_VERSION', '4.5' );
define( 'GROUNDHOGG__FILE__', __FILE__ );
define( 'GROUNDHOGG_PLUGIN_BASE', plugin_basename( GROUNDHOGG__FILE__ ) );
--- a/groundhogg/includes/contact-query.php
+++ b/groundhogg/includes/contact-query.php
@@ -608,6 +608,7 @@
] );
$countries = maybe_explode( $filter['country'] );
+ $countries = array_intersect( $countries, array_keys( utils()->location->get_countries_list() ) );
if ( empty( $countries ) ) {
return;
@@ -687,7 +688,9 @@
$col = "COALESCE($alias.meta_value,'$default')";
$query->add_safe_column( $col );
- $where->in( $col, $filter['locales'] );
+ $locales = array_intersect( maybe_explode( $filter['locales'] ), array_keys( wp_get_available_translations() ) );
+
+ $where->in( $col, $locales );
}
/**
@@ -764,7 +767,7 @@
$subQuery->set_query_var( 'select', 'ID' );
$subQuery->setSelect( 'ID' );
- $where->notIn( 'ID', $subQuery->get_sql() );
+ $where->notIn( 'ID', $subQuery );
} else {
self::set_where_conditions( $search['query'], $where );
}
@@ -1925,21 +1928,13 @@
case 'optin_status': // Include by opt-in status
if ( ! empty( $value ) ) {
$optin_stati = wp_parse_id_list( $value );
- if ( count( $optin_stati ) === 1 ) {
- $where->equals( 'optin_status', $optin_stati[0] );
- } else {
- $where->in( 'optin_status', $optin_stati );
- }
+ $where->in( 'optin_status', $optin_stati );
}
break;
case 'optin_status_exclude': // Exclude by opt-in status
if ( ! empty( $value ) ) {
$optin_stati = wp_parse_id_list( $value );
- if ( count( $optin_stati ) === 1 ) {
- $where->notEquals( 'optin_status', $optin_stati[0] );
- } else {
- $where->notIn( 'optin_status', $optin_stati );
- }
+ $where->notIn( 'optin_status', $optin_stati );
}
break;
case 'before': // Date before
@@ -1959,11 +1954,7 @@
case 'owner': // filter by owner
if ( ! empty( $value ) ) {
$owner_ids = wp_parse_id_list( $value );
- if ( count( $owner_ids ) === 1 ) {
- $where->equals( 'owner_id', $owner_ids[0] );
- } else {
- $where->in( 'owner_id', $owner_ids );
- }
+ $where->in( 'owner_id', $owner_ids );
}
break;
case 'email': // Email search
@@ -2206,7 +2197,7 @@
$exclude_query->maybe_setup_query();
if ( ! $exclude_query->where->isEmpty() ) {
- $where->notIn( 'ID', "$exclude_query" );
+ $where->notIn( 'ID', $exclude_query );
}
}
@@ -2386,23 +2377,21 @@
$tags = wp_parse_id_list( $tags );
if ( count( $tags ) === 1 ) {
- $where->notIn( 'ID', get_db( 'tag_relationships' )->get_sql( [
- 'select' => 'contact_id',
- 'tag_id' => $tags[0],
- 'orderby' => false,
- 'order' => false,
- ] ) );
+
+ $tagQuery = new Table_Query( 'tag_relationships' );
+ $tagQuery->setSelect( 'contact_id' )->where->equals( 'tag_id', $tags[0] );
+
+ $where->notIn( 'ID', $tagQuery );
return;
}
if ( $all ) {
- $where->notIn( 'ID', get_db( 'tag_relationships' )->get_sql( [
- 'select' => 'contact_id',
- 'tag_id' => $tags,
- 'orderby' => false,
- 'order' => false,
- ] ) );
+
+ $tagQuery = new Table_Query( 'tag_relationships' );
+ $tagQuery->setSelect( 'contact_id' )->where->in( 'tag_id', $tags );
+
+ $where->notIn( 'ID', $tagQuery );
return;
}
@@ -2410,12 +2399,11 @@
$subWhere = $where->subWhere();
foreach ( $tags as $tag ) {
- $subWhere->notIn( 'ID', get_db( 'tag_relationships' )->get_sql( [
- 'select' => 'contact_id',
- 'tag_id' => $tag,
- 'orderby' => false,
- 'order' => false,
- ] ) );
+
+ $tagQuery = new Table_Query( 'tag_relationships' );
+ $tagQuery->setSelect( 'contact_id' )->where->in( 'tag_id', $tag );
+
+ $subWhere->notIn( 'ID', $tagQuery );
}
}