Completed
Push — beta ( 34edd0...92c756 )
by
unknown
07:59 queued 11s
created

Core::pre_post_update()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 2
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
/*
3
 *
4
 *
5
 * inspired by https://github.com/adamsilverstein/wp-post-meta-revisions/blob/master/wp-post-meta-revisions.php
6
 * many thx @adamsilverstein
7
 *
8
 */
9
10
namespace KMM\Timeshift;
11
12
class Core {
13
    private $plugin_dir;
14
    private $last_author = false;
15
    private $timeshift_cached_meta;
16
17 16
    public function __construct($i18n) {
18
        global $wpdb;
19 16
        $this->i18n = $i18n;
0 ignored issues
show
Bug introduced by
The property i18n does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
20 16
        $this->wpdb = $wpdb;
0 ignored issues
show
Bug introduced by
The property wpdb does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
21 16
        $this->plugin_dir = plugin_dir_url(__FILE__) . '../';
22 16
        $this->timeshift_posts_per_page = 5;
0 ignored issues
show
Bug introduced by
The property timeshift_posts_per_page does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
23 16
        $this->pagination_ajax_action = 'pagination_timeshift';
0 ignored issues
show
Bug introduced by
The property pagination_ajax_action does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
24 16
        $this->add_filters();
25 16
        $this->add_actions();
26 16
        $this->add_metabox();
27
        // Disable WP's own revision system
28 16
        remove_post_type_support('post', 'revisions');
29
    }
30
31 2
    public function hasTimeshifts($post_id) {
32 2
        $post_type = get_post_type($post_id);
33 2
        $table_name = $this->wpdb->prefix . 'timeshift_' . $post_type;
34 2
        $this->checkTable($post_type);
35 2
        $sql = "select count(1) as amount from $table_name where post_id=" . $post_id;
36 2
        $r = $this->wpdb->get_results($sql);
37
38 2
        if ($r && 1 == count($r)) {
39 1
            if (intval($r[0]->amount) > 0) {
40 1
                return true;
41
            }
42
        }
43
44 1
        return false;
45
    }
46
47 16
    public function timeshiftVisible() {
48 16
        $check = apply_filters('krn_timeshift_visible', true);
49
50 16
        return $check;
51
    }
52
53 16
    public function add_metabox() {
54 16
        $cl = $this;
55 16
        if (! $this->timeshiftVisible()) {
56
            return;
57
        }
58
        // Keep adding the metabox even if no timeshifts available, i.e. will render timeshift box with only live version
59 16
        if (! isset($_GET['post'])) {
60 16
            return;
61
        }
62
        add_action('add_meta_boxes', function () use ($cl) {
63
            add_meta_box('krn-timeshift', __('Timeshift', 'kmm-timeshift'), [$cl, 'timeshift_metabox'], null, 'normal', 'core');
64
        });
65
    }
66
67 1
    public function timeshift_metabox() {
68 1
        if (! isset($_GET['post'])) {
69 1
            return;
70
        }
71
        $prod_post = get_post($_GET['post']);
72
73
        $start = 0;
74
        $timeshift_page = 1;
75
        if (isset($_GET['timeshift_page'])) {
76
            $start = ($_GET['timeshift_page'] - 1) * $this->timeshift_posts_per_page;
77
            $timeshift_page = $_GET['timeshift_page'];
78
        }
79
80
        // pagination
81
        $pagination = $this->get_paginated_links($prod_post, $timeshift_page);
82
        echo $pagination;
83
84
        // load first few & render
85
        $rows = $this->get_next_rows($prod_post, $start);
86
        $timeshift_table = $this->render_metabox_table($prod_post, $rows);
87
        echo $timeshift_table;
88
89
        if (isset($_GET['action']) && $_GET['action'] == $this->pagination_ajax_action) {
90
            wp_die();
91
        }
92
    }
93
94 16
    public function add_filters() {
95
        // When revisioned post meta has changed, trigger a revision save.
96
        //add_filter('wp_save_post_revision_post_has_changed', [$this, '_wp_check_revisioned_meta_fields_have_changed'], 10, 3);
97
98 16
        add_filter('get_post_metadata', [$this, 'inject_metadata_timeshift'], 1, 4);
99 16
        add_filter('update_post_metadata', [$this, 'update_post_metadata'], 1, 5);
100
    }
101
102 4
    public function update_post_metadata($check, int $object_id, string $meta_key, $meta_value, $prev_value) {
0 ignored issues
show
Unused Code introduced by
The parameter $meta_value is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $prev_value is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
103 4
        if ('_edit_last' == $meta_key) {
104 4
            $lo = get_post_meta($object_id, '_edit_last', true);
105 4
            $this->last_author = $lo;
106
        }
107
108 4
        return null;
109
    }
110
111 5
    public function inject_metadata_timeshift($value, $post_id, $key, $single) {
0 ignored issues
show
Unused Code introduced by
The parameter $single is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
112 5
        if (! isset($_GET['timeshift'])) {
113 5
            return;
114
        }
115
        // Load timeshift
116
        if (! $this->timeshift_cached_meta) {
117
            $post_type = get_post_type($post_id);
118
            $table_name = $this->wpdb->prefix . 'timeshift_' . $post_type;
119
            $sql = "select * from $table_name where id=" . intval($_GET['timeshift']);
120
            $r = $this->wpdb->get_results($sql);
121 View Code Duplication
            if ($r && 1 == count($r)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
122
                $payload = unserialize($r[0]->post_payload);
123
                $this->timeshift_cached_meta = $payload->meta;
124
            }
125
        }
126
        // is the requested meta data in the stored snapshot
127
        if ($this->timeshift_cached_meta && isset($this->timeshift_cached_meta[$key])) {
128
            return $this->timeshift_cached_meta[$key];
129
        } else {
130
            // Otherwise return default value, like acf core fields.
131
            return $value;
132
        }
133
    }
134
135 2
    public function inject_timeshift($p) {
0 ignored issues
show
Unused Code introduced by
The parameter $p is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
136
        global $post;
137 2
        if (! isset($_GET['timeshift'])) {
138 1
            return;
139
        }
140
        // Load timeshift
141 1
        $table_name = $this->wpdb->prefix . 'timeshift_' . $post->post_type;
142 1
        $sql = "select * from $table_name where id=" . intval($_GET['timeshift']);
143 1
        $r = $this->wpdb->get_results($sql);
144 1 View Code Duplication
        if ($r && 1 == count($r)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
145
            $payload = unserialize($r[0]->post_payload);
146
            $post = $payload->post;
147
        }
148
    }
149
150 16
    public function add_actions() {
151 16
        add_action('edit_form_top', [$this, 'inject_timeshift'], 1, 1);
152 16
        add_action('pre_post_update', [$this, 'pre_post_update'], 2, 1);
153 16
        add_action('add_attachment', [$this, 'add_attachment'], 1, 1);
154 16
        add_action('admin_notices', [$this, 'admin_notice']);
155 16
        add_action('krn_timeshift_create_snapshot', [$this, 'create_snapshot'], 1, 2);
156 16
        add_action('admin_enqueue_scripts', [$this, 'enqueue_scripts']);
157 16
        add_action('wp_ajax_pagination_timeshift', [$this, 'timeshift_metabox']);
158
    }
159
160
    public function admin_notice() {
161
        if (isset($_GET['timeshift']) && $_GET['timeshift']) {
162
            echo '<div class="notice notice-warning is-dismissible">';
163
            echo '<p style="font-weight: 800; color: red">' . __('You are editing a historical version! if you save or publish, this will replace the current live one', 'kmm-timeshift') . '</p>';
164
            echo '</div>';
165
        }
166
    }
167
168
    public function enqueue_scripts() {
169
        wp_enqueue_script('krn-timeshift-pagination-ajax', plugin_dir_url(__FILE__) . '../assets/js/pagination-ajax.js', ['jquery']);
170
        wp_localize_script('krn-timeshift-pagination-ajax', 'krn_timeshift', [
171
            'action' => $this->pagination_ajax_action,
172
            'post' => isset($_GET['post']) ? $_GET['post'] : false,
173
            'timeshift' => isset($_GET['timeshift']) ? $_GET['timeshift'] : false,
174
        ]);
175
    }
176
177 6
    public function checkTable($postType) {
178 6
        $table_name = $this->wpdb->prefix . 'timeshift_' . $postType;
179
180 6
        $charset_collate = $this->wpdb->get_charset_collate();
181
182 6
        $sql = "CREATE TABLE IF NOT EXISTS $table_name (
183
                id int(12) NOT NULL AUTO_INCREMENT,
184
                post_id int(12) NOT NULL,
185
                create_date datetime default CURRENT_TIMESTAMP,
186
                post_payload LONGTEXT,
187
                PRIMARY KEY (id)
188 6
            ) $charset_collate;";
189
190 6
        require_once ABSPATH . 'wp-admin/includes/upgrade.php';
191 6
        $a = dbDelta($sql);
0 ignored issues
show
Unused Code introduced by
$a is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
192
193 6
        return true;
194
    }
