Completed
Push — beta ( d0c35f...fdc0e9 )
by
unknown
10:38
created

Core::inject_metadata_timeshift()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 24

Duplication

Lines 4
Ratio 16.67 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
cc 7
nc 7
nop 4
dl 4
loc 24
ccs 0
cts 21
cp 0
crap 56
rs 8.6026
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
{
14
    private $plugin_dir;
15
    private $last_author = false;
16
    private $timeshift_cached_meta;
17
18 11
    public function __construct($i18n)
19
    {
20
        global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
21 11
        $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...
22 11
        $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...
23 11
        $this->plugin_dir = plugin_dir_url(__FILE__) . '../';
24 11
        $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...
25 11
        $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...
26 11
        $this->add_filters();
27
        $this->add_actions();
28 11
        $this->add_metabox();
29
        // Disable WP's own revision system
30
        remove_post_type_support('post', 'revisions');
31 2
    }
32
33 2
    public function hasTimeshifts($post_id)
34 2
    {
35 2
        $post_type = get_post_type($post_id);
36 2
        $table_name = $this->wpdb->prefix . 'timeshift_' . $post_type;
37 2
        $this->checkTable($post_type);
38
        $sql = "select count(1) as amount from $table_name where post_id=" . $post_id;
39 2
        $r = $this->wpdb->get_results($sql);
40 1
41 1
        if ($r && count($r) == 1) {
42
            if (intval($r[0]->amount) > 0) {
43
                return true;
44
            }
45 1
        }
46
47
        return false;
48 11
    }
49
50 11
    public function timeshiftVisible()
51
    {
52 11
        $check = apply_filters('krn_timeshift_visible', true);
53
        return $check;
54
    }
55 11
56
    public function add_metabox()
57 11
    {
58 11
        $cl = $this;
59
        if (! $this->timeshiftVisible()) {
60
            return;
61 11
        }
62 11
        if (! isset($_GET['post']) || ! $this->hasTimeshifts($_GET['post'])) {
63
            return;
64
        }
65
        add_action('add_meta_boxes', function () use ($cl) {
66
            add_meta_box('krn-timeshift', __('Timeshift', 'kmm-timeshift'), [$cl, 'timeshift_metabox'], null, 'normal', 'core');
67
        });
68
    }
69 1
70
    public function timeshift_metabox()
71
    {
72 1
        if (! isset($_GET['post'])) {
73 1
            return;
74
        }
75
        $prod_post = get_post($_GET['post']);
76
77
        $start = 0;
78
        $timeshift_page = 1;
79
        if(isset($_GET['timeshift_page'])) {
80
            $start = ($_GET['timeshift_page'] - 1) * $this->timeshift_posts_per_page;
81
            $timeshift_page = $_GET['timeshift_page'];
82
        }
83
84
85
        // pagination
86
        $pagination = $this->get_paginated_links($prod_post, $timeshift_page);
87
        echo $pagination;
88
89
        // load first few & render
90
        $rows = $this->get_next_rows($prod_post, $start);
91
        $timeshift_table = $this->render_metabox_table($prod_post, $rows);
92
        echo $timeshift_table;
93
94
        if(isset($_GET['action']) && $_GET['action'] == $this->pagination_ajax_action) {
95
            wp_die();
96
        }
97
    }
98
99
    public function add_filters()
100
    {
101
        // When revisioned post meta has changed, trigger a revision save.
102
        //add_filter('wp_save_post_revision_post_has_changed', [$this, '_wp_check_revisioned_meta_fields_have_changed'], 10, 3);
0 ignored issues
show
Unused Code Comprehensibility introduced by
74% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
103
104
        add_filter('get_post_metadata', [$this, 'inject_metadata_timeshift'], 1, 4);
105
        add_filter('update_post_metadata', [$this, 'update_post_metadata'], 1, 5);
106
    }
107
108
    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...
109
    {
110
        if ($meta_key == '_edit_last') {
111
            $lo = get_post_meta($object_id, '_edit_last', true);
112
            $this->last_author = $lo;
113
        }
114
115
        return null;
116
    }
117
118
    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...
119
    {
120
        if (! isset($_GET['timeshift'])) {
121
            return;
122
        }
123
        // Load timeshift
124
        if (! $this->timeshift_cached_meta) {
125
            $post_type = get_post_type($post_id);
126
            $table_name = $this->wpdb->prefix . 'timeshift_' . $post_type;
127
            $sql = "select * from $table_name where id=" . intval($_GET['timeshift']);
128
            $r = $this->wpdb->get_results($sql);
129 View Code Duplication
            if ($r && count($r) == 1) {
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...
130
                $payload = unserialize($r[0]->post_payload);
131
                $this->timeshift_cached_meta = $payload->meta;
132
            }
133
        }
134
        // is the requested meta data in the stored snapshot
135
        if ($this->timeshift_cached_meta && isset($this->timeshift_cached_meta[$key])) {
136
            return $this->timeshift_cached_meta[$key];
137
        } else {
138
            // Otherwise return default value, like acf core fields.
139
            return $value;
140
        }
141
    }
142
143
    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...
144
    {
145
        global $post;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
146
        if (! isset($_GET['timeshift'])) {
147 11
            return;
148
        }
149
        // Load timeshift
150
        $table_name = $this->wpdb->prefix . 'timeshift_' . $post->post_type;
151
        $sql = "select * from $table_name where id=" . intval($_GET['timeshift']);
152 11
        $r = $this->wpdb->get_results($sql);
153 11 View Code Duplication
        if ($r && count($r) == 1) {
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...
154
            $payload = unserialize($r[0]->post_payload);
155
            $post = $payload->post;
156 1
        }
157
    }
158 1
159 1
    public function add_actions()
160 1
    {
161
        add_action('edit_form_top', [$this, 'inject_timeshift'], 1, 1);
162
        add_action('pre_post_update', [$this, 'pre_post_update'], 2, 1);
163 1
        add_action('admin_notices', [$this, 'admin_notice']);
164
        add_action('krn_timeshift_create_snapshot', [$this, 'create_snapshot'], 1, 1);
165
        add_action('admin_enqueue_scripts', [$this, 'enqueue_scripts']);
166 2
        add_action('wp_ajax_pagination_timeshift', [$this, 'timeshift_metabox']);
167
    }
168 2
169 2
    public function admin_notice()
170
    {
171
        if (isset($_GET['timeshift']) && $_GET['timeshift']) {
172
            echo '<div class="notice notice-warning is-dismissible">';
173
            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>';
174
            echo '</div>';
175
        }
176
    }
177
178
    public function enqueue_scripts()
179
    {
180
        wp_enqueue_script('krn-timeshift-pagination-ajax', plugin_dir_url( __FILE__ ) . '../assets/js/pagination-ajax.js', ['jquery']);
181
        wp_localize_script('krn-timeshift-pagination-ajax', 'krn_timeshift', array(
182
            'action' => $this->pagination_ajax_action,
183
            'post' => isset($_GET['post']) ? $_GET['post'] : false,
184
            'timeshift' => isset($_GET['timeshift']) ? $_GET['timeshift'] : false
185
        ));
186
    }
187
188
    public function checkTable($postType)
189
    {
190
        $table_name = $this->wpdb->prefix . 'timeshift_' . $postType;
191 2
192
        $charset_collate = $this->wpdb->get_charset_collate();
193
194 2
        $sql = "CREATE TABLE IF NOT EXISTS $table_name (
195 1
                id int(12) NOT NULL AUTO_INCREMENT,
196
                post_id int(12) NOT NULL,
197
                create_date datetime default CURRENT_TIMESTAMP,
198 1
                post_payload TEXT,
199 1
                PRIMARY KEY (id)
200 1
            ) $charset_collate;";
201 1
202
        require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
203
        $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...
204
205
        return true;
206
    }
