Completed
Branch BUG-10738-inconsistency-in-ses... (cda363)
by
unknown
13:38 queued 12s
created

DatetimeOffsetFix   A

Complexity

Total Complexity 28

Size/Duplication

Total Lines 305
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 0
Metric Value
dl 0
loc 305
rs 10
c 0
b 0
f 0
wmc 28
lcom 1
cbo 8

12 Methods

Rating   Name   Duplication   Size   Complexity  
A create_job() 0 10 1
A continue_job() 0 23 2
A cleanup_job() 0 10 1
C processModel() 0 43 8
B recordChangeLog() 0 48 5
A getModelsWithDatetimeFields() 0 19 4
A setModelsToProcess() 0 4 1
A updateCountOfModelsProcessed() 0 5 1
A getCountOfModelsProcessed() 0 4 1
A getModelsToProcess() 0 7 2
A updateOffset() 0 4 1
A getOffset() 0 4 1
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) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $fields_affected of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
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