195
196 1
    public function storeTimeshift($timeshift) {
197 1
        $table_name = $this->wpdb->prefix . 'timeshift_' . $timeshift->post->post_type;
198 1
        $sql = "insert into $table_name (post_id, post_payload) VALUES(%d, '%s')";
199 1
        $query = $this->wpdb->prepare($sql, $timeshift->post->ID, serialize($timeshift));
200 1
        $this->wpdb->query($query);
201
    }
202
203
    private function updateTimeshiftVersion($postID, $mdata): int {
204
        $verToSave = 0;
205
        if (is_array($mdata) && isset($mdata['_timeshift_version'][0]) &&
206
        is_numeric($mdata['_timeshift_version'][0])) {
207 3
            // Increment version for post's meta
208 3
            $verToSave = (int) $mdata['_timeshift_version'][0] + 1;
209 3
        }
210 3
        update_post_meta($postID, '_timeshift_version', $verToSave);
211
        if ($verToSave > 1) {
212
            // Previous version for when timeshift existed before
213 3
            return $verToSave - 1;
214 3
        } else {
215
            // When this is the first timeshift
216
            return 0;
217 3
        }
218
    }
219
220 3
    public function create_snapshot($postID, $editSource) {
221 3
        $this->krn_pre_post_update($postID, null, $editSource);
222
    }