207 11
208
    public function storeTimeshift($timeshift)
209 11
    {
210 11
        $table_name = $this->wpdb->prefix . 'timeshift_' . $timeshift->post->post_type;
211 11
        $sql = "insert into $table_name (post_id, post_payload) VALUES(%d, '%s')";
212
        $query = $this->wpdb->prepare($sql, $timeshift->post->ID, serialize($timeshift));
213
        $this->wpdb->query($query);
214
    }
215
216
    public function create_snapshot($postID)
217 11
    {
218 11
        $this->pre_post_update($postID);
219
    }
220
221 4
    public function pre_post_update(int $post_ID, array $data = null)
0 ignored issues
show
Unused Code introduced by
The parameter $data 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...
222
    {
223 4
        if (wp_is_post_autosave($post_ID)) {
224
            return;
225 4
        }
226
        if (get_post_status($post_ID) == 'auto-draft') {
227 4
            return;
228
        }
229
        $post_type = get_post_type($post_ID);
230
        $this->checkTable($post_type);
231
232
        $mdata = get_metadata('post', $post_ID);
233 4
        $post = get_post($post_ID);
234
235 4
        if ($this->last_author) {
236 4
            $mdata['_edit_last'][0] = $this->last_author;
237
        }
238 4
        unset($mdata['_edit_lock']);
239
240
        $timeshift = (object) ['post' => $post, 'meta' => $mdata];
241 2
        $this->storeTimeshift($timeshift);
242
    }
243 2
244 2
    public function get_paginated_links($prod_post, $paged = 1)
