1
|
|
|
<?php |
2
|
|
|
namespace EventEspresso\core\services\database; |
3
|
|
|
|
4
|
|
|
defined('EVENT_ESPRESSO_VERSION') || exit('No direct script access allowed'); |
5
|
|
|
|
6
|
|
|
|
7
|
|
|
|
8
|
|
|
/** |
9
|
|
|
* Class TableManager |
10
|
|
|
* For performing mysql database table schema manipulation |
11
|
|
|
* |
12
|
|
|
* @package Event Espresso |
13
|
|
|
* @subpackage |
14
|
|
|
* @author Mike Nelson |
15
|
|
|
* @since $VID:$ |
16
|
|
|
*/ |
17
|
|
|
class TableManager extends \EE_Base |
18
|
|
|
{ |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* @var TableAnalysis $table_analysis |
22
|
|
|
*/ |
23
|
|
|
private $table_analysis; |
24
|
|
|
|
25
|
|
|
|
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* TableManager constructor. |
29
|
|
|
* |
30
|
|
|
* @param TableAnalysis $TableAnalysis |
31
|
|
|
*/ |
32
|
|
|
public function __construct(TableAnalysis $TableAnalysis) |
33
|
|
|
{ |
34
|
|
|
$this->table_analysis = $TableAnalysis; |
35
|
|
|
} |
36
|
|
|
|
37
|
|
|
|
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* Gets the injected table analyzer, or throws an exception |
41
|
|
|
* |
42
|
|
|
* @return TableAnalysis |
43
|
|
|
* @throws \EE_Error |
44
|
|
|
*/ |
45
|
|
View Code Duplication |
protected function getTableAnalysis() |
|
|
|
|
46
|
|
|
{ |
47
|
|
|
if ($this->table_analysis instanceof TableAnalysis) { |
48
|
|
|
return $this->table_analysis; |
49
|
|
|
} else { |
50
|
|
|
throw new \EE_Error( |
51
|
|
|
sprintf( |
52
|
|
|
__('Table analysis class on class %1$s is not set properly.', 'event_espresso'), |
53
|
|
|
get_class($this) |
54
|
|
|
) |
55
|
|
|
); |
56
|
|
|
} |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
|
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* @param string $table_name which can optionally start with $wpdb->prefix or not |
63
|
|
|
* @param string $column_name |
64
|
|
|
* @param string $column_info |
65
|
|
|
* @return bool|false|int |
66
|
|
|
*/ |
67
|
|
|
public function addColumn($table_name, $column_name, $column_info = 'INT UNSIGNED NOT NULL') |
68
|
|
|
{ |
69
|
|
|
if (apply_filters('FHEE__EEH_Activation__add_column_if_it_doesnt_exist__short_circuit', false)) { |
70
|
|
|
return false; |
71
|
|
|
} |
72
|
|
|
global $wpdb; |
73
|
|
|
$full_table_name = $this->getTableAnalysis()->ensureTableNameHasPrefix($table_name); |
74
|
|
|
$columns = $this->getTableColumns($table_name); |
75
|
|
|
if ( ! in_array($column_name, $columns)) { |
76
|
|
|
$alter_query = "ALTER TABLE {$full_table_name} ADD {$column_name} {$column_info}"; |
77
|
|
|
return $wpdb->query($alter_query); |
78
|
|
|
} |
79
|
|
|
return true; |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
|
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* Gets the name of all columns on the table. $table_name can |
86
|
|
|
* optionally start with $wpdb->prefix or not |
87
|
|
|
* |
88
|
|
|
* @global \wpdb $wpdb |
89
|
|
|
* @param string $table_name |
90
|
|
|
* @return array |
91
|
|
|
*/ |
92
|
|
|
public function getTableColumns($table_name) |
93
|
|
|
{ |
94
|
|
|
global $wpdb; |
95
|
|
|
$table_name = $this->getTableAnalysis()->ensureTableNameHasPrefix($table_name); |
96
|
|
|
$field_array = array(); |
97
|
|
|
if ( ! empty($table_name)) { |
98
|
|
|
$columns = $wpdb->get_results("SHOW COLUMNS FROM {$table_name} "); |
99
|
|
|
if ($columns !== false) { |
100
|
|
|
foreach ($columns as $column) { |
101
|
|
|
$field_array[] = $column->Field; |
102
|
|
|
} |
103
|
|
|
} |
104
|
|
|
} |
105
|
|
|
return $field_array; |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
|
109
|
|
|
|
110
|
|
|
/** |
111
|
|
|
* Drops the specified table from the database. $table_name can |
112
|
|
|
* optionally start with $wpdb->prefix or not |
113
|
|
|
* |
114
|
|
|
* @global \wpdb $wpdb |
115
|
|
|
* @param string $table_name |
116
|
|
|
* @return int |
117
|
|
|
*/ |
118
|
|
|
public function dropTable($table_name) |
119
|
|
|
{ |
120
|
|
|
global $wpdb; |
121
|
|
|
if ($this->getTableAnalysis()->tableExists($table_name)) { |
122
|
|
|
$table_name = $this->getTableAnalysis()->ensureTableNameHasPrefix($table_name); |
123
|
|
|
return $wpdb->query("DROP TABLE IF EXISTS {$table_name}"); |
124
|
|
|
} |
125
|
|
|
return 0; |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
|
129
|
|
|
|
130
|
|
|
/** |
131
|
|
|
* Drops all the tables mentioned in a single MYSQL query. Double-checks |
132
|
|
|
* each table name provided has a wpdb prefix attached, and that it exists. |
133
|
|
|
* Returns the list actually deleted |
134
|
|
|
* |
135
|
|
|
* @global WPDB $wpdb |
136
|
|
|
* @param array $table_names |
137
|
|
|
* @return array of table names which we deleted |
138
|
|
|
*/ |
139
|
|
|
public function dropTables($table_names) |
140
|
|
|
{ |
141
|
|
|
$tables_to_delete = array(); |
142
|
|
|
foreach ($table_names as $table_name) { |
143
|
|
|
$table_name = $this->getTableAnalysis()->ensureTableNameHasPrefix($table_name); |
144
|
|
|
if ($this->getTableAnalysis()->tableExists($table_name)) { |
145
|
|
|
$tables_to_delete[] = $table_name; |
146
|
|
|
} |
147
|
|
|
} |
148
|
|
|
if( ! empty( $tables_to_delete ) ) { |
149
|
|
|
global $wpdb; |
150
|
|
|
$wpdb->query('DROP TABLE ' . implode(', ', $tables_to_delete)); |
151
|
|
|
} |
152
|
|
|
return $tables_to_delete; |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
|
156
|
|
|
|
157
|
|
|
/** |
158
|
|
|
* Drops the specified index from the specified table. $table_name can |
159
|
|
|
* optionally start with $wpdb->prefix or not |
160
|
|
|
|
161
|
|
|
* |
162
|
|
|
*@global \wpdb $wpdb |
163
|
|
|
* @param string $table_name |
164
|
|
|
* @param string $index_name |
165
|
|
|
* @return int |
166
|
|
|
*/ |
167
|
|
|
public function dropIndex($table_name, $index_name) |
168
|
|
|
{ |
169
|
|
|
if (apply_filters('FHEE__EEH_Activation__drop_index__short_circuit', false)) { |
170
|
|
|
return false; |
|
|
|
|
171
|
|
|
} |
172
|
|
|
global $wpdb; |
173
|
|
|
$table_name = $this->getTableAnalysis()->ensureTableNameHasPrefix($table_name); |
174
|
|
|
$index_exists_query = "SHOW INDEX FROM {$table_name} WHERE key_name = '{$index_name}'"; |
175
|
|
|
if ( |
176
|
|
|
$this->getTableAnalysis()->tableExists($table_name) |
177
|
|
|
&& $wpdb->get_var($index_exists_query) |
178
|
|
|
=== $table_name //using get_var with the $index_exists_query returns the table's name |
179
|
|
|
) { |
180
|
|
|
return $wpdb->query("ALTER TABLE {$table_name} DROP INDEX {$index_name}"); |
181
|
|
|
} |
182
|
|
|
return 0; |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
|
186
|
|
|
|
187
|
|
|
/** |
188
|
|
|
* Just creates the requested table. $table_name can |
189
|
|
|
* optionally start with $wpdb->prefix or not |
190
|
|
|
|
191
|
|
|
* |
192
|
|
|
*@param string $table_name |
193
|
|
|
* @param string $create_sql defining the table's columns and indexes |
194
|
|
|
* @param string $engine (no need to specify "ENGINE=", that's implied) |
195
|
|
|
* @return void |
196
|
|
|
* @throws \EE_Error |
197
|
|
|
*/ |
198
|
|
|
public function createTable($table_name, $create_sql, $engine = 'MyISAM') |
199
|
|
|
{ |
200
|
|
|
// does $sql contain valid column information? ( LPT: https://regex101.com/ is great for working out regex patterns ) |
201
|
|
|
if (preg_match('((((.*?))(,\s))+)', $create_sql, $valid_column_data)) { |
202
|
|
|
$table_name = $this->getTableAnalysis()->ensureTableNameHasPrefix($table_name); |
203
|
|
|
/** @var \wpdb $wpdb */ |
204
|
|
|
global $wpdb; |
205
|
|
|
$SQL = "CREATE TABLE {$table_name} ( {$create_sql} ) ENGINE={$engine} " . $wpdb->get_charset_collate(); |
206
|
|
|
|
207
|
|
|
//get $wpdb to echo errors, but buffer them. This way at least WE know an error |
208
|
|
|
//happened. And then we can choose to tell the end user |
209
|
|
|
$old_show_errors_policy = $wpdb->show_errors(true); |
210
|
|
|
$old_error_suppression_policy = $wpdb->suppress_errors(false); |
211
|
|
|
ob_start(); |
212
|
|
|
dbDelta($SQL); |
213
|
|
|
$output = ob_get_contents(); |
214
|
|
|
ob_end_clean(); |
215
|
|
|
$wpdb->show_errors($old_show_errors_policy); |
216
|
|
|
$wpdb->suppress_errors($old_error_suppression_policy); |
217
|
|
|
if ( ! empty($output)) { |
218
|
|
|
throw new \EE_Error($output); |
219
|
|
|
} |
220
|
|
|
} else { |
221
|
|
|
throw new \EE_Error( |
222
|
|
|
sprintf( |
223
|
|
|
__('The following table creation SQL does not contain valid information about the table columns: %1$s %2$s', |
224
|
|
|
'event_espresso'), |
225
|
|
|
'<br />', |
226
|
|
|
$create_sql |
227
|
|
|
) |
228
|
|
|
); |
229
|
|
|
} |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
|
233
|
|
|
|
234
|
|
|
/** |
235
|
|
|
* Drops the specified index if it's size differs from $desired_index_size. |
236
|
|
|
* WordPress' dbdelta method doesn't automatically change index sizes, so this |
237
|
|
|
* method can be used to only drop the index if needed, and afterwards dbdelta can be used as normal. |
238
|
|
|
* |
239
|
|
|
*@param string $table_name |
240
|
|
|
* @param string $index_name |
241
|
|
|
* @param string $column_name if none is provided, we assume the column name matches the index (often true in EE) |
242
|
|
|
* @param string|int $desired_index_size defaults to 191, the max for utf8mb4. |
243
|
|
|
* See https://events.codebasehq.com/redirect?https://make.wordpress.org/core/2015/04/02/the-utf8mb4-upgrade/ |
244
|
|
|
* @return bool |
245
|
|
|
* @throws /EE_Error if table analysis object isn't defined |
246
|
|
|
*/ |
247
|
|
|
public function dropIndexIfSizeNot($table_name, $index_name, $column_name = null, $desired_index_size = 191) |
248
|
|
|
{ |
249
|
|
|
if($column_name === null){ |
250
|
|
|
$column_name = $index_name; |
251
|
|
|
} |
252
|
|
|
$index_entries = $this->getTableAnalysis()->showIndexes($table_name,$index_name); |
253
|
|
|
if(empty($index_entries)){ |
254
|
|
|
return false; |
255
|
|
|
} |
256
|
|
|
foreach($index_entries as $index_entry){ |
257
|
|
|
if( $column_name === $index_entry->Column_name |
258
|
|
|
&& (string)$desired_index_size !== $index_entry->Sub_part){ |
259
|
|
|
return $this->dropIndex($table_name,$index_name); |
260
|
|
|
} |
261
|
|
|
} |
262
|
|
|
return false; |
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
} |
266
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.