223 3
224 3
    public function add_attachment($postID) {
225
        $post = get_post($postID);
226 3
        update_post_meta($postID, '_edit_last', $post->post_author);
227
        $this->krn_pre_post_update($postID, null, 'Backend', false);
228
    }
229
230 3
    public function pre_post_update(int $post_ID, array $data = null) {
231
        $this->krn_pre_post_update($post_ID, $data);
232 3
    }
233
234
    public function krn_pre_post_update(int $post_ID, array $data = null, $editSource = 'Backend', $recordTimeshift = true) {
235 3
        if (true == apply_filters('krn_timeshift_skip', false, $post_ID, $data, $editSource)) {
236 1
            return;
237 1
        }
238
        if (wp_is_post_autosave($post_ID)) {
239
            return;
240
        }
241
        if ('auto-draft' == get_post_status($post_ID)) {
242
            return;
243
        }
244
        $post_type = get_post_type($post_ID);
245
        $this->checkTable($post_type);
246
247
        // Get previous save initiator
248
        $prevSaveInit = get_post_meta($post_ID, 'save_initiator');
249
250
        // When executed by Cron
251
        if (defined('DOING_CRON')) {
252
            $editSource = 'Kron';
253
        }
254
255
        // Update live save initiator
256
        update_post_meta($post_ID, 'save_initiator', $editSource);
257
258
        $mdata = get_metadata('post', $post_ID);
259
        $post = get_post($post_ID);
260
261
        if ($this->last_author) {
262
            $mdata['_edit_last'][0] = $this->last_author;
263
        } else {
264
            // For unknown last author, clear it. It is a current user now
265
            $mdata['_edit_last'][0] = '';
266
            if ('Frontend' == $editSource) {
267
                $lo = get_post_meta($post_ID, '_edit_last', true);
268
                $mdata['_edit_last'][0] = $lo;
269
            }
270
        }
271
        unset($mdata['_edit_lock']);
272
273
        // Store timeshift version to post's meta
274
        $timeshiftVer = $this->updateTimeshiftVersion($post_ID, $mdata);
275
276
        // Don't save timeshift record when the article was just created
277
        if ('article' == $post->post_type && 'auto-draft' == $post->post_status) {
278
            $recordTimeshift = false;
279
        }
280
281
        if ($recordTimeshift) {
282
            $mdata['_timeshift_version'][0] = $timeshiftVer;
283
            $mdata['save_initiator'] = $prevSaveInit;
284
            $timeshift = (object) ['post' => $post, 'meta' => $mdata];
285
            $this->storeTimeshift($timeshift);
286
        }
287
    }
288
289
    public function get_paginated_links($prod_post, $paged = 1) {
290
        if (is_null($prod_post)) {
291
            return;
292
        }
293
294
        // count timeshift-versions
295
        $table_name = $this->wpdb->prefix . 'timeshift_' . $prod_post->post_type;
296
        $sql = "select  count(1) as cnt from $table_name where post_id=" . $prod_post->ID;
297
        $maxrows = $this->wpdb->get_results($sql);
298
        $allrows = (int) $maxrows[0]->{'cnt'};
299
300
        // max. number of pages
301
        $max_page = ceil($allrows / $this->timeshift_posts_per_page);
302
303
        // create pagination links
304
        $output = paginate_links([
305
            'current' => max(1, $paged),
306
            'total' => $max_page,
307
            'mid_size' => 1,
308
            'prev_text' => __('«'),
309
            'next_text' => __('»'),
310
        ]);
311
312
        return $output;
313
    }
