1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace EventEspressoBatchRequest\JobHandlers; |
4
|
|
|
|
5
|
|
|
use EE_Model_Field_Base; |
6
|
|
|
use EE_Table_Base; |
7
|
|
|
use EventEspressoBatchRequest\JobHandlerBaseClasses\JobHandler; |
8
|
|
|
use EventEspressoBatchRequest\Helpers\BatchRequestException; |
9
|
|
|
use EventEspressoBatchRequest\Helpers\JobParameters; |
10
|
|
|
use EventEspressoBatchRequest\Helpers\JobStepResponse; |
11
|
|
|
use EE_Registry; |
12
|
|
|
use EE_Datetime_Field; |
13
|
|
|
use EEM_Base; |
14
|
|
|
use EE_Change_Log; |
15
|
|
|
|
16
|
|
|
defined('EVENT_ESPRESSO_VERSION') || exit('No direct access allowed.'); |
17
|
|
|
|
18
|
|
|
class DatetimeOffsetFix extends JobHandler |
19
|
|
|
{ |
20
|
|
|
|
21
|
|
|
/** |
22
|
|
|
* Key for the option used to track which models have been processed when doing the batches. |
23
|
|
|
*/ |
24
|
|
|
const MODELS_TO_PROCESS_OPTION_KEY = 'ee_models_processed_for_datetime_offset_fix'; |
25
|
|
|
|
26
|
|
|
|
27
|
|
|
const COUNT_OF_MODELS_PROCESSED = 'ee_count_of_ee_models_processed_for_datetime_offset_fixed'; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* Key for the option used to track what the current offset is that will be applied when this tool is executed. |
31
|
|
|
*/ |
32
|
|
|
const OFFSET_TO_APPLY_OPTION_KEY = 'ee_datetime_offset_fix_offset_to_apply'; |
33
|
|
|
|
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* String labelling the datetime offset fix type for change-log entries. |
37
|
|
|
*/ |
38
|
|
|
const DATETIME_OFFSET_FIX_CHANGELOG_TYPE = 'datetime_offset_fix'; |
39
|
|
|
|
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* String labelling a datetime offset fix error for change-log entries. |
43
|
|
|
*/ |
44
|
|
|
const DATETIME_OFFSET_FIX_CHANGELOG_ERROR_TYPE = 'datetime_offset_fix_error'; |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* @var EEM_Base[] |
48
|
|
|
*/ |
49
|
|
|
protected $models_with_datetime_fields = array(); |
50
|
|
|
|
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* Performs any necessary setup for starting the job. This is also a good |
54
|
|
|
* place to setup the $job_arguments which will be used for subsequent HTTP requests |
55
|
|
|
* when continue_job will be called |
56
|
|
|
* |
57
|
|
|
* @param JobParameters $job_parameters |
58
|
|
|
* @throws BatchRequestException |
59
|
|
|
* @return JobStepResponse |
60
|
|
|
*/ |
61
|
|
|
public function create_job(JobParameters $job_parameters) |
62
|
|
|
{ |
63
|
|
|
$models_with_datetime_fields = $this->getModelsWithDatetimeFields(); |
64
|
|
|
//we'll be doing each model as a batch. |
65
|
|
|
$job_parameters->set_job_size(count($models_with_datetime_fields)); |
66
|
|
|
return new JobStepResponse( |
67
|
|
|
$job_parameters, |
68
|
|
|
esc_html__('Starting Datetime Offset Fix', 'event_espresso') |
69
|
|
|
); |
70
|
|
|
} |
71
|
|
|
|
72
|
|
|
/** |
73
|
|
|
* Performs another step of the job |
74
|
|
|
* |
75
|
|
|
* @param JobParameters $job_parameters |
76
|
|
|
* @param int $batch_size |
77
|
|
|
* @return JobStepResponse |
78
|
|
|
* @throws \EE_Error |
79
|
|
|
*/ |
80
|
|
|
public function continue_job(JobParameters $job_parameters, $batch_size = 50) |
81
|
|
|
{ |
82
|
|
|
$models_to_process = $this->getModelsWithDatetimeFields(); |
83
|
|
|
//let's pop off the a model and do the query to apply the offset. |
84
|
|
|
$model_to_process = array_pop($models_to_process); |
85
|
|
|
//update our record |
86
|
|
|
$this->setModelsToProcess($models_to_process); |
87
|
|
|
$this->processModel($model_to_process); |
88
|
|
|
$this->updateCountOfModelsProcessed(); |
89
|
|
|
$job_parameters->set_units_processed($this->getCountOfModelsProcessed()); |
90
|
|
|
if (count($models_to_process) > 0) { |
91
|
|
|
$job_parameters->set_status(JobParameters::status_continue); |
92
|
|
|
} else { |
93
|
|
|
$job_parameters->set_status(JobParameters::status_complete); |
94
|
|
|
} |
95
|
|
|
return new JobStepResponse( |
96
|
|
|
$job_parameters, |
97
|
|
|
sprintf( |
98
|
|
|
esc_html__('Updated the offset for all datetime fields on the %s model.', 'event_espresso'), |
99
|
|
|
$model_to_process |
100
|
|
|
) |
101
|
|
|
); |
102
|
|
|
} |
103
|
|
|
|
104
|
|
|
/** |
105
|
|
|
* Performs any clean-up logic when we know the job is completed |
106
|
|
|
* |
107
|
|
|
* @param JobParameters $job_parameters |
108
|
|
|
* @return JobStepResponse |
109
|
|
|
* @throws BatchRequestException |
110
|
|
|
*/ |
111
|
|
|
public function cleanup_job(JobParameters $job_parameters) |
112
|
|
|
{ |
113
|
|
|
//delete important saved options. |
114
|
|
|
delete_option(self::MODELS_TO_PROCESS_OPTION_KEY); |
115
|
|
|
delete_option(self::COUNT_OF_MODELS_PROCESSED); |
116
|
|
|
return new JobStepResponse($job_parameters, esc_html__( |
117
|
|
|
'Offset has been applied to all affected fields.', |
118
|
|
|
'event_espresso' |
119
|
|
|
)); |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
|
123
|
|
|
/** |
124
|
|
|
* Contains the logic for processing a model and applying the datetime offset to affected fields on that model. |
125
|
|
|
* @param string $model_class_name |
126
|
|
|
* @throws \EE_Error |
127
|
|
|
*/ |
128
|
|
|
protected function processModel($model_class_name) |
129
|
|
|
{ |
130
|
|
|
global $wpdb; |
131
|
|
|
/** @var EEM_Base $model */ |
132
|
|
|
$model = $model_class_name::instance(); |
133
|
|
|
$original_offset = self::getOffset(); |
134
|
|
|
$sql_date_function = $original_offset > 0 ? 'DATE_ADD' : 'DATE_SUB'; |
135
|
|
|
$offset = abs($original_offset) * 60; |
136
|
|
|
//since some affected models might have two tables, we have to get our tables and set up a query for each table. |
137
|
|
|
foreach ($model->get_tables() as $table) { |
138
|
|
|
$query = 'UPDATE ' . $table->get_table_name(); |
139
|
|
|
$fields_affected = array(); |
140
|
|
|
$inner_query = array(); |
141
|
|
|
foreach ($model->_get_fields_for_table($table->get_table_alias()) as $model_field) { |
142
|
|
|
if ($model_field instanceof EE_Datetime_Field) { |
143
|
|
|
$inner_query[] = $model_field->get_table_column() . ' = ' |
144
|
|
|
. $sql_date_function . '(' |
145
|
|
|
. $model_field->get_table_column() |
146
|
|
|
. ", INTERVAL $offset MINUTE)"; |
147
|
|
|
$fields_affected[] = $model_field; |
148
|
|
|
} |
149
|
|
|
} |
150
|
|
|
if (! $fields_affected) { |
|
|
|
|
151
|
|
|
continue; |
152
|
|
|
} |
153
|
|
|
//k convert innerquery to string |
154
|
|
|
$query .= ' SET ' . implode(',', $inner_query); |
155
|
|
|
//execute query |
156
|
|
|
$result = $wpdb->query($query); |
157
|
|
|
//record log |
158
|
|
|
if ($result !== false) { |
159
|
|
|
$this->recordChangeLog($model, $original_offset, $table, $fields_affected); |
160
|
|
|
} else { |
161
|
|
|
//record error. |
162
|
|
|
$error_message = $wpdb->last_error; |
163
|
|
|
//handle the edgecases where last_error might be empty. |
164
|
|
|
if (! $error_message) { |
165
|
|
|
$error_message = esc_html__('Unknown mysql error occured.', 'event_espresso'); |
166
|
|
|
} |
167
|
|
|
$this->recordChangeLog($model, $original_offset, $table, $fields_affected, $error_message); |
168
|
|
|
} |
169
|
|
|
} |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
|
173
|
|
|
/** |
174
|
|
|
* Records a changelog entry using the given information. |
175
|
|
|
* |
176
|
|
|
* @param EEM_Base $model |
177
|
|
|
* @param float $offset |
178
|
|
|
* @param EE_Table_Base $table |
179
|
|
|
* @param EE_Model_Field_Base[] $model_fields_affected |
180
|
|
|
* @param string $error_message If present then there was an error so let's record that instead. |
181
|
|
|
* @throws \EE_Error |
182
|
|
|
*/ |
183
|
|
|
private function recordChangeLog( |
184
|
|
|
EEM_Base $model, |
185
|
|
|
$offset, |
186
|
|
|
EE_Table_Base $table, |
187
|
|
|
$model_fields_affected, |
188
|
|
|
$error_message = '' |
189
|
|
|
) { |
190
|
|
|
//setup $fields list. |
191
|
|
|
$fields = array(); |
192
|
|
|
/** @var EE_Datetime_Field $model_field */ |
193
|
|
|
foreach ($model_fields_affected as $model_field) { |
194
|
|
|
if (! $model_field instanceof EE_Datetime_Field) { |
195
|
|
|
continue; |
196
|
|
|
} |
197
|
|
|
$fields[] = $model_field->get_name(); |
198
|
|
|
} |
199
|
|
|
//setup the message for the changelog entry. |
200
|
|
|
$message = $error_message |
201
|
|
|
? sprintf( |
202
|
|
|
esc_html__( |
203
|
|
|
'The %1$s table for the %2$s model did not have the offset of %3$f applied to its fields (%4$s), because of the following error:%5$s', |
204
|
|
|
'event_espresso' |
205
|
|
|
), |
206
|
|
|
$table->get_table_name(), |
207
|
|
|
$model->get_this_model_name(), |
208
|
|
|
$offset, |
209
|
|
|
implode(',', $fields), |
210
|
|
|
$error_message |
211
|
|
|
) |
212
|
|
|
: sprintf( |
213
|
|
|
esc_html__( |
214
|
|
|
'The %1$s table for the %2$s model has had the offset of %3$f applied to its following fields: %4$s', |
215
|
|
|
'event_espresso' |
216
|
|
|
), |
217
|
|
|
$table->get_table_name(), |
218
|
|
|
$model->get_this_model_name(), |
219
|
|
|
$offset, |
220
|
|
|
implode(',', $fields) |
221
|
|
|
); |
222
|
|
|
//write to the log |
223
|
|
|
$changelog = EE_Change_Log::new_instance(array( |
224
|
|
|
'LOG_type' => $error_message |
225
|
|
|
? self::DATETIME_OFFSET_FIX_CHANGELOG_ERROR_TYPE |
226
|
|
|
: self::DATETIME_OFFSET_FIX_CHANGELOG_TYPE, |
227
|
|
|
'LOG_message' => $message |
228
|
|
|
)); |
229
|
|
|
$changelog->save(); |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
|
233
|
|
|
/** |
234
|
|
|
* Returns an array of models that have datetime fields. |
235
|
|
|
* This array is added to a short lived transient cache to keep having to build this list to a minimum. |
236
|
|
|
* @return array an array of model class names. |
237
|
|
|
*/ |
238
|
|
|
private function getModelsWithDatetimeFields() |
239
|
|
|
{ |
240
|
|
|
$this->getModelsToProcess(); |
241
|
|
|
if (! empty($this->models_with_datetime_fields)) { |
242
|
|
|
return $this->models_with_datetime_fields; |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
$all_non_abstract_models = EE_Registry::instance()->non_abstract_db_models; |
246
|
|
|
foreach ($all_non_abstract_models as $non_abstract_model) { |
247
|
|
|
//get model instance |
248
|
|
|
/** @var EEM_Base $non_abstract_model */ |
249
|
|
|
$non_abstract_model = $non_abstract_model::instance(); |
250
|
|
|
if ($non_abstract_model->get_a_field_of_type('EE_Datetime_Field') instanceof EE_Datetime_Field) { |
251
|
|
|
$this->models_with_datetime_fields[] = get_class($non_abstract_model); |
252
|
|
|
} |
253
|
|
|
} |
254
|
|
|
$this->setModelsToProcess($this->models_with_datetime_fields); |
255
|
|
|
return $this->models_with_datetime_fields; |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
|
259
|
|
|
/** |
260
|
|
|
* This simply records the models that have been processed with our tracking option. |
261
|
|
|
* @param array $models_to_set array of model class names. |
262
|
|
|
*/ |
263
|
|
|
private function setModelsToProcess($models_to_set) |
264
|
|
|
{ |
265
|
|
|
update_option(self::MODELS_TO_PROCESS_OPTION_KEY, $models_to_set); |
266
|
|
|
} |
267
|
|
|
|
268
|
|
|
|
269
|
|
|
/** |
270
|
|
|
* Used to keep track of how many models have been processed for the batch |
271
|
|
|
* @param $count |
272
|
|
|
*/ |
273
|
|
|
private function updateCountOfModelsProcessed($count = 1) |
274
|
|
|
{ |
275
|
|
|
$count = $this->getCountOfModelsProcessed() + (int) $count; |
276
|
|
|
update_option(self::COUNT_OF_MODELS_PROCESSED, $count); |
277
|
|
|
} |
278
|
|
|
|
279
|
|
|
|
280
|
|
|
/** |
281
|
|
|
* Retrieve the tracked number of models processed between requests. |
282
|
|
|
* @return int |
283
|
|
|
*/ |
284
|
|
|
private function getCountOfModelsProcessed() |
285
|
|
|
{ |
286
|
|
|
return (int) get_option(self::COUNT_OF_MODELS_PROCESSED, 0); |
287
|
|
|
} |
288
|
|
|
|
289
|
|
|
|
290
|
|
|
/** |
291
|
|
|
* Returns the models that are left to process. |
292
|
|
|
* @return array an array of model class names. |
293
|
|
|
*/ |
294
|
|
|
private function getModelsToProcess() |
295
|
|
|
{ |
296
|
|
|
if (empty($this->models_with_datetime_fields)) { |
297
|
|
|
$this->models_with_datetime_fields = get_option(self::MODELS_TO_PROCESS_OPTION_KEY, array()); |
298
|
|
|
} |
299
|
|
|
return $this->models_with_datetime_fields; |
300
|
|
|
} |
301
|
|
|
|
302
|
|
|
|
303
|
|
|
/** |
304
|
|
|
* Used to record the offset that will be applied to dates and times for EE_Datetime_Field columns. |
305
|
|
|
* @param float $offset |
306
|
|
|
*/ |
307
|
|
|
public static function updateOffset($offset) |
308
|
|
|
{ |
309
|
|
|
update_option(self::OFFSET_TO_APPLY_OPTION_KEY, $offset); |
310
|
|
|
} |
311
|
|
|
|
312
|
|
|
|
313
|
|
|
/** |
314
|
|
|
* Used to retrieve the saved offset that will be applied to dates and times for EE_Datetime_Field columns. |
315
|
|
|
* |
316
|
|
|
* @return float |
317
|
|
|
*/ |
318
|
|
|
public static function getOffset() |
319
|
|
|
{ |
320
|
|
|
return (float) get_option(self::OFFSET_TO_APPLY_OPTION_KEY, 0); |
321
|
|
|
} |
322
|
|
|
} |
323
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.