PluginProbe
NotificationX – FOMO, Live Sales Notification, WooCommerce Sales Popup, GDPR, Social Proof, Announcement Banner & Floating Notification Bar / trunk
NotificationX – FOMO, Live Sales Notification, WooCommerce Sales Popup, GDPR, Social Proof, Announcement Banner & Floating Notification Bar vtrunk
3.3.1 3.3.0 3.2.14 3.2.13 3.2.12 3.2.11 3.2.10 3.2.9 3.2.8 3.2.7 trunk 0.2.5.5 0.2.5.6 0.2.5.7 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.2.0 1.2.1 All 156 releases
← All changes | vendor/wpdeveloper/query-builder/src/Query.php +62 -4 3.2.11trunk View file →
@@ -263,11 +263,11 @@
263 263 //If not column then use wpdb prepare
264 264 //if contains $prefix
265 265 $contain_join = preg_replace( '/^(\s?AND ?|\s?OR ?)|\s$/i', '', $param2 );
266 266
267 - $param2 = is_array( $param2 ) ? ( '("' . implode( '","', $param2 ) . '")' ) : ( $param2 === null
267 + $param2 = is_array( $param2 ) ? ( '(' . implode( ',', array_map( [ $this, 'bind_value' ], $param2 ) ) . ')' ) : ( $param2 === null
268 268 ? 'null'
269 - : ( strpos( $param2, '.' ) !== false || strpos( $param2, $wpdb->prefix ) !== false ? $param2 : $wpdb->prepare( is_numeric( $param2 ) ? '%d' : '%s', $param2 ) )
269 + : ( $this->is_column_reference( $param2 ) ? $param2 : $this->bind_value( $param2 ) )
270 270 );
271 271
272 272 $this->where[] = [
273 273 'joint' => $joint,
@@ -508,12 +508,12 @@
508 508 $referenceKey = $operator;
509 509 $operator = '=';
510 510 }
511 511
512 - $referenceKey = is_array( $referenceKey ) ? ( '(\'' . implode( '\',\'', $referenceKey ) . '\')' )
512 + $referenceKey = is_array( $referenceKey ) ? ( '(' . implode( ',', array_map( [ $this, 'bind_value' ], $referenceKey ) ) . ')' )
513 513 : ( $referenceKey === null
514 514 ? 'null'
515 - : ( strpos( $referenceKey, '.' ) !== false || strpos( $referenceKey, $wpdb->prefix ) !== false ? $referenceKey : $wpdb->prepare( is_numeric( $referenceKey ) ? '%d' : '%s', $referenceKey ) )
515 + : ( $this->is_column_reference( $referenceKey ) ? $referenceKey : $this->bind_value( $referenceKey ) )
516 516 );
517 517
518 518 $join['on'][] = [
519 519 'joint' => $joint,
@@ -1354,8 +1354,66 @@
1354 1354 $query .= trim( 'DELETE ' . ( count( $this->join )
1355 1355 ? preg_replace( '/\s[aA][sS][\s\S]+.*?/', '', $this->from )
1356 1356 : ''
1357 1357 ) );
1358 + }
1359 +
1360 + /**
1361 + * Decide whether a right-hand operand is a column reference rather than a value.
1362 + *
1363 + * `where()` and `join()` accept either on the right: `join('nx_stats b', 'b.nx_id',
1364 + * '=', 'a.nx_id')` compares two columns, while `where('title', 'LIKE', $search)`
1365 + * compares a column to a value. Only a value may be bound, so the two have to be
1366 + * told apart -- and the test used to be "does it contain a dot, or the table
1367 + * prefix?". That is true of `a.nx_id`, but it is also true of any search term
1368 + * carrying a dot, so `?s=.%27%20UNION%20SELECT...` was emitted into the statement
1369 + * verbatim and the LIKE became an injection point.
1370 + *
1371 + * The test is now the shape of a *qualified* identifier -- `table.column`, each half
1372 + * optionally backquoted -- so a real column reference still passes through unbound
1373 + * while anything carrying a quote, space, comment marker or wildcard falls through
1374 + * to {@see self::bind_value()}.
1375 + *
1376 + * The qualifier is required on purpose. A bare `published` is far more likely to be
1377 + * a value than a column, and it was always bound under the old test too, so treating
1378 + * unqualified names as columns here would silently turn `where('status',
1379 + * 'published')` into a comparison against a non-existent column.
1380 + *
1381 + * @param mixed $value Right-hand operand.
1382 + *
1383 + * @return bool
1384 + */
1385 + private function is_column_reference( $value ) {
1386 + if ( ! is_string( $value ) || '' === $value ) {
1387 + return false;
1388 + }
1389 +
1390 + return (bool) preg_match( '/^`?[A-Za-z_][A-Za-z0-9_]*`?\.`?[A-Za-z_][A-Za-z0-9_]*`?$/', $value );
1391 + }
1392 +
1393 + /**
1394 + * Bind a value into the statement through $wpdb->prepare().
1395 + *
1396 + * @param mixed $value Value to bind.
1397 + *
1398 + * @return string Quoted, escaped SQL literal.
1399 + */
1400 + private function bind_value( $value ) {
1401 + global $wpdb;
1402 +
1403 + if ( null === $value ) {
1404 + return 'null';
1405 + }
1406 + if ( is_bool( $value ) ) {
1407 + return $wpdb->prepare( '%d', $value ? 1 : 0 );
1408 + }
1409 + if ( ! is_numeric( $value ) ) {
1410 + return $wpdb->prepare( '%s', $value );
1411 + }
1412 +
1413 + // %d on a float silently truncates it, so anything with a decimal point or an
1414 + // exponent takes %f. Integer strings -- '007' included -- keep %d as before.
1415 + return $wpdb->prepare( ( is_float( $value ) || false !== strpbrk( (string) $value, '.eE' ) ) ? '%f' : '%d', $value );
1358 1416 }
1359 1417
1360 1418 /**
1361 1419 * Sanitize value.