314
315
    public function get_next_rows($prod_post, $start = 0) {
316
        if (! isset($prod_post)) {
317
            return;
318
        }
319
320
        $table_name = $this->wpdb->prefix . 'timeshift_' . $prod_post->post_type;
321
        $sql = "select  * from $table_name where post_id=" . $prod_post->ID . ' order by create_date desc limit ' . $start . ', ' . $this->timeshift_posts_per_page;
322
        $rows = $this->wpdb->get_results($sql);
323
324
        return $rows;
325
    }
326
327
    public function render_metabox_table($prod_post, $rows = []) {
328
        if (! isset($prod_post)) {
329
            return;
330
        }
331
332
        // get last editor
333
        $table_postmeta = $this->wpdb->prefix . 'postmeta';
334
        $sql_last_editor = 'select meta_value from ' . $table_postmeta . ' where post_id=' . $prod_post->ID . " AND meta_key='_edit_last'";
335
        $last_editor = $this->wpdb->get_var($sql_last_editor);
336
337
        // check save initiator
338
        if (get_post_meta($prod_post->ID, 'save_initiator')) {
339
            $save_initiator_live = get_post_meta($prod_post->ID, 'save_initiator')[0];
340
        } else {
341
            $save_initiator_live = __('unknown', 'kmm-timeshift');
342
        }
343
344
        $output = '<table class="widefat fixed">';
345
        $output .= '<thead>';
346
        $output .= '<tr>';
347
        $output .= '<th width=30></th>';
348
        $output .= '<th width="35%" id="columnname" class="manage-column column-columnname"  scope="col">' . __('Title', 'kmm-timeshift') . '</th>';
349
        $output .= '<th width="25%" id="columnname" class="manage-column column-columnname"  scope="col">' . __('Snapshot Date', 'kmm-timeshift') . '</th>';
350
        $output .= '<th width="10%" id="columnname" class="manage-column column-columnname"  scope="col">' . __('Author', 'kmm-timeshift') . '</th>';
351
        $output .= '<th width="10%" id="columnname" class="manage-column column-columnname"  scope="col">' . __('Save-initiator', 'kmm-timeshift') . '</th>';
352
        $output .= '<th width="10%" id="columnname" class="manage-column column-columnname"  scope="col">' . __('Actions', 'kmm-timeshift') . '</th>';
353
        $output .= '</tr>';
354
        $output .= '</thead>';
355
        $output .= '<tbody>';
356
357
        // live-version
358
        $output .= '<tr style="font-weight: 800;">';
359
        $output .= '<td>' . get_avatar($last_editor, 30) . '</td>';
360
        $output .= '<td>' . $prod_post->post_title . '</td>';
361
        $output .= '<td>' . $prod_post->post_modified . '</td>';
362
        $output .= '<td>' . get_the_author_meta('display_name', $last_editor) . '</td>';
363
        $output .= '<td>' . $save_initiator_live . '</td>';
364
        $output .= '<td><a href="post.php?post=' . $prod_post->ID . '&action=edit"><span class="dashicons dashicons-admin-site"></span></A></td>';
365
        $output .= '</tr>';
366
367
        foreach ($rows as $rev) {
368
            $timeshift = unserialize($rev->post_payload);
369
            $style = '';
370
371
            // highlight currently loaded version
372
            if (isset($_GET['timeshift']) && $_GET['timeshift'] == $rev->id) {
373
                $style = 'style="font-style:italic;background-color: lightblue;"';
374
            }
375
376
            // some images dont have _edit_last field
377
            if (! isset($timeshift->meta['_edit_last']) || is_null($timeshift->meta['_edit_last'])) {
378
                $timeshift->meta['_edit_last'] = 0;
379
            }
380
381
            // check save initiator
382
            if (array_key_exists('save_initiator', $timeshift->meta) && array_key_exists(0, $timeshift->meta['save_initiator'])) {
383
                $save_initiator_timeshift = $timeshift->meta['save_initiator'][0];
384
            } else {
385
                $save_initiator_timeshift = __('unknown', 'kmm-timeshift');
386
            }
387
388
            $output .= '<tr ' . $style . '>';
389
            $output .= '<td>' . get_avatar($timeshift->meta['_edit_last'][0], 30) . '</td>';
390
            $output .= '<td>' . $timeshift->post->post_title . '</td>';
391
            $output .= '<td>' . $timeshift->post->post_modified . '</td>';
392
            $output .= '<td>' . get_the_author_meta('display_name', $timeshift->meta['_edit_last'][0]) . '</td>';
393
            $output .= '<td>' . $save_initiator_timeshift . '</td>';
394
            $output .= '<td><a href="post.php?post=' . $prod_post->ID . '&action=edit&timeshift=' . $rev->id . '"><span class="dashicons dashicons-backup"></span></a></td>';
395
            $output .= '</tr>';
396
        }
397
398
        $output .= '</tbody>';
399
        $output .= '</table>';
400
401
        return $output;
402
    }
403
}
404