245 2
    {
246 2
        if (is_null($prod_post)) {
247
            return;
248
        }
249
250
        // count timeshift-versions
251
        $table_name = $this->wpdb->prefix . 'timeshift_' . $prod_post->post_type;
252
        $sql = "select  count(1) as cnt from $table_name where post_id=" . $prod_post->ID;
253
        $maxrows = $this->wpdb->get_results($sql);
254 1
        $allrows = (int)$maxrows[0]->{'cnt'};
255
256 1
        // max. number of pages
257
        $max_page = ceil($allrows / $this->timeshift_posts_per_page);
258
259 1
        // create pagination links
260
        $output = paginate_links([
261
            'current' => max(1, $paged),
262 1
            'total' => $max_page,
263 1
            'mid_size' => 1,
264
            'prev_text' => __('«'),
265 1
            'next_text' => __('»'),
266 1
        ]);
267
268 1
        return $output;
269
    }
270
271 1
    public function get_next_rows($prod_post, $start = 0)
272
    {
273 1
        if (! isset($prod_post)) {
274 1
            return;
275
        }
276
277
        $table_name = $this->wpdb->prefix . 'timeshift_' . $prod_post->post_type;
278
        $sql = "select  * from $table_name where post_id=" . $prod_post->ID . ' order by create_date desc limit ' . $start . ', ' . $this->timeshift_posts_per_page;
279
        $rows = $this->wpdb->get_results($sql);
280
281
        return $rows;
282
    }
283
284
    public function render_metabox_table($prod_post, $rows = [])
285
    {
286
        if (! isset($prod_post)) {
287
            return;
288
        }
289
290
        // get last editor
291
        $table_postmeta = $this->wpdb->prefix . 'postmeta';
292
        $sql_last_editor = 'select meta_value from ' . $table_postmeta . ' where post_id=' . $prod_post->ID . " AND meta_key='_edit_last'";
293
        $last_editor = $this->wpdb->get_var($sql_last_editor);
294
295
        $output =  '<table class="widefat fixed">';
296
        $output .= '<thead>';
297
        $output .= '<tr>';
298
        $output .= '<th width=30></th>';
299
        $output .= '<th width="40%" id="columnname" class="manage-column column-columnname"  scope="col">' . __('Title', 'kmm-timeshift') . '</th>';
300
        $output .= '<th width="30%" id="columnname" class="manage-column column-columnname"  scope="col">' . __('Snapshot Date', 'kmm-timeshift') . '</th>';
301
        $output .= '<th width="10%" id="columnname" class="manage-column column-columnname"  scope="col">' . __('Author', 'kmm-timeshift') . '</th>';
302
        $output .= '<th width="10%" id="columnname" class="manage-column column-columnname"  scope="col">' . __('Actions', 'kmm-timeshift') . '</th>';
303
        $output .= '</tr>';
304
        $output .= '</thead>';
305
        $output .= '<tbody>';
306
307
        // live-version
308
        $output .= '<tr style="font-weight: 800;">';
309
        $output .= '<td>' . get_avatar($last_editor, 30) . '</td>';
310
        $output .= '<td>' . $prod_post->post_title . '</td>';
311
        $output .= '<td>' . $prod_post->post_modified . '</td>';
312
        $output .= '<td>' . get_the_author_meta('display_name', $last_editor) . '</td>';
313
        $output .= '<td><a href="post.php?post=' . $_GET['post'] . '&action=edit"><span class="dashicons dashicons-admin-site"></span></A></td>';
314
        $output .= '</tr>';
315
316
        foreach ($rows as $rev) {
317
            $timeshift = unserialize($rev->post_payload);
318
            $style = '';
319
320
            // highlight currently loaded version
321
            if (isset($_GET['timeshift']) && $_GET['timeshift'] == $rev->id) {
322
                $style = 'style="font-style:italic;background-color: lightblue;"';
323
            }
324
325
            // some images dont have _edit_last field
326
            if (! isset($timeshift->meta['_edit_last']) || is_null($timeshift->meta['_edit_last'])) {
327
                $timeshift->meta['_edit_last'] = 0;
328
            }
329
330
            $output .=  '<tr ' . $style . '>';
331
            $output .=  '<td>' . get_avatar($timeshift->meta['_edit_last'][0], 30) . '</td>';
332
            $output .=  '<td>' . $timeshift->post->post_title . '</td>';
333
            $output .=  '<td>' . $timeshift->post->post_modified . '</td>';
334
            $output .=  '<td>' . get_the_author_meta('display_name', $timeshift->meta['_edit_last'][0]) . '</td>';
335
            $output .=  '<td><a href="post.php?post=' . $_GET['post'] . "&action=edit&timeshift=" . $rev->id . '"><span class="dashicons dashicons-backup"></span></a></td>';
336
            $output .=  '</tr>';
337
        }
338
339
        $output .=  '</tbody>';
340
        $output .=  '</table>';
341
342
        return $output;
343
    }
344
}
345