Completed
Branch BUG-10626-dst-unit-test (0025a0)
by
unknown
105:00 queued 94:16
created

DatetimeOffsetFix::processModel()   B

Complexity

Conditions 6
Paths 14

Size

Total Lines 33
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 22
nc 14
nop 1
dl 0
loc 33
rs 8.439
c 0
b 0
f 0
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
     * Key for the option used to track what the current offset is that will be applied when this tool is executed.
28
     */
29
    const OFFSET_TO_APPLY_OPTION_KEY = 'ee_datetime_offset_fix_offset_to_apply';
30
31
32
    /**
33
     * String labelling the datetime offset fix type for changelog entries.
34
     */
35
    const DATETIME_OFFSET_FIX_CHANGELOG_TYPE = 'datetime_offset_fix';
36
37
    /**
38
     * @var EEM_Base[]
39
     */
40
    protected $models_with_datetime_fields = array();
41
42
43
    /**
44
     * Performs any necessary setup for starting the job. This is also a good
45
     * place to setup the $job_arguments which will be used for subsequent HTTP requests
46
     * when continue_job will be called
47
     *
48
     * @param JobParameters $job_parameters
49
     * @throws BatchRequestException
50
     * @return JobStepResponse
51
     */
52
    public function create_job(JobParameters $job_parameters)
53
    {
54
        $models_with_datetime_fields = $this->getModelsWithDatetimeFields();
55
        //we'll be doing each model as a batch.
56
        $job_parameters->set_job_size(count($models_with_datetime_fields));
57
        return new JobStepResponse(
58
            $job_parameters,
59
            esc_html__('Starting Datetime Offset Fix', 'event_espresso')
60
        );
61
    }
62
63
    /**
64
     * Performs another step of the job
65
     *
66
     * @param JobParameters $job_parameters
67
     * @param int           $batch_size
68
     * @return JobStepResponse
69
     * @throws \EE_Error
70
     */
71
    public function continue_job(JobParameters $job_parameters, $batch_size = 50)
72
    {
73
        $models_to_process = $this->getModelsWithDatetimeFields();
74
        //let's pop off the a model and do the query to apply the offset.
75
        $model_to_process = array_pop($models_to_process);
76
        //update our record
77
        $this->setModelsToProcess($models_to_process);
78
        $this->processModel($model_to_process);
79
        $job_parameters->set_units_processed(1);
80
        if (count($models_to_process) > 0) {
81
            $job_parameters->set_status(JobParameters::status_continue);
82
        } else {
83
            $job_parameters->set_status(JobParameters::status_complete);
84
        }
85
        return new JobStepResponse(
86
            $job_parameters,
87
            sprintf(
88
                esc_html__('Updated the offset for all datetime fields on the %s model.', 'event_espresso'),
89
                $model_to_process
90
            )
91
        );
92
    }
93
94
    /**
95
     * Performs any clean-up logic when we know the job is completed
96
     *
97
     * @param JobParameters $job_parameters
98
     * @return JobStepResponse
99
     * @throws BatchRequestException
100
     */
101
    public function cleanup_job(JobParameters $job_parameters)
102
    {
103
        //delete important saved options.
104
        delete_option(self::MODELS_TO_PROCESS_OPTION_KEY);
105
        return new JobStepResponse($job_parameters, esc_html__(
106
            'Offset has been applied to all affected fields.',
107
            'event_espresso'
108
        ));
109
    }
110
111
112
    /**
113
     * Contains the logic for processing a model and applying the datetime offset to affected fields on that model.
114
     * @param string $model_class_name
115
     * @throws \EE_Error
116
     */
117
    protected function processModel($model_class_name)
118
    {
119
        global $wpdb;
120
        /** @var EEM_Base $model */
121
        $model = $model_class_name::instance();
122
        $original_offset = self::getOffset();
123
        $sql_date_function = $original_offset > 0 ? 'DATE_ADD' : 'DATE_SUB';
124
        $offset = abs($original_offset);
125
        //since some affected models might have two tables, we have to get our tables and set up a query for each table.
126
        foreach ($model->get_tables() as $table) {
127
            $query = 'UPDATE ' . $table->get_table_name();
128
            $fields_affected = array();
129
            $inner_query = array();
130
            foreach ($model->_get_fields_for_table($table->get_table_alias()) as $model_field) {
131
                if ($model_field instanceof EE_Datetime_Field) {
132
                    $inner_query[] = $model_field->get_table_column() . ' = '
133
                                     . $sql_date_function . '('
134
                                     . $model_field->get_table_column()
135
                                     . ", INTERVAL $offset HOUR)";
136
                    $fields_affected[] = $model_field;
137
                }
138
            }
139
            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...
140
                continue;
141
            }
142
            //k convert innerquery to string
143
            $query .= ' SET ' . implode(',', $inner_query);
144
            //execute query
145
            $wpdb->query($query);
146
            //record log
147
            $this->recordChangeLog($model, $original_offset, $table, $fields_affected);
148
        }
