PluginProbe ʕ •ᴥ•ʔ
WooCommerce / 10.6.0-beta.2
WooCommerce v10.6.0-beta.2
10.8.1 10.8.0 10.8.0-rc.1 10.8.0-beta.2 10.8.0-beta.1 7.8.0-beta.1 7.8.0-beta.2 7.8.0-rc.1 7.8.0-rc.2 7.8.1 7.8.2 7.8.3 7.8.4 7.9.0 7.9.0-beta.1 7.9.0-beta.2 7.9.0-rc.2 7.9.0-rc.3 7.9.1 7.9.2 8.0.0 8.0.0-beta.1 8.0.0-beta.2 8.0.0-rc.1 8.0.0-rc.2 8.0.1 8.0.2 8.0.3 8.0.4 8.0.5 8.1.0 8.1.0-beta.1 8.1.0-rc.1 8.1.0-rc.2 8.1.1 8.1.2 8.1.3 8.1.4 8.2.0 8.2.0-beta.1 8.2.0-rc.1 8.2.0-rc.2 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.3.0 8.3.0-beta.1 8.3.0-rc.1 8.3.0-rc.2 8.3.1 8.3.2 8.3.3 8.3.4 8.4.0 8.4.0-beta.1 8.4.0-rc.1 8.4.1 8.4.2 8.4.3 8.5.0 8.5.0-beta.1 8.5.0-rc.1 8.5.1 8.5.2 8.5.3 8.5.4 8.5.5 8.6.0 8.6.0-beta.1 8.6.0-rc.1 8.6.1 8.6.2 8.6.3 8.6.4 8.7.0 8.7.0-beta.1 8.7.0-beta.2 8.7.0-rc.1 8.7.1 8.7.2 8.7.3 8.8.0 8.8.0-beta.1 8.8.0-rc.1 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.8.6 8.8.7 8.9.0 8.9.0-beta.1 8.9.0-rc.1 8.9.1 8.9.2 8.9.3 8.9.4 8.9.5 9.0.0 9.0.0-beta.1 9.0.0-beta.2 9.0.0-rc.1 9.0.1 9.0.2 9.0.3 9.0.4 9.1.0 9.1.0-beta.1 9.1.0-rc.1 9.1.1 9.1.2 9.1.3 9.1.4 9.1.5 9.1.6 9.2.0 9.2.0-beta.1 9.2.0-rc.1 9.2.1 9.2.2 9.2.3 9.2.4 9.2.5 9.3.0 9.3.0-beta.1 9.3.0-rc.1 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.3.6 9.4.0 9.4.0-beta.1 9.4.0-beta.2 9.4.0-rc.1 9.4.0-rc.2 9.4.0-rc.3 9.4.0-rc.4 9.4.1 9.4.2 9.4.3 9.4.4 9.4.5 9.5.0 9.5.0-beta.1 9.5.0-beta.2 9.5.0-rc.1 9.5.1 9.5.2 9.5.3 9.5.4 9.6.0 9.6.0-beta.1 9.6.0-beta.2 9.6.0-rc.1 9.6.1 9.6.2 9.6.3 9.6.4 9.7.0 9.7.0-beta.1 9.7.0-rc.1 9.7.1 9.7.2 9.7.3 9.8.0 9.8.0-beta.1 9.8.0-rc.1 9.8.1 9.8.2 9.8.3 9.8.4 9.8.5 9.8.6 9.8.7 9.9.0 9.9.0-beta.1 9.9.0-rc.1 9.9.1 9.9.2 9.9.3 9.9.4 9.9.5 9.9.6 9.9.7 3.7.3 7.1.2 3.8.0 7.2.0 3.8.0-beta.1 7.2.0-beta.1 3.8.0-rc.1 7.2.0-beta.2 3.8.0-rc.2 7.2.0-rc.1 3.8.1 7.2.0-rc.2 3.8.2 7.2.1 3.8.3 7.2.2 3.9.0 7.2.3 3.9.0-beta.1 7.2.4 3.9.0-beta.2 7.3.0 3.9.0-rc.1 7.3.0-beta.1 3.9.0-rc.2 7.3.0-beta.2 3.9.0-rc.3 7.3.0-rc.1 3.9.0-rc.4 7.3.0-rc.2 3.9.1 7.3.1 3.9.2 7.4.0 3.9.3 7.4.0-beta.1 3.9.4 7.4.0-beta.2 3.9.5 7.4.0-rc.1 4.0.0 7.4.0-rc.2 4.0.0-beta.1 7.4.1 4.0.0-rc.1 7.4.2 4.0.0-rc.2 7.5.0 4.0.1 7.5.0-beta.1 4.0.2 7.5.0-beta.2 4.0.3 7.5.0-rc.1 4.0.4 7.5.1 4.1.0 7.5.2 4.1.0-beta.1 7.6.0 4.1.0-beta.2 7.6.0-beta.1 4.1.0-rc.1 7.6.0-beta.2 4.1.0-rc.2 7.6.0-rc.1 4.1.1 7.6.0-rc.2 4.1.2 7.6.0-rc.3 4.1.3 7.6.1 4.1.4 7.6.2 4.2.0 7.7.0 4.2.0-RC.1 7.7.0-beta.1 4.2.0-RC.2 7.7.0-beta.2 4.2.0-beta.1 7.7.0-rc.1 4.2.1 7.7.1 4.2.2 7.7.2 4.2.3 7.7.3 4.2.4 7.8.0 4.2.5 4.3.0 4.3.0-beta.1 4.3.0-rc.1 4.3.0-rc.2 4.3.0-rc.3 4.3.1 4.3.2 4.3.3 4.3.4 4.3.5 4.3.6 4.4.0 4.4.0-beta.1 4.4.0-rc.1 4.4.1 4.4.2 4.4.3 4.4.4 4.5.0 4.5.0-beta.1 4.5.0-rc.1 4.5.0-rc.3 4.5.1 4.5.2 4.5.3 4.5.4 4.5.5 4.6.0 4.6.0-beta.1 4.6.0-rc.1 4.6.1 4.6.2 4.6.3 4.6.4 4.6.5 4.7.0 4.7.0-beta.1 4.7.0-beta.2 4.7.0-rc.1 4.7.1 4.7.1-beta.1 4.7.2 4.7.3 4.7.4 4.8.0 4.8.0-beta.1 4.8.0-rc.1 4.8.0-rc.2 4.8.1 4.8.2 4.8.3 4.9.0 4.9.0-beta.1 4.9.0-rc.1 4.9.0-rc.2 4.9.1 4.9.2 4.9.3 4.9.4 4.9.5 5.0.0 5.0.0-beta.1 5.0.0-beta.2 5.0.0-rc.1 5.0.0-rc.2 5.0.0-rc.3 5.0.1 5.0.2 5.0.3 5.1.0 5.1.0-beta.1 5.1.0-rc.1 trunk 5.1.1 10.0.0 5.1.2 10.0.0-rc.1 5.1.3 10.0.0-rc.2 5.2.0 10.0.1 5.2.0-beta.1 10.0.2 5.2.0-rc.1 10.0.3 5.2.0-rc.2 10.0.4 5.2.1 10.0.5 5.2.2 10.0.6 5.2.3 10.1.0 5.2.4 10.1.0-rc.1 5.2.5 10.1.0-rc.2 5.3.0 10.1.0-rc.3 5.3.0-beta.1 10.1.0-rc.4 5.3.0-rc.1 10.1.1 5.3.0-rc.2 10.1.2 5.3.1 10.1.3 5.3.2 10.1.4 5.3.3 10.2.0 5.4.0 10.2.0-beta.1 5.4.0-beta.1 10.2.0-beta.2 5.4.0-rc.1 10.2.0-rc.1 5.4.1 10.2.1 5.4.2 10.2.2 5.4.3 10.2.3 5.4.4 10.2.4 5.4.5 10.3.0 5.5.0 10.3.0-beta.1 5.5.0-beta.1 10.3.0-beta.2 5.5.0-rc.1 10.3.0-rc.1 5.5.0-rc.2 10.3.0-rc.2 5.5.1 10.3.1 5.5.2 10.3.2 5.5.3 10.3.3 5.5.4 10.3.4 5.5.5 10.3.5 5.6.0 10.3.6 5.6.0-beta.1 10.3.7 5.6.0-rc.1 10.3.8 5.6.0-rc.2 10.4.0 5.6.1 10.4.0-beta.1 5.6.2 10.4.0-beta.2 5.6.3 10.4.0-rc.1 5.7.0 10.4.1 5.7.0-beta.1 10.4.2 5.7.0-rc.1 10.4.3 5.7.1 10.4.4 5.7.2 10.5.0 5.7.3 10.5.0-beta.1 5.8.0 10.5.0-beta.2 5.8.0-beta.1 10.5.0-rc.1 5.8.0-beta.2 10.5.0-rc.2 5.8.0-rc.1 10.5.0-rc.3 5.8.1 10.5.1 5.8.2 10.5.2 5.9.0 10.5.3 5.9.0-beta.1 10.6.0 5.9.0-rc.1 10.6.0-beta.1 5.9.0-rc.2 10.6.0-beta.2 5.9.1 10.6.0-rc.1 5.9.2 10.6.1 6.0.0 10.6.2 6.0.0-beta.1 10.7.0 6.0.0-rc.1 10.7.0-beta.1 6.0.1 10.7.0-beta.2 6.0.2 10.7.0-rc.1 6.1.0 3.0.0 6.1.0-beta.1 3.0.1 6.1.0-rc.1 3.0.2 6.1.0-rc.2 3.0.3 6.1.1 3.0.4 6.1.2 3.0.5 6.1.3 3.0.6 6.2.0 3.0.7 6.2.0-beta.1 3.0.8 6.2.0-rc.1 3.0.9 6.2.0-rc.2 3.1.0 6.2.1 3.1.1 6.2.2 3.1.2 6.2.3 3.2.0 6.3.0 3.2.1 6.3.0-beta.1 3.2.2 6.3.0-rc.1 3.2.3 6.3.0-rc.2 3.2.4 6.3.1 3.2.5 6.3.2 3.2.6 6.4.0 3.3.0 6.4.0-beta.1 3.3.1 6.4.0-rc.1 3.3.2 6.4.1 3.3.2-rc.1 6.4.2 3.3.3 6.5.0 3.3.4 6.5.0-beta.1 3.3.5 6.5.0-rc.1 3.3.6 6.5.0-rc.2 3.4.0 6.5.1 3.4.0-beta.1 6.5.2 3.4.0-rc.2 6.6.0 3.4.1 6.6.0-beta.1 3.4.2 6.6.0-rc.1 3.4.3 6.6.0-rc.2 3.4.4 6.6.1 3.4.5 6.6.2 3.4.6 6.7.0 3.4.7 6.7.0-beta.1 3.4.8 6.7.0-beta.2 3.5.0 6.7.0-rc.1 3.5.0-beta.1 6.7.1 3.5.0-rc.1 6.8.0 3.5.0-rc.2 6.8.0-beta.1 3.5.1 6.8.0-beta.2 3.5.10 6.8.0-rc.1 3.5.2 6.8.1 3.5.3 6.8.2 3.5.4 6.8.3 3.5.5 6.9.0 3.5.6 6.9.0-beta.1 3.5.7 6.9.0-beta.2 3.5.8 6.9.0-rc.1 3.5.9 6.9.1 3.6.0 6.9.2 3.6.0-beta.1 6.9.3 3.6.0-rc.1 6.9.4 3.6.0-rc.2 6.9.5 3.6.0-rc.3 7.0.0 3.6.1 7.0.0-beta.1 3.6.2 7.0.0-beta.2 3.6.3 7.0.0-beta.3 3.6.4 7.0.0-rc.1 3.6.5 7.0.0-rc.2 3.6.6 7.0.1 3.6.7 7.0.2 3.7.0 7.1.0 3.7.0-beta.1 7.1.0-beta.1 3.7.0-rc.1 7.1.0-beta.2 3.7.0-rc.2 7.1.0-rc.1 3.7.1 7.1.0-rc.2 3.7.2 7.1.1
woocommerce / src / Internal / Utilities / DatabaseUtil.php
woocommerce / src / Internal / Utilities Last commit date
ArrayUtil.php 6 months ago BlocksUtil.php 4 months ago COTMigrationUtil.php 1 year ago DatabaseUtil.php 1 year ago FilesystemUtil.php 6 months ago HtmlSanitizer.php 2 years ago LegacyRestApiStub.php 1 year ago PluginInstaller.php 1 year ago ProductUtil.php 8 months ago Types.php 1 year ago URL.php 1 year ago URLException.php 4 years ago Users.php 3 months ago WebhookUtil.php 1 year ago
DatabaseUtil.php
441 lines
1 <?php
2 /**
3 * DatabaseUtil class file.
4 */
5
6 namespace Automattic\WooCommerce\Internal\Utilities;
7
8 use DateTime;
9 use DateTimeZone;
10 use Vtiful\Kernel\Format;
11
12 /**
13 * A class of utilities for dealing with the database.
14 */
15 class DatabaseUtil {
16
17 /**
18 * Wrapper for the WordPress dbDelta function, allows to execute a series of SQL queries.
19 *
20 * @param string $queries The SQL queries to execute.
21 * @param bool $execute Ture to actually execute the queries, false to only simulate the execution.
22 * @return array The result of the execution (or simulation) from dbDelta.
23 */
24 public function dbdelta( string $queries = '', bool $execute = true ): array {
25 require_once ABSPATH . 'wp-admin/includes/upgrade.php';
26
27 return dbDelta( $queries, $execute );
28 }
29
30 /**
31 * Given a set of table creation SQL statements, check which of the tables are currently missing in the database.
32 *
33 * @param string $creation_queries The SQL queries to execute ("CREATE TABLE" statements, same format as for dbDelta).
34 * @return array An array containing the names of the tables that currently don't exist in the database.
35 */
36 public function get_missing_tables( string $creation_queries ): array {
37 global $wpdb;
38 $suppress_errors = $wpdb->suppress_errors( true );
39 $dbdelta_output = $this->dbdelta( $creation_queries, false );
40 $wpdb->suppress_errors( $suppress_errors );
41 $parsed_output = $this->parse_dbdelta_output( $dbdelta_output );
42 return $parsed_output['created_tables'];
43 }
44
45 /**
46 * Parses the output given by dbdelta and returns information about it.
47 *
48 * @param array $dbdelta_output The output from the execution of dbdelta.
49 * @return array[] An array containing a 'created_tables' key whose value is an array with the names of the tables that have been (or would have been) created.
50 */
51 public function parse_dbdelta_output( array $dbdelta_output ): array {
52 $created_tables = array();
53
54 foreach ( $dbdelta_output as $table_name => $result ) {
55 if ( "Created table $table_name" === $result ) {
56 $created_tables[] = str_replace( '(', '', $table_name );
57 }
58 }
59
60 return array( 'created_tables' => $created_tables );
61 }
62
63 /**
64 * Drops a database table.
65 *
66 * @param string $table_name The name of the table to drop.
67 * @param bool $add_prefix True if the table name passed needs to be prefixed with $wpdb->prefix before processing.
68 * @return bool True on success, false on error.
69 */
70 public function drop_database_table( string $table_name, bool $add_prefix = false ) {
71 global $wpdb;
72
73 if ( $add_prefix ) {
74 $table_name = $wpdb->prefix . $table_name;
75 }
76
77 //phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
78 return $wpdb->query( "DROP TABLE IF EXISTS `{$table_name}`" );
79 }
80
81 /**
82 * Drops a table index, if both the table and the index exist.
83 *
84 * @param string $table_name The name of the table that contains the index.
85 * @param string $index_name The name of the index to be dropped.
86 * @return bool True if the index has been dropped, false if either the table or the index don't exist.
87 */
88 public function drop_table_index( string $table_name, string $index_name ): bool {
89 global $wpdb;
90
91 if ( empty( $this->get_index_columns( $table_name, $index_name ) ) ) {
92 return false;
93 }
94
95 // phpcs:ignore WordPress.DB.PreparedSQL
96 $wpdb->query( "ALTER TABLE $table_name DROP INDEX $index_name" );
97 return true;
98 }
99
100 /**
101 * Create a primary key for a table, only if the table doesn't have a primary key already.
102 *
103 * @param string $table_name Table name.
104 * @param array $columns An array with the index column names.
105 * @return bool True if the key has been created, false if the table already had a primary key.
106 */
107 public function create_primary_key( string $table_name, array $columns ) {
108 global $wpdb;
109
110 if ( ! empty( $this->get_index_columns( $table_name ) ) ) {
111 return false;
112 }
113
114 // phpcs:ignore WordPress.DB.PreparedSQL
115 $wpdb->query( "ALTER TABLE $table_name ADD PRIMARY KEY(`" . join( '`,`', $columns ) . '`)' );
116 return true;
117 }
118
119 /**
120 * Get the columns of a given table index, or of the primary key.
121 *
122 * @param string $table_name Table name.
123 * @param string $index_name Index name, empty string for the primary key.
124 * @return array The index columns. Empty array if the table or the index don't exist.
125 */
126 public function get_index_columns( string $table_name, string $index_name = '' ): array {
127 global $wpdb;
128
129 if ( empty( $index_name ) ) {
130 $index_name = 'PRIMARY';
131 }
132
133 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
134 $results = $wpdb->get_results( $wpdb->prepare( "SHOW INDEX FROM $table_name WHERE Key_name = %s", $index_name ) );
135
136 if ( empty( $results ) ) {
137 return array();
138 }
139
140 return array_column( $results, 'Column_name' );
141 }
142
143 /**
144 * Formats an object value of type `$type` for inclusion in the database.
145 *
146 * @param mixed $value Raw value.
147 * @param string $type Data type.
148 * @return mixed
149 * @throws \Exception When an invalid type is passed.
150 */
151 public function format_object_value_for_db( $value, string $type ) {
152 switch ( $type ) {
153 case 'decimal':
154 $value = wc_format_decimal( $value, false, true );
155 break;
156 case 'int':
157 $value = (int) $value;
158 break;
159 case 'bool':
160 $value = wc_string_to_bool( $value );
161 break;
162 case 'string':
163 $value = strval( $value );
164 break;
165 case 'date':
166 // Date properties are converted to the WP timezone (see WC_Data::set_date_prop() method), however
167 // for our own tables we persist dates in GMT.
168 $value = $value ? ( new DateTime( $value ) )->setTimezone( new DateTimeZone( '+00:00' ) )->format( 'Y-m-d H:i:s' ) : null;
169 break;
170 case 'date_epoch':
171 $value = $value ? ( new DateTime( "@{$value}" ) )->format( 'Y-m-d H:i:s' ) : null;
172 break;
173 default:
174 throw new \Exception( esc_html( 'Invalid type received: ' . $type ) );
175 }
176
177 return $value;
178 }
179
180 /**
181 * Returns the `$wpdb` placeholder to use for data type `$type`.
182 *
183 * @param string $type Data type.
184 * @return string
185 * @throws \Exception When an invalid type is passed.
186 */
187 public function get_wpdb_format_for_type( string $type ) {
188 static $wpdb_placeholder_for_type = array(
189 'int' => '%d',
190 'decimal' => '%f',
191 'string' => '%s',
192 'date' => '%s',
193 'date_epoch' => '%s',
194 'bool' => '%d',
195 );
196
197 if ( ! isset( $wpdb_placeholder_for_type[ $type ] ) ) {
198 throw new \Exception( esc_html( 'Invalid column type: ' . $type ) );
199 }
200
201 return $wpdb_placeholder_for_type[ $type ];
202 }
203
204 /**
205 * Generates ON DUPLICATE KEY UPDATE clause to be used in migration.
206 *
207 * @param array $columns List of column names.
208 *
209 * @return string SQL clause for INSERT...ON DUPLICATE KEY UPDATE
210 */
211 public function generate_on_duplicate_statement_clause( array $columns ): string {
212 $update_value_statements = array();
213 foreach ( $columns as $column ) {
214 $update_value_statements[] = "`$column` = VALUES( `$column` )";
215 }
216 $update_value_clause = implode( ', ', $update_value_statements );
217
218 return "ON DUPLICATE KEY UPDATE $update_value_clause";
219 }
220
221 /**
222 * Hybrid of $wpdb->update and $wpdb->insert. It will try to update a row, and if it doesn't exist, it will insert it. This needs unique constraints to be set on the table on all ID columns.
223 *
224 * You can use this function only when:
225 * 1. There is only one unique constraint on the table. The constraint can contain multiple columns, but it must be the only one unique constraint.
226 * 2. The complete unique constraint must be part of the $data array.
227 * 3. You do not need the LAST_INSERT_ID() value.
228 *
229 * @param string $table_name Table name.
230 * @param array $data Unescaped data to update (in column => value pairs).
231 * @param array $format An array of formats to be mapped to each of the values in $data.
232 *
233 * @return int Returns the value of DB's ON DUPLICATE KEY UPDATE clause.
234 */
235 public function insert_on_duplicate_key_update( $table_name, $data, $format ): int {
236 global $wpdb;
237 if ( empty( $data ) ) {
238 return 0;
239 }
240
241 $columns = array_keys( $data );
242 $value_format = array();
243 $values = array();
244 $index = 0;
245 // Directly use NULL for placeholder if the value is NULL, since otherwise $wpdb->prepare will convert it to empty string.
246 foreach ( $data as $key => $value ) {
247 if ( is_null( $value ) ) {
248 $value_format[] = 'NULL';
249 } else {
250 $values[] = $value;
251 $value_format[] = $format[ $index ];
252 }
253 ++$index;
254 }
255 $column_clause = '`' . implode( '`, `', $columns ) . '`';
256 $value_format_clause = implode( ', ', $value_format );
257 $on_duplicate_clause = $this->generate_on_duplicate_statement_clause( $columns );
258 // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- Values are escaped in $wpdb->prepare.
259 $sql = $wpdb->prepare(
260 "
261 INSERT INTO $table_name ( $column_clause )
262 VALUES ( $value_format_clause )
263 $on_duplicate_clause
264 ",
265 $values
266 );
267 // phpcs:enable
268 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $sql is prepared.
269 return $wpdb->query( $sql );
270 }
271
272 /**
273 * Hybrid of $wpdb->update and $wpdb->insert. It will try to update a row, and if it doesn't exist, it will insert it. Unlike `insert_on_duplicate_key_update` it does not require a unique constraint, but also does not guarantee uniqueness on its own.
274 *
275 * When a unique constraint is present, it will perform better than the `insert_on_duplicate_key_update` since it needs fewer locks.
276 *
277 * Note that it will only update at max just 1 database row, unlike `wpdb->update` which updates everything that matches the `$where` criteria. This is also why it needs a primary_key_column.
278 *
279 * @param string $table_name Table Name.
280 * @param array $data Data to insert update in array($column_name => $value) format.
281 * @param array $where Update conditions in array($column_name => $value) format. Conditions will be joined by AND.
282 * @param array $format Format strings for data. Unlike $wpdb->update/insert, this method won't guess the format, and has to be provided explicitly.
283 * @param array $where_format Format strings for where conditions. Unlike $wpdb->update/insert, this method won't guess the format, and has to be provided explicitly.
284 * @param string $primary_key_column Name of the Primary key column.
285 * @param string $primary_key_format Format for primary key.
286 *
287 * @return bool|int Number of rows affected. Boolean false on error.
288 */
289 public function insert_or_update( $table_name, $data, $where, $format, $where_format, $primary_key_column = 'id', $primary_key_format = '%d' ) {
290 global $wpdb;
291 if ( empty( $data ) || empty( $where ) ) {
292 return 0;
293 }
294
295 // Build select query.
296 $values = array();
297 $index = 0;
298 $conditions = array();
299 foreach ( $where as $column => $value ) {
300 if ( is_null( $value ) ) {
301 $conditions[] = "`$column` IS NULL";
302 continue;
303 }
304 $conditions[] = "`$column` = " . $where_format[ $index ];
305 $values[] = $value;
306 ++$index;
307 }
308
309 $conditions = implode( ' AND ', $conditions );
310 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- $primary_key_column and $table_name are hardcoded. $conditions is being prepared.
311 $query = $wpdb->prepare( "SELECT `$primary_key_column` FROM `$table_name` WHERE $conditions LIMIT 1", $values );
312
313 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $query is prepared above.
314 $row_id = $wpdb->get_var( $query );
315
316 if ( $row_id ) {
317 // Update the row.
318 $result = $wpdb->update( $table_name, $data, array( $primary_key_column => $row_id ), $format, array( $primary_key_format ) );
319 } else {
320 // Insert the row.
321 $result = $wpdb->insert( $table_name, $data, $format );
322 }
323
324 return $result;
325 }
326
327 /**
328 * Get max index length.
329 *
330 * @return int Max index length.
331 */
332 public function get_max_index_length(): int {
333 /**
334 * Filters the maximum index length in the database.
335 *
336 * Indexes have a maximum size of 767 bytes. Historically, we haven't need to be concerned about that.
337 * As of WP 4.2, however, they moved to utf8mb4, which uses 4 bytes per character. This means that an index which
338 * used to have room for floor(767/3) = 255 characters, now only has room for floor(767/4) = 191 characters.
339 *
340 * Additionally, MyISAM engine also limits the index size to 1000 bytes. We add this filter so that interested folks on InnoDB engine can increase the size till allowed 3071 bytes.
341 *
342 * @param int $max_index_length Maximum index length. Default 191.
343 *
344 * @since 8.0.0
345 */
346 $max_index_length = apply_filters( 'woocommerce_database_max_index_length', 191 );
347 // Index length cannot be more than 768, which is 3078 bytes in utf8mb4 and max allowed by InnoDB engine.
348 return min( absint( $max_index_length ), 767 );
349 }
350
351 /**
352 * Create a fulltext index on order address table.
353 *
354 * @return void
355 */
356 public function create_fts_index_order_address_table(): void {
357 global $wpdb;
358 $address_table = $wpdb->prefix . 'wc_order_addresses';
359 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $address_table is hardcoded.
360 $wpdb->query( "CREATE FULLTEXT INDEX order_addresses_fts ON $address_table (first_name, last_name, company, address_1, address_2, city, state, postcode, country, email, phone)" );
361 }
362
363 /**
364 * Helper method to drop the fulltext index on order address table.
365 *
366 * @since 9.4.0
367 *
368 * @return void
369 */
370 public function drop_fts_index_order_address_table(): void {
371 global $wpdb;
372 $address_table = $wpdb->prefix . 'wc_order_addresses';
373 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $address_table is hardcoded.
374 $wpdb->query( "ALTER TABLE $address_table DROP INDEX order_addresses_fts;" );
375 }
376
377 /**
378 * Sanitize FTS Search params to remove relevancy operators for performance, and add partial matches. Useful when the sorting is already happening based on some other conditions, so relevancy calculation is not needed.
379 *
380 * @since 9.4.0
381 *
382 * @param string $param Search term.
383 *
384 * @return string Sanitized search term.
385 */
386 public function sanitise_boolean_fts_search_term( string $param ): string {
387 // Remove any operator to prevent incorrect query and fatals, such as search starting with `++`. We can allow this in the future if we have proper validation for FTS search operators.
388 // Space is allowed to provide multiple words.
389 $sanitized_param = preg_replace( '/[^\p{L}\p{N}_]+/u', ' ', $param );
390 if ( $sanitized_param !== $param ) {
391 $param = str_replace( '"', '', $param );
392 return '"' . $param . '"';
393 }
394 // Split the search phrase into words so that we can add operators when needed.
395 $words = explode( ' ', $param );
396 $sanitized_words = array();
397 foreach ( $words as $word ) {
398 // Add `*` as suffix to every term so that partial matches happens.
399 $word = $word . '*';
400 $sanitized_words[] = $word;
401 }
402 return implode( ' ', $sanitized_words );
403 }
404
405 /**
406 * Check if fulltext index with key `order_addresses_fts` on order address table exists.
407 *
408 * @return bool
409 */
410 public function fts_index_on_order_address_table_exists(): bool {
411 global $wpdb;
412 $address_table = $wpdb->prefix . 'wc_order_addresses';
413 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $address_table is hardcoded.
414 return ! empty( $wpdb->get_results( "SHOW INDEX FROM $address_table WHERE Key_name = 'order_addresses_fts'" ) );
415 }
416
417 /**
418 * Create a fulltext index on order item table.
419 *
420 * @return void
421 */
422 public function create_fts_index_order_item_table(): void {
423 global $wpdb;
424 $order_item_table = $wpdb->prefix . 'woocommerce_order_items';
425 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $order_item_table is hardcoded.
426 $wpdb->query( "CREATE FULLTEXT INDEX order_item_fts ON $order_item_table (order_item_name)" );
427 }
428
429 /**
430 * Check if fulltext index with key `order_item_fts` on order item table exists.
431 *
432 * @return bool
433 */
434 public function fts_index_on_order_item_table_exists(): bool {
435 global $wpdb;
436 $order_item_table = $wpdb->prefix . 'woocommerce_order_items';
437 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $order_item_table is hardcoded.
438 return ! empty( $wpdb->get_results( "SHOW INDEX FROM $order_item_table WHERE Key_name = 'order_item_fts'" ) );
439 }
440 }
441