prepare(). * * @package Nota_Store_Analytics */ defined( 'ABSPATH' ) || exit; // This is the one place where the plugin's report queries run. Every query is made of pieces that // carry a placeholder for each value (add(), in()), and prepared() hands the whole text and all // the values to a single $wpdb->prepare(). Code the plugin wrote itself (a column from a fixed // list, a date format) is added with code(), which holds no value. The tables are the plugin's // own and change with every order, so the results are not cached. // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- See the comment above. /** * A prepared-in-one-step query. */ final class NOTASTAN_Sql { /** * SQL so far, with placeholders. * * @var string */ private $sql = ''; /** * The values of the placeholders, in order. * * @var array */ private $args = array(); /** * Starts a query. * * @param string|NOTASTAN_Sql $sql SQL with placeholders, or another query. * @param array $args Values of the placeholders. */ public static function of( $sql, array $args = array() ): self { return ( new self() )->add( $sql, $args ); } /** * Appends SQL, or another query, and its values. * * @param string|NOTASTAN_Sql $sql SQL with placeholders, or another query. * @param array $args Values of the placeholders. Ignored for a query. */ public function add( $sql, array $args = array() ): self { if ( $sql instanceof self ) { $this->sql .= $sql->sql; $this->args = array_merge( $this->args, $sql->args ); return $this; } $this->sql .= (string) $sql; $this->args = array_merge( $this->args, array_values( $args ) ); return $this; } /** * Appends SQL the plugin wrote itself and that holds no value: a column, a sort direction, a * date format. A percent sign in it is kept as it is. * * @param string $sql SQL. */ public function code( string $sql ): self { $this->sql .= str_replace( '%', '%%', $sql ); return $this; } /** * Appends a list for IN or NOT IN with a placeholder for every value. * * @param array $values Values; an empty list matches nothing. * @param string $format Placeholder of each value, '%d' or '%s'. */ public function in( array $values, string $format = '%d' ): self { $values = array_values( $values ); if ( ! $values ) { return $this->add( '(NULL)' ); } return $this->add( '(' . implode( ',', array_fill( 0, count( $values ), '%s' === $format ? '%s' : '%d' ) ) . ')', $values ); } /** * The query with its values in place. */ public function prepared(): string { global $wpdb; if ( ! $this->args ) { return str_replace( '%%', '%', $this->sql ); } return $wpdb->prepare( $this->sql, $this->args ); } /** * Runs the query for its effect (a delete, an update). */ public function run(): void { global $wpdb; $wpdb->query( $this->prepared() ); } /** * Rows as associative arrays. */ public function results(): array { global $wpdb; return (array) $wpdb->get_results( $this->prepared(), ARRAY_A ); } /** * The first row as an associative array, or an empty array. */ public function row(): array { global $wpdb; return (array) $wpdb->get_row( $this->prepared(), ARRAY_A ); } /** * The first column of the first row. * * @return string|null */ public function value() { global $wpdb; return $wpdb->get_var( $this->prepared() ); } /** * The first column of every row. */ public function column(): array { global $wpdb; return (array) $wpdb->get_col( $this->prepared() ); } }