149
    }
150
151
152
    /**
153
     * Records a changelog entry using the given information.
154
     * @param EEM_Base              $model
155
     * @param float                 $offset
156
     * @param EE_Table_Base         $table
157
     * @param EE_Model_Field_Base[] $model_fields_affected
158
     * @throws \EE_Error
159
     */
160
    private function recordChangeLog(EEM_Base $model, $offset, EE_Table_Base $table, $model_fields_affected)
161
    {
162
        //setup $fields list.
163
        $fields = array();
164
        /** @var EE_Datetime_Field $model_field */
165
        foreach ($model_fields_affected as $model_field) {
166
            if (! $model_field instanceof EE_Datetime_Field) {
167
                continue;
168
            }
169
            $fields[] = $model_field->get_name();
170
        }
171
        //setup the message for the changelog entry.
172
        $message = sprintf(
173
            esc_html__(
174
                'The %1$s table for the %2$s model has had the offset of %3$f applied to its following fields: %4$s',
175
                'event_espresso'
176
            ),
177
            $table->get_table_name(),
178
            $model->get_this_model_name(),
179
            $offset,
180
            implode(',', $fields)
181
        );
182
        //write to the log
183
        $changelog = EE_Change_Log::new_instance(array(
184
            'LOG_type' => self::DATETIME_OFFSET_FIX_CHANGELOG_TYPE,
185
            'LOG_message' => $message
186
        ));
187
        $changelog->save();
188
    }
189
190
191
    /**
192
     * Returns an array of models that have datetime fields.
193
     * This array is added to a short lived transient cache to keep having to build this list to a minimum.
194
     * @return array  an array of model class names.
195
     */
196
    private function getModelsWithDatetimeFields()
197
    {
198
        $this->getModelsToProcess();
199
        if (! empty($this->models_with_datetime_fields)) {
200
            return $this->models_with_datetime_fields;
201
        }
202
203
        $all_non_abstract_models = EE_Registry::instance()->non_abstract_db_models;
204
        foreach ($all_non_abstract_models as $non_abstract_model) {
205
            //get model instance
206
            /** @var EEM_Base $non_abstract_model */
207
            $non_abstract_model = $non_abstract_model::instance();
208
            if ($non_abstract_model->get_a_field_of_type('EE_Datetime_Field') instanceof EE_Datetime_Field) {
209
                $this->models_with_datetime_fields[] = get_class($non_abstract_model);
210
            }
211
        }
212
        $this->setModelsToProcess($this->models_with_datetime_fields);
213
        return $this->models_with_datetime_fields;
214
    }
215
216
217
    /**
218
     * This simply records the models that have been processed with our tracking option.
219
     * @param array $models_to_set  array of model class names.
220
     */
221
    private function setModelsToProcess($models_to_set)
222
    {
223
        update_option(self::MODELS_TO_PROCESS_OPTION_KEY, $models_to_set);
224
    }
225
226
227
    /**
228
     * Returns the models that are left to process.
229
     * @return array  an array of model class names.
230
     */
231
    private function getModelsToProcess()
232
    {
233
        if (empty($this->models_with_datetime_fields)) {
234
            $this->models_with_datetime_fields = get_option(self::MODELS_TO_PROCESS_OPTION_KEY, array());
235
        }
236
        return $this->models_with_datetime_fields;
237
    }
238
239
240
    /**
241
     * Used to record the offset that will be applied to dates and times for EE_Datetime_Field columns.
242
     * @param float $offset
243
     */
244
    public static function updateOffset($offset)
245
    {
246
        update_option(self::OFFSET_TO_APPLY_OPTION_KEY, $offset);
247
    }
248
249
250
    /**
251
     * Used to retrieve the saved offset that will be applied to dates and times for EE_Datetime_Field columns.
252
     *
253
     * @return float
254
     */
255
    public static function getOffset()
256
    {
257
        return (float) get_option(self::OFFSET_TO_APPLY_OPTION_KEY, 0);
258
    }
259
}
260