WP Revisions Control - Version 1.3

Version Description

  • Add bulk actions to purge excess or all revisions.
  • Introduce unit tests.
  • Conform to coding standards.
Download this release

Release Info

Developer ethitter
Plugin Icon wp plugin WP Revisions Control
Version 1.3
Comparing to
See all releases

Code changes from version 1.2.1 to 1.3

LICENSE ADDED
@@ -0,0 +1,280 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ GNU GENERAL PUBLIC LICENSE
2
+ Version 2, June 1991
3
+
4
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
5
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6
+ Everyone is permitted to copy and distribute verbatim copies
7
+ of this license document, but changing it is not allowed.
8
+
9
+ Preamble
10
+
11
+ The licenses for most software are designed to take away your
12
+ freedom to share and change it. By contrast, the GNU General Public
13
+ License is intended to guarantee your freedom to share and change free
14
+ software--to make sure the software is free for all its users. This
15
+ General Public License applies to most of the Free Software
16
+ Foundation's software and to any other program whose authors commit to
17
+ using it. (Some other Free Software Foundation software is covered by
18
+ the GNU Lesser General Public License instead.) You can apply it to
19
+ your programs, too.
20
+
21
+ When we speak of free software, we are referring to freedom, not
22
+ price. Our General Public Licenses are designed to make sure that you
23
+ have the freedom to distribute copies of free software (and charge for
24
+ this service if you wish), that you receive source code or can get it
25
+ if you want it, that you can change the software or use pieces of it
26
+ in new free programs; and that you know you can do these things.
27
+
28
+ To protect your rights, we need to make restrictions that forbid
29
+ anyone to deny you these rights or to ask you to surrender the rights.
30
+ These restrictions translate to certain responsibilities for you if you
31
+ distribute copies of the software, or if you modify it.
32
+
33
+ For example, if you distribute copies of such a program, whether
34
+ gratis or for a fee, you must give the recipients all the rights that
35
+ you have. You must make sure that they, too, receive or can get the
36
+ source code. And you must show them these terms so they know their
37
+ rights.
38
+
39
+ We protect your rights with two steps: (1) copyright the software, and
40
+ (2) offer you this license which gives you legal permission to copy,
41
+ distribute and/or modify the software.
42
+
43
+ Also, for each author's protection and ours, we want to make certain
44
+ that everyone understands that there is no warranty for this free
45
+ software. If the software is modified by someone else and passed on, we
46
+ want its recipients to know that what they have is not the original, so
47
+ that any problems introduced by others will not reflect on the original
48
+ authors' reputations.
49
+
50
+ Finally, any free program is threatened constantly by software
51
+ patents. We wish to avoid the danger that redistributors of a free
52
+ program will individually obtain patent licenses, in effect making the
53
+ program proprietary. To prevent this, we have made it clear that any
54
+ patent must be licensed for everyone's free use or not licensed at all.
55
+
56
+ The precise terms and conditions for copying, distribution and
57
+ modification follow.
58
+
59
+ GNU GENERAL PUBLIC LICENSE
60
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61
+
62
+ 0. This License applies to any program or other work which contains
63
+ a notice placed by the copyright holder saying it may be distributed
64
+ under the terms of this General Public License. The "Program", below,
65
+ refers to any such program or work, and a "work based on the Program"
66
+ means either the Program or any derivative work under copyright law:
67
+ that is to say, a work containing the Program or a portion of it,
68
+ either verbatim or with modifications and/or translated into another
69
+ language. (Hereinafter, translation is included without limitation in
70
+ the term "modification".) Each licensee is addressed as "you".
71
+
72
+ Activities other than copying, distribution and modification are not
73
+ covered by this License; they are outside its scope. The act of
74
+ running the Program is not restricted, and the output from the Program
75
+ is covered only if its contents constitute a work based on the
76
+ Program (independent of having been made by running the Program).
77
+ Whether that is true depends on what the Program does.
78
+
79
+ 1. You may copy and distribute verbatim copies of the Program's
80
+ source code as you receive it, in any medium, provided that you
81
+ conspicuously and appropriately publish on each copy an appropriate
82
+ copyright notice and disclaimer of warranty; keep intact all the
83
+ notices that refer to this License and to the absence of any warranty;
84
+ and give any other recipients of the Program a copy of this License
85
+ along with the Program.
86
+
87
+ You may charge a fee for the physical act of transferring a copy, and
88
+ you may at your option offer warranty protection in exchange for a fee.
89
+
90
+ 2. You may modify your copy or copies of the Program or any portion
91
+ of it, thus forming a work based on the Program, and copy and
92
+ distribute such modifications or work under the terms of Section 1
93
+ above, provided that you also meet all of these conditions:
94
+
95
+ a) You must cause the modified files to carry prominent notices
96
+ stating that you changed the files and the date of any change.
97
+
98
+ b) You must cause any work that you distribute or publish, that in
99
+ whole or in part contains or is derived from the Program or any
100
+ part thereof, to be licensed as a whole at no charge to all third
101
+ parties under the terms of this License.
102
+
103
+ c) If the modified program normally reads commands interactively
104
+ when run, you must cause it, when started running for such
105
+ interactive use in the most ordinary way, to print or display an
106
+ announcement including an appropriate copyright notice and a
107
+ notice that there is no warranty (or else, saying that you provide
108
+ a warranty) and that users may redistribute the program under
109
+ these conditions, and telling the user how to view a copy of this
110
+ License. (Exception: if the Program itself is interactive but
111
+ does not normally print such an announcement, your work based on
112
+ the Program is not required to print an announcement.)
113
+
114
+ These requirements apply to the modified work as a whole. If
115
+ identifiable sections of that work are not derived from the Program,
116
+ and can be reasonably considered independent and separate works in
117
+ themselves, then this License, and its terms, do not apply to those
118
+ sections when you distribute them as separate works. But when you
119
+ distribute the same sections as part of a whole which is a work based
120
+ on the Program, the distribution of the whole must be on the terms of
121
+ this License, whose permissions for other licensees extend to the
122
+ entire whole, and thus to each and every part regardless of who wrote it.
123
+
124
+ Thus, it is not the intent of this section to claim rights or contest
125
+ your rights to work written entirely by you; rather, the intent is to
126
+ exercise the right to control the distribution of derivative or
127
+ collective works based on the Program.
128
+
129
+ In addition, mere aggregation of another work not based on the Program
130
+ with the Program (or with a work based on the Program) on a volume of
131
+ a storage or distribution medium does not bring the other work under
132
+ the scope of this License.
133
+
134
+ 3. You may copy and distribute the Program (or a work based on it,
135
+ under Section 2) in object code or executable form under the terms of
136
+ Sections 1 and 2 above provided that you also do one of the following:
137
+
138
+ a) Accompany it with the complete corresponding machine-readable
139
+ source code, which must be distributed under the terms of Sections
140
+ 1 and 2 above on a medium customarily used for software interchange; or,
141
+
142
+ b) Accompany it with a written offer, valid for at least three
143
+ years, to give any third party, for a charge no more than your
144
+ cost of physically performing source distribution, a complete
145
+ machine-readable copy of the corresponding source code, to be
146
+ distributed under the terms of Sections 1 and 2 above on a medium
147
+ customarily used for software interchange; or,
148
+
149
+ c) Accompany it with the information you received as to the offer
150
+ to distribute corresponding source code. (This alternative is
151
+ allowed only for noncommercial distribution and only if you
152
+ received the program in object code or executable form with such
153
+ an offer, in accord with Subsection b above.)
154
+
155
+ The source code for a work means the preferred form of the work for
156
+ making modifications to it. For an executable work, complete source
157
+ code means all the source code for all modules it contains, plus any
158
+ associated interface definition files, plus the scripts used to
159
+ control compilation and installation of the executable. However, as a
160
+ special exception, the source code distributed need not include
161
+ anything that is normally distributed (in either source or binary
162
+ form) with the major components (compiler, kernel, and so on) of the
163
+ operating system on which the executable runs, unless that component
164
+ itself accompanies the executable.
165
+
166
+ If distribution of executable or object code is made by offering
167
+ access to copy from a designated place, then offering equivalent
168
+ access to copy the source code from the same place counts as
169
+ distribution of the source code, even though third parties are not
170
+ compelled to copy the source along with the object code.
171
+
172
+ 4. You may not copy, modify, sublicense, or distribute the Program
173
+ except as expressly provided under this License. Any attempt
174
+ otherwise to copy, modify, sublicense or distribute the Program is
175
+ void, and will automatically terminate your rights under this License.
176
+ However, parties who have received copies, or rights, from you under
177
+ this License will not have their licenses terminated so long as such
178
+ parties remain in full compliance.
179
+
180
+ 5. You are not required to accept this License, since you have not
181
+ signed it. However, nothing else grants you permission to modify or
182
+ distribute the Program or its derivative works. These actions are
183
+ prohibited by law if you do not accept this License. Therefore, by
184
+ modifying or distributing the Program (or any work based on the
185
+ Program), you indicate your acceptance of this License to do so, and
186
+ all its terms and conditions for copying, distributing or modifying
187
+ the Program or works based on it.
188
+
189
+ 6. Each time you redistribute the Program (or any work based on the
190
+ Program), the recipient automatically receives a license from the
191
+ original licensor to copy, distribute or modify the Program subject to
192
+ these terms and conditions. You may not impose any further
193
+ restrictions on the recipients' exercise of the rights granted herein.
194
+ You are not responsible for enforcing compliance by third parties to
195
+ this License.
196
+
197
+ 7. If, as a consequence of a court judgment or allegation of patent
198
+ infringement or for any other reason (not limited to patent issues),
199
+ conditions are imposed on you (whether by court order, agreement or
200
+ otherwise) that contradict the conditions of this License, they do not
201
+ excuse you from the conditions of this License. If you cannot
202
+ distribute so as to satisfy simultaneously your obligations under this
203
+ License and any other pertinent obligations, then as a consequence you
204
+ may not distribute the Program at all. For example, if a patent
205
+ license would not permit royalty-free redistribution of the Program by
206
+ all those who receive copies directly or indirectly through you, then
207
+ the only way you could satisfy both it and this License would be to
208
+ refrain entirely from distribution of the Program.
209
+
210
+ If any portion of this section is held invalid or unenforceable under
211
+ any particular circumstance, the balance of the section is intended to
212
+ apply and the section as a whole is intended to apply in other
213
+ circumstances.
214
+
215
+ It is not the purpose of this section to induce you to infringe any
216
+ patents or other property right claims or to contest validity of any
217
+ such claims; this section has the sole purpose of protecting the
218
+ integrity of the free software distribution system, which is
219
+ implemented by public license practices. Many people have made
220
+ generous contributions to the wide range of software distributed
221
+ through that system in reliance on consistent application of that
222
+ system; it is up to the author/donor to decide if he or she is willing
223
+ to distribute software through any other system and a licensee cannot
224
+ impose that choice.
225
+
226
+ This section is intended to make thoroughly clear what is believed to
227
+ be a consequence of the rest of this License.
228
+
229
+ 8. If the distribution and/or use of the Program is restricted in
230
+ certain countries either by patents or by copyrighted interfaces, the
231
+ original copyright holder who places the Program under this License
232
+ may add an explicit geographical distribution limitation excluding
233
+ those countries, so that distribution is permitted only in or among
234
+ countries not thus excluded. In such case, this License incorporates
235
+ the limitation as if written in the body of this License.
236
+
237
+ 9. The Free Software Foundation may publish revised and/or new versions
238
+ of the General Public License from time to time. Such new versions will
239
+ be similar in spirit to the present version, but may differ in detail to
240
+ address new problems or concerns.
241
+
242
+ Each version is given a distinguishing version number. If the Program
243
+ specifies a version number of this License which applies to it and "any
244
+ later version", you have the option of following the terms and conditions
245
+ either of that version or of any later version published by the Free
246
+ Software Foundation. If the Program does not specify a version number of
247
+ this License, you may choose any version ever published by the Free Software
248
+ Foundation.
249
+
250
+ 10. If you wish to incorporate parts of the Program into other free
251
+ programs whose distribution conditions are different, write to the author
252
+ to ask for permission. For software which is copyrighted by the Free
253
+ Software Foundation, write to the Free Software Foundation; we sometimes
254
+ make exceptions for this. Our decision will be guided by the two goals
255
+ of preserving the free status of all derivatives of our free software and
256
+ of promoting the sharing and reuse of software generally.
257
+
258
+ NO WARRANTY
259
+
260
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261
+ FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262
+ OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263
+ PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264
+ OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266
+ TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267
+ PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268
+ REPAIR OR CORRECTION.
269
+
270
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271
+ WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272
+ REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273
+ INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274
+ OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275
+ TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276
+ YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277
+ PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278
+ POSSIBILITY OF SUCH DAMAGES.
279
+
280
+ END OF TERMS AND CONDITIONS
inc/class-wp-revisions-control-bulk-actions.php ADDED
@@ -0,0 +1,305 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Bulk actions.
4
+ *
5
+ * @package WP_Revisions_Control
6
+ */
7
+
8
+ /**
9
+ * Class WP_Revisions_Control_Bulk_Actions.
10
+ */
11
+ class WP_Revisions_Control_Bulk_Actions {
12
+ /**
13
+ * Singleton.
14
+ *
15
+ * @var static
16
+ */
17
+ private static $__instance;
18
+
19
+ /**
20
+ * Supported post types.
21
+ *
22
+ * @var array
23
+ */
24
+ protected $post_types;
25
+
26
+ /**
27
+ * Base for bulk action names.
28
+ *
29
+ * @var string
30
+ */
31
+ protected $action_base = 'wp_rev_ctl_bulk_';
32
+
33
+ /**
34
+ * Custom bulk actions.
35
+ *
36
+ * @var array
37
+ */
38
+ protected $actions;
39
+
40
+ /**
41
+ * Silence is golden!
42
+ */
43
+ private function __construct() {}
44
+
45
+ /**
46
+ * Singleton implementation.
47
+ *
48
+ * @param array $post_types Supported post types, used only on instantiation.
49
+ * @return static
50
+ */
51
+ public static function get_instance( $post_types = array() ) {
52
+ if ( ! is_a( static::$__instance, __CLASS__ ) ) {
53
+ static::$__instance = new self();
54
+
55
+ static::$__instance->setup( $post_types );
56
+ }
57
+
58
+ return static::$__instance;
59
+ }
60
+
61
+ /**
62
+ * One-time actions.
63
+ *
64
+ * @param array $post_types Supported post types.
65
+ */
66
+ public function setup( $post_types ) {
67
+ if ( empty( $post_types ) || ! is_array( $post_types ) ) {
68
+ return;
69
+ }
70
+
71
+ $this->post_types = $post_types;
72
+ $this->register_actions();
73
+
74
+ add_action( 'load-edit.php', array( $this, 'register_admin_hooks' ) );
75
+ add_filter( 'removable_query_args', array( $this, 'remove_message_query_args' ) );
76
+ }
77
+
78
+ /**
79
+ * Register custom actions.
80
+ */
81
+ protected function register_actions() {
82
+ $actions = array();
83
+
84
+ $actions[ $this->action_base . 'purge_excess' ] = __(
85
+ 'Purge excess revisions',
86
+ 'wp_revisions_control'
87
+ );
88
+
89
+ $actions[ $this->action_base . 'purge_all' ] = __(
90
+ 'Purge ALL revisions',
91
+ 'wp_revisions_control'
92
+ );
93
+
94
+ $this->actions = $actions;
95
+ }
96
+
97
+ /**
98
+ * Register various hooks.
99
+ */
100
+ public function register_admin_hooks() {
101
+ $screen = get_current_screen();
102
+
103
+ if ( null === $screen ) {
104
+ return;
105
+ }
106
+
107
+ $post_types = array_keys( $this->post_types );
108
+
109
+ if ( ! in_array( $screen->post_type, $post_types, true ) ) {
110
+ return;
111
+ }
112
+
113
+ $post_type_caps = get_post_type_object( $screen->post_type )->cap;
114
+ $user_can = (
115
+ current_user_can( $post_type_caps->edit_posts ) &&
116
+ current_user_can( $post_type_caps->edit_published_posts ) &&
117
+ current_user_can( $post_type_caps->edit_others_posts )
118
+ );
119
+ $user_can = apply_filters(
120
+ 'wp_revisions_control_current_user_can_bulk_actions',
121
+ $user_can,
122
+ $screen->post_type
123
+ );
124
+
125
+ if ( ! $user_can ) {
126
+ return;
127
+ }
128
+
129
+ if ( 'edit' !== $screen->base ) {
130
+ return;
131
+ }
132
+
133
+ add_filter( 'bulk_actions-' . $screen->id, array( $this, 'add_actions' ) );
134
+ add_filter( 'handle_bulk_actions-' . $screen->id, array( $this, 'handle_action' ), 10, 3 );
135
+ add_action( 'admin_notices', array( $this, 'admin_notices' ) );
136
+ }
137
+
138
+ /**
139
+ * Remove message query arguments to prevent re-display.
140
+ *
141
+ * @param array $args Array of query variables to remove from URL.
142
+ * @return array
143
+ */
144
+ public function remove_message_query_args( $args ) {
145
+ return array_merge( $args, $this->get_message_query_args() );
146
+ }
147
+
148
+ /**
149
+ * Return array of supported query args that trigger admin notices.
150
+ *
151
+ * @return array
152
+ */
153
+ protected function get_message_query_args() {
154
+ $args = array_keys( $this->actions );
155
+ $args[] = $this->action_base . 'missing';
156
+ $args[] = $this->action_base . 'nonce';
157
+
158
+ return $args;
159
+ }
160
+
161
+ /**
162
+ * Add our actions.
163
+ *
164
+ * @param string[] $actions Array of available actions.
165
+ * @return array
166
+ */
167
+ public function add_actions( $actions ) {
168
+ return array_merge( $actions, $this->actions );
169
+ }
170
+
171
+ /**
172
+ * Handle our bulk actions.
173
+ *
174
+ * @param string $redirect_to Redirect URL.
175
+ * @param string $action Bulk action being taken.
176
+ * @param array $ids Object IDs to manipulate.
177
+ * @return string
178
+ */
179
+ public function handle_action( $redirect_to, $action, $ids ) {
180
+ if ( ! array_key_exists( $action, $this->actions ) ) {
181
+ return $redirect_to;
182
+ }
183
+
184
+ $response = array_fill_keys( $this->get_message_query_args(), 0 );
185
+
186
+ switch ( str_replace( $this->action_base, '', $action ) ) {
187
+ case 'purge_all':
188
+ $this->purge_all( $ids );
189
+ $response[ $action ] = 1;
190
+ break;
191
+
192
+ case 'purge_excess':
193
+ $this->purge_excess( $ids );
194
+ $response[ $action ] = 1;
195
+ break;
196
+
197
+ case 'nonce':
198
+ break;
199
+
200
+ default:
201
+ $response[ $this->action_base . 'missing' ] = 1;
202
+ break;
203
+ }
204
+
205
+ if ( is_array( $response ) ) {
206
+ $response[ $this->action_base . 'nonce' ] = wp_create_nonce( $this->action_base );
207
+ $redirect_to = add_query_arg( $response, $redirect_to );
208
+ }
209
+
210
+ return $redirect_to;
211
+ }
212
+
213
+ /**
214
+ * Remove all revisions from the given IDs.
215
+ *
216
+ * @param array $ids Object IDs.
217
+ */
218
+ protected function purge_all( $ids ) {
219
+ $plugin = WP_Revisions_Control::get_instance();
220
+
221
+ foreach ( $ids as $id ) {
222
+ $plugin->do_purge_all( $id );
223
+ }
224
+ }
225
+
226
+ /**
227
+ * Remove excess revisions from the given IDs.
228
+ *
229
+ * @param array $ids Object IDs.
230
+ */
231
+ protected function purge_excess( $ids ) {
232
+ $plugin = WP_Revisions_Control::get_instance();
233
+
234
+ foreach ( $ids as $id ) {
235
+ $plugin->do_purge_excess( $id );
236
+ }
237
+ }
238
+
239
+ /**
240
+ * Render admin notices.
241
+ */
242
+ public function admin_notices() {
243
+ $message = null;
244
+
245
+ $nonce_key = $this->action_base . 'nonce';
246
+
247
+ if (
248
+ ! isset( $_GET[ $nonce_key ] ) ||
249
+ ! wp_verify_nonce( sanitize_text_field( $_GET[ $nonce_key ] ), $this->action_base )
250
+ ) {
251
+ return;
252
+ }
253
+
254
+ foreach ( $this->get_message_query_args() as $arg ) {
255
+ if ( isset( $_GET[ $arg ] ) && 1 === (int) $_GET[ $arg ] ) {
256
+ $message = $arg;
257
+ break;
258
+ }
259
+ }
260
+
261
+ if ( null === $message ) {
262
+ return;
263
+ }
264
+
265
+ $type = 'updated';
266
+
267
+ switch ( str_replace( $this->action_base, '', $message ) ) {
268
+ case 'purge_all':
269
+ $message = __(
270
+ 'Purged all revisions.',
271
+ 'wp_revisions_control'
272
+ );
273
+ break;
274
+
275
+ case 'purge_excess':
276
+ $message = __(
277
+ 'Purged excess revisions.',
278
+ 'wp_revisions_control'
279
+ );
280
+ break;
281
+
282
+ case 'nonce':
283
+ break;
284
+
285
+ default:
286
+ case 'missing':
287
+ $message = __(
288
+ 'WP Revisions Control encountered an unspecified error.',
289
+ 'wp_revisions_control'
290
+ );
291
+ $type = 'error';
292
+ break;
293
+ }
294
+
295
+ if ( ! isset( $message, $type ) ) {
296
+ return;
297
+ }
298
+
299
+ ?>
300
+ <div class="notice is-dismissible <?php echo esc_attr( $type ); ?>">
301
+ <p><?php echo esc_html( $message ); ?></p>
302
+ </div>
303
+ <?php
304
+ }
305
+ }
inc/class-wp-revisions-control.php ADDED
@@ -0,0 +1,615 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Main plugin functionality.
4
+ *
5
+ * @package WP_Revisions_Control
6
+ */
7
+
8
+ /**
9
+ * Class WP_Revisions_Control.
10
+ */
11
+ class WP_Revisions_Control {
12
+ /**
13
+ * Singleton.
14
+ *
15
+ * @var static
16
+ */
17
+ private static $__instance;
18
+
19
+ /**
20
+ * Filter priority.
21
+ *
22
+ * @see $this->filter_priority()
23
+ *
24
+ * @var int
25
+ */
26
+ private static $priority = null;
27
+
28
+ /**
29
+ * Default filter priority.
30
+ *
31
+ * @var int
32
+ */
33
+ private $priority_default = 50;
34
+
35
+ /**
36
+ * Supported post types.
37
+ *
38
+ * @see $this->get_post_types()
39
+ *
40
+ * @var array
41
+ */
42
+ private static $post_types = array();
43
+
44
+ /**
45
+ * Plugin settings.
46
+ *
47
+ * @see $this->get_settings()
48
+ *
49
+ * @var array
50
+ */
51
+ private static $settings = array();
52
+
53
+ /**
54
+ * WordPress options page to display settings on.
55
+ *
56
+ * @var string
57
+ */
58
+ private $settings_page = 'writing';
59
+
60
+ /**
61
+ * Name of custom settings sections.
62
+ *
63
+ * @var string
64
+ */
65
+ private $settings_section = 'wp_revisions_control';
66
+
67
+ /**
68
+ * Meta key holding post's revisions limit.
69
+ *
70
+ * @var string
71
+ */
72
+ private $meta_key_limit = '_wp_rev_ctl_limit';
73
+
74
+ /**
75
+ * Silence is golden!
76
+ */
77
+ private function __construct() {}
78
+
79
+ /**
80
+ * Singleton implementation.
81
+ *
82
+ * @return static
83
+ */
84
+ public static function get_instance() {
85
+ if ( ! is_a( static::$__instance, __CLASS__ ) ) {
86
+ static::$__instance = new self();
87
+
88
+ static::$__instance->setup();
89
+ }
90
+
91
+ return static::$__instance;
92
+ }
93
+
94
+ /**
95
+ * Register actions and filters at `init` so others can interact, if desired.
96
+ */
97
+ private function setup() {
98
+ add_action( 'plugins_loaded', array( $this, 'action_plugins_loaded' ) );
99
+ add_action( 'init', array( $this, 'action_init' ) );
100
+ }
101
+
102
+ /**
103
+ * Load plugin translations.
104
+ */
105
+ public function action_plugins_loaded() {
106
+ load_plugin_textdomain(
107
+ 'wp_revisions_control',
108
+ false,
109
+ dirname( dirname( plugin_basename( __FILE__ ) ) ) . '/languages/'
110
+ );
111
+ }
112
+
113
+ /**
114
+ * Register actions and filters.
115
+ */
116
+ public function action_init() {
117
+ add_action( 'admin_init', array( $this, 'action_admin_init' ) );
118
+
119
+ add_filter( 'wp_revisions_to_keep', array( $this, 'filter_wp_revisions_to_keep' ), $this->plugin_priority(), 2 );
120
+ }
121
+
122
+ /**
123
+ * Register plugin's admin-specific elements.
124
+ *
125
+ * Plugin title is intentionally not translatable.
126
+ */
127
+ public function action_admin_init() {
128
+ $post_types = $this->get_post_types();
129
+
130
+ // Plugin setting section.
131
+ register_setting( $this->settings_page, $this->settings_section, array( $this, 'sanitize_options' ) );
132
+
133
+ add_settings_section( $this->settings_section, 'WP Revisions Control', array( $this, 'settings_section_intro' ), $this->settings_page );
134
+
135
+ foreach ( $post_types as $post_type => $name ) {
136
+ add_settings_field( $this->settings_section . '-' . $post_type, $name, array( $this, 'field_post_type' ), $this->settings_page, $this->settings_section, array( 'post_type' => $post_type ) );
137
+ }
138
+
139
+ // Post-level functionality.
140
+ add_action( 'add_meta_boxes', array( $this, 'action_add_meta_boxes' ), 10, 2 );
141
+ add_action( 'wp_ajax_' . $this->settings_section . '_purge', array( $this, 'ajax_purge' ) );
142
+ add_action( 'save_post', array( $this, 'action_save_post' ) );
143
+
144
+ // Bulk actions.
145
+ WP_Revisions_Control_Bulk_Actions::get_instance( $post_types );
146
+ }
147
+
148
+ /**
149
+ * PLUGIN SETTINGS SECTION
150
+ * FOUND UNDER SETTINGS > WRITING
151
+ */
152
+
153
+ /**
154
+ * Display assistive text in settings section.
155
+ */
156
+ public function settings_section_intro() {
157
+ ?>
158
+ <p><?php esc_html_e( 'Set the number of revisions to save for each post type listed. To retain all revisions for a given post type, leave the field empty.', 'wp_revisions_control' ); ?></p>
159
+ <p><?php esc_html_e( 'If a post type isn\'t listed, revisions are not enabled for that post type.', 'wp_revisions_control' ); ?></p>
160
+ <?php
161
+
162
+ // Display a note if the plugin priority is other than the default.
163
+ // Will be useful when debugging issues later.
164
+ if ( $this->plugin_priority() !== $this->priority_default ) :
165
+ ?>
166
+ <p>
167
+ <?php
168
+ printf(
169
+ /* translators: 1. Filter tag. */
170
+ esc_html__(
171
+ 'A local change is causing this plugin\'s functionality to run at a priority other than the default. If you experience difficulties with the plugin, please unhook any functions from the %1$s filter.',
172
+ 'wp_revisions_control'
173
+ ),
174
+ '<code>wp_revisions_control_priority</code>'
175
+ );
176
+ ?>
177
+ </p>
178
+ <?php
179
+ endif;
180
+ }
181
+
182
+ /**
183
+ * Render field for each post type.
184
+ *
185
+ * @param array $args Field arguments.
186
+ */
187
+ public function field_post_type( $args ) {
188
+ $revisions_to_keep = $this->get_revisions_to_keep( $args['post_type'], true );
189
+ ?>
190
+ <input type="text" name="<?php echo esc_attr( $this->settings_section . '[' . $args['post_type'] . ']' ); ?>" value="<?php echo esc_attr( $revisions_to_keep ); ?>" class="small-text" />
191
+ <?php
192
+ }
193
+
194
+ /**
195
+ * Sanitize plugin settings.
196
+ *
197
+ * @param array $options Unsanitized settings.
198
+ * @return array
199
+ */
200
+ public function sanitize_options( $options ) {
201
+ $options_sanitized = array();
202
+
203
+ if ( is_array( $options ) ) {
204
+ foreach ( $options as $post_type => $to_keep ) {
205
+ $type_length = strlen( $to_keep );
206
+
207
+ if ( 0 === $type_length ) {
208
+ $to_keep = -1;
209
+ } else {
210
+ $to_keep = (int) $to_keep;
211
+ }
212
+
213
+ // Lowest possible value is -1, used to indicate infinite revisions are stored.
214
+ if ( -1 > $to_keep ) {
215
+ $to_keep = -1;
216
+ }
217
+
218
+ $options_sanitized[ $post_type ] = $to_keep;
219
+ }
220
+ }
221
+
222
+ return $options_sanitized;
223
+ }
224
+
225
+ /**
226
+ * REVISIONS QUANTITY OVERRIDES.
227
+ */
228
+
229
+ /**
230
+ * Allow others to change the priority this plugin's functionality runs at
231
+ *
232
+ * @uses apply_filters
233
+ * @return int
234
+ */
235
+ private function plugin_priority() {
236
+ if ( is_null( self::$priority ) ) {
237
+ $plugin_priority = apply_filters( 'wp_revisions_control_priority', $this->priority_default );
238
+
239
+ self::$priority = is_numeric( $plugin_priority ) ? (int) $plugin_priority : $this->priority_default;
240
+ }
241
+
242
+ return self::$priority;
243
+ }
244
+
245
+ /**
246
+ * Override number of revisions to keep using plugin's settings.
247
+ *
248
+ * Can either be post-specific or universal.
249
+ *
250
+ * @param int $qty Number of revisions to keep.
251
+ * @param WP_Post $post Post object.
252
+ * @return int
253
+ */
254
+ public function filter_wp_revisions_to_keep( $qty, $post ) {
255
+ $post_limit = get_post_meta( $post->ID, $this->meta_key_limit, true );
256
+
257
+ if ( 0 < strlen( $post_limit ) ) {
258
+ $qty = $post_limit;
259
+ } else {
260
+ $post_type = get_post_type( $post ) ? get_post_type( $post ) : $post->post_type;
261
+ $settings = $this->get_settings();
262
+
263
+ if ( array_key_exists( $post_type, $settings ) ) {
264
+ $qty = $settings[ $post_type ];
265
+ }
266
+ }
267
+
268
+ return $qty;
269
+ }
270
+
271
+ /**
272
+ * POST-LEVEL FUNCTIONALITY.
273
+ */
274
+
275
+ /**
276
+ * Override Core's revisions metabox.
277
+ *
278
+ * @param string $post_type Post type.
279
+ * @param object $post Post object.
280
+ */
281
+ public function action_add_meta_boxes( $post_type, $post ) {
282
+ if ( post_type_supports( $post_type, 'revisions' ) && 'auto-draft' !== get_post_status() && count( wp_get_post_revisions( $post ) ) > 1 ) {
283
+ // Replace the metabox.
284
+ remove_meta_box( 'revisionsdiv', null, 'normal' );
285
+ add_meta_box(
286
+ 'revisionsdiv-wp-rev-ctl',
287
+ __(
288
+ 'Revisions',
289
+ 'wp_revisions_control'
290
+ ),
291
+ array(
292
+ $this,
293
+ 'revisions_meta_box',
294
+ ),
295
+ null,
296
+ 'normal',
297
+ 'core'
298
+ );
299
+
300
+ // A bit of JS for us.
301
+ $handle = 'wp-revisions-control-post';
302
+ wp_enqueue_script( $handle, plugins_url( 'js/post.js', __DIR__ ), array( 'jquery' ), '20131205', true );
303
+ wp_localize_script(
304
+ $handle,
305
+ $this->settings_section,
306
+ array(
307
+ 'namespace' => $this->settings_section,
308
+ 'action_base' => $this->settings_section,
309
+ 'processing_text' => __( 'Processing&hellip;', 'wp_revisions_control' ),
310
+ 'ays' => __( 'Are you sure you want to remove revisions from this post?', 'wp_revisions_control' ),
311
+ 'autosave' => __( 'Autosave', 'wp_revisions_control' ),
312
+ 'nothing_text' => wpautop( __( 'There are no revisions to remove.', 'wp_revisions_control' ) ),
313
+ 'error' => __( 'An error occurred. Please refresh the page and try again.', 'wp_revisions_control' ),
314
+ )
315
+ );
316
+
317
+ // Add some styling to our metabox additions.
318
+ add_action( 'admin_head', array( $this, 'action_admin_head' ), 999 );
319
+ }
320
+ }
321
+
322
+ /**
323
+ * Render Revisions metabox with plugin's additions
324
+ *
325
+ * @param WP_Post $post Post object.
326
+ */
327
+ public function revisions_meta_box( $post ) {
328
+ post_revisions_meta_box( $post );
329
+
330
+ ?>
331
+ <div id="<?php echo esc_attr( $this->settings_section ); ?>">
332
+ <h4>WP Revisions Control</h4>
333
+
334
+ <p class="button purge" data-postid="<?php the_ID(); ?>" data-nonce="<?php echo esc_attr( wp_create_nonce( $this->settings_section . '_purge' ) ); ?>"><?php _e( 'Purge these revisions', 'wp_revisions_control' ); ?></p>
335
+
336
+ <p>
337
+ <?php
338
+ printf(
339
+ /* translators: 1. Text input field. */
340
+ esc_html__(
341
+ 'Limit this post to %1$s revisions. Leave this field blank for default behavior.',
342
+ 'wp_revisions_control'
343
+ ),
344
+ '<input type="text" name="' . esc_attr( $this->settings_section ) . '_qty" value="' . esc_attr( $this->get_post_revisions_to_keep( $post->ID ) ) . '" id="' . esc_attr( $this->settings_section ) . '_qty" size="2" />'
345
+ );
346
+ ?>
347
+
348
+ <?php wp_nonce_field( $this->settings_section . '_limit', $this->settings_section . '_limit_nonce', false ); ?>
349
+ </p>
350
+ </div><!-- #<?php echo esc_attr( $this->settings_section ); ?> -->
351
+ <?php
352
+ }
353
+
354
+ /**
355
+ * Process a post-specific request to purge revisions.
356
+ */
357
+ public function ajax_purge() {
358
+ $post_id = isset( $_REQUEST['post_id'] ) ? (int) $_REQUEST['post_id'] : false;
359
+
360
+ // Hold the current state of this Ajax request.
361
+ $response = array();
362
+
363
+ // Check for necessary data and capabilities.
364
+ if ( ! $post_id ) {
365
+ $response['error'] = __( 'No post ID was provided. Please refresh the page and try again.', 'wp_revisions_control' );
366
+ } elseif ( ! check_ajax_referer( $this->settings_section . '_purge', 'nonce', false ) ) {
367
+ $response['error'] = __( 'Invalid request. Please refresh the page and try again.', 'wp_revisions_control' );
368
+ } elseif ( ! current_user_can( 'edit_post', $post_id ) ) {
369
+ $response['error'] = __( 'You are not allowed to edit this post.', 'wp_revisions_control' );
370
+ }
371
+
372
+ // Request is valid if $response is still empty, as no errors arose above.
373
+ if ( empty( $response ) ) {
374
+ $response = $this->do_purge_all( $post_id );
375
+ }
376
+
377
+ // Pass the response back to JS.
378
+ echo json_encode( $response );
379
+ exit;
380
+ }
381
+
382
+ /**
383
+ * Remove all revisions from a given post ID.
384
+ *
385
+ * @param int $post_id Post ID to purge of revisions.
386
+ * @return array
387
+ */
388
+ public function do_purge_all( $post_id ) {
389
+ $response = array();
390
+
391
+ $revisions = wp_get_post_revisions( $post_id );
392
+
393
+ $count = count( $revisions );
394
+
395
+ foreach ( $revisions as $revision ) {
396
+ wp_delete_post_revision( $revision->ID );
397
+ }
398
+
399
+ $response['success'] = sprintf(
400
+ /* translators: 1. Number of removed revisions, already formatted for locale. */
401
+ esc_html__(
402
+ 'Removed %1$s revisions associated with this post.',
403
+ 'wp_revisions_control'
404
+ ),
405
+ number_format_i18n( $count, 0 )
406
+ );
407
+
408
+ $response['count'] = $count;
409
+
410
+ return $response;
411
+ }
412
+
413
+ /**
414
+ * Remove any revisions in excess of a post's limit.
415
+ *
416
+ * @param int $post_id Post ID to purge of excess revisions.
417
+ * @return array
418
+ */
419
+ public function do_purge_excess( $post_id ) {
420
+ $response = array(
421
+ 'count' => 0,
422
+ );
423
+
424
+ $to_keep = wp_revisions_to_keep( get_post( $post_id ) );
425
+
426
+ if ( $to_keep < 0 ) {
427
+ $response['success'] = __(
428
+ 'No revisions to remove.',
429
+ 'wp_revisions_control'
430
+ );
431
+
432
+ return $response;
433
+ }
434
+
435
+ $revisions = wp_get_post_revisions( $post_id );
436
+ $starting_count = count( $revisions );
437
+
438
+ if ( $starting_count <= $to_keep ) {
439
+ $response['success'] = __(
440
+ 'No revisions to remove.',
441
+ 'wp_revisions_control'
442
+ );
443
+
444
+ return $response;
445
+ }
446
+
447
+ $to_remove = array_slice( $revisions, $to_keep, null, true );
448
+ $response['count'] = count( $to_remove );
449
+
450
+ foreach ( $to_remove as $revision ) {
451
+ wp_delete_post_revision( $revision->ID );
452
+ }
453
+
454
+ return $response;
455
+ }
456
+
457
+ /**
458
+ * Sanitize and store post-specifiy revisions quantity.
459
+ *
460
+ * @param int $post_id Post ID.
461
+ */
462
+ public function action_save_post( $post_id ) {
463
+ $nonce = $this->settings_section . '_limit_nonce';
464
+ $qty = $this->settings_section . '_qty';
465
+
466
+ if ( isset( $_POST[ $nonce ], $_POST[ $qty ] ) && wp_verify_nonce( sanitize_text_field( $_POST[ $nonce ] ), $this->settings_section . '_limit' ) ) {
467
+ $limit = (int) $_POST[ $qty ];
468
+
469
+ if ( -1 === $limit || empty( $limit ) ) {
470
+ delete_post_meta( $post_id, $this->meta_key_limit );
471
+ } else {
472
+ update_post_meta( $post_id, $this->meta_key_limit, absint( $limit ) );
473
+ }
474
+ }
475
+ }
476
+
477
+ /**
478
+ * Add a border between the regular revisions list and this plugin's additions.
479
+ */
480
+ public function action_admin_head() {
481
+ ?>
482
+ <style type="text/css">
483
+ #revisionsdiv-wp-rev-ctl #<?php echo esc_attr( $this->settings_section ); ?> {
484
+ border-top: 1px solid #dfdfdf;
485
+ padding-top: 0;
486
+ margin-top: 20px;
487
+ }
488
+
489
+ #revisionsdiv-wp-rev-ctl #<?php echo esc_attr( $this->settings_section ); ?> h4 {
490
+ border-top: 1px solid #fff;
491
+ padding-top: 1.33em;
492
+ margin-top: 0;
493
+ }
494
+ </style>
495
+ <?php
496
+ }
497
+
498
+ /**
499
+ * PLUGIN UTILITIES.
500
+ */
501
+
502
+ /**
503
+ * Retrieve plugin settings.
504
+ *
505
+ * @return array
506
+ */
507
+ private function get_settings() {
508
+ static $hash = null;
509
+
510
+ $settings = get_option( $this->settings_section, array() );
511
+
512
+ if ( empty( self::$settings ) || $hash !== $this->hash_settings( $settings ) ) {
513
+ $post_types = $this->get_post_types();
514
+
515
+ if ( ! is_array( $settings ) ) {
516
+ $settings = array();
517
+ }
518
+
519
+ $merged_settings = array();
520
+
521
+ foreach ( $post_types as $post_type => $name ) {
522
+ if ( array_key_exists( $post_type, $settings ) ) {
523
+ $merged_settings[ $post_type ] = (int) $settings[ $post_type ];
524
+ } else {
525
+ $merged_settings[ $post_type ] = - 1;
526
+ }
527
+ }
528
+
529
+ self::$settings = $merged_settings;
530
+ $hash = $this->hash_settings( self::$settings );
531
+ }
532
+
533
+ return self::$settings;
534
+ }
535
+
536
+ /**
537
+ * Hash settings to limit re-parsing.
538
+ *
539
+ * @param array $settings Settings array.
540
+ * @return string
541
+ */
542
+ private function hash_settings( $settings ) {
543
+ // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
544
+ return md5( serialize( $settings ) );
545
+ }
546
+
547
+ /**
548
+ * Retrieve array of supported post types and their labels.
549
+ *
550
+ * @return array
551
+ */
552
+ private function get_post_types() {
553
+ if ( empty( self::$post_types ) ) {
554
+ foreach ( get_post_types() as $type ) {
555
+ if ( post_type_supports( $type, 'revisions' ) ) {
556
+ $object = get_post_type_object( $type );
557
+
558
+ if ( null === $object ) {
559
+ continue;
560
+ }
561
+
562
+ if ( property_exists( $object, 'labels' ) && property_exists( $object->labels, 'name' ) ) {
563
+ $name = $object->labels->name;
564
+ } else {
565
+ $name = $object->name;
566
+ }
567
+
568
+ self::$post_types[ $type ] = $name;
569
+ }
570
+ }
571
+ }
572
+
573
+ return self::$post_types;
574
+ }
575
+
576
+ /**
577
+ * Retrieve number of revisions to keep for a given post type.
578
+ *
579
+ * @param string $post_type Post type.
580
+ * @param bool $blank_for_all Should blank value be used to indicate all are kept.
581
+ * @return int|string
582
+ */
583
+ private function get_revisions_to_keep( $post_type, $blank_for_all = false ) {
584
+ // wp_revisions_to_keep() accepts a post object, not just the post type.
585
+ // We construct a new WP_Post object to ensure anything hooked to the wp_revisions_to_keep filter has the same basic data WP provides.
586
+ $_post = new WP_Post( (object) array( 'post_type' => $post_type ) );
587
+ $to_keep = wp_revisions_to_keep( $_post );
588
+
589
+ if ( $blank_for_all && ( -1 === $to_keep || '-1' === $to_keep ) ) {
590
+ return '';
591
+ } else {
592
+ return (int) $to_keep;
593
+ }
594
+ }
595
+
596
+ /**
597
+ * Retrieve number of revisions to keep for a give post.
598
+ *
599
+ * @param int $post_id Post ID.
600
+ * @return int|string
601
+ */
602
+ private function get_post_revisions_to_keep( $post_id ) {
603
+ $to_keep = get_post_meta( $post_id, $this->meta_key_limit, true );
604
+
605
+ if ( empty( $to_keep ) || -1 === $to_keep || '-1' === $to_keep ) {
606
+ $to_keep = '';
607
+ } else {
608
+ $to_keep = (int) $to_keep;
609
+ }
610
+
611
+ return $to_keep;
612
+ }
613
+ }
614
+
615
+ WP_Revisions_Control::get_instance();
languages/wp-revisions-control.pot CHANGED
@@ -1,15 +1,15 @@
1
- # Copyright (C) 2019 Erick Hitter
2
  # This file is distributed under the same license as the WP Revisions Control package.
3
  msgid ""
4
  msgstr ""
5
- "Project-Id-Version: WP Revisions Control 1.2.1\n"
6
  "Report-Msgid-Bugs-To: "
7
  "https://wordpress.org/support/plugin/wp-revisions-control\n"
8
- "POT-Creation-Date: 2019-04-14 04:52:10+00:00\n"
9
  "MIME-Version: 1.0\n"
10
  "Content-Type: text/plain; charset=utf-8\n"
11
  "Content-Transfer-Encoding: 8bit\n"
12
- "PO-Revision-Date: 2019-MO-DA HO:MI+ZONE\n"
13
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
  "Language-Team: LANGUAGE <LL@li.org>\n"
15
  "X-Generator: grunt-wp-i18n 0.5.4\n"
@@ -25,71 +25,99 @@ msgstr ""
25
  "X-Poedit-Bookmarks: \n"
26
  "X-Textdomain-Support: yes\n"
27
 
28
- #: wp-revisions-control.php:148
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  msgid ""
30
  "Set the number of revisions to save for each post type listed. To retain "
31
  "all revisions for a given post type, leave the field empty."
32
  msgstr ""
33
 
34
- #: wp-revisions-control.php:149
35
  msgid "If a post type isn't listed, revisions are not enabled for that post type."
36
  msgstr ""
37
 
38
- #: wp-revisions-control.php:155
 
39
  msgid ""
40
  "A local change is causing this plugin's functionality to run at a priority "
41
  "other than the default. If you experience difficulties with the plugin, "
42
- "please unhook any functions from the %s filter."
43
  msgstr ""
44
 
45
- #: wp-revisions-control.php:274
46
  msgid "Revisions"
47
  msgstr ""
48
 
49
- #: wp-revisions-control.php:282
50
  msgid "Processing&hellip;"
51
  msgstr ""
52
 
53
- #: wp-revisions-control.php:283
54
  msgid "Are you sure you want to remove revisions from this post?"
55
  msgstr ""
56
 
57
- #: wp-revisions-control.php:284
58
  msgid "Autosave"
59
  msgstr ""
60
 
61
- #: wp-revisions-control.php:285
62
  msgid "There are no revisions to remove."
63
  msgstr ""
64
 
65
- #: wp-revisions-control.php:286
66
  msgid "An error occurred. Please refresh the page and try again."
67
  msgstr ""
68
 
69
- #: wp-revisions-control.php:312
70
  msgid "Purge these revisions"
71
  msgstr ""
72
 
73
- #: wp-revisions-control.php:315
 
74
  msgid ""
75
- "Limit this post to %s revisions. Leave this field blank for default "
76
  "behavior."
77
  msgstr ""
78
 
79
- #: wp-revisions-control.php:341
80
  msgid "No post ID was provided. Please refresh the page and try again."
81
  msgstr ""
82
 
83
- #: wp-revisions-control.php:343
84
  msgid "Invalid request. Please refresh the page and try again."
85
  msgstr ""
86
 
87
- #: wp-revisions-control.php:345
88
  msgid "You are not allowed to edit this post."
89
  msgstr ""
90
 
91
- #: wp-revisions-control.php:357
92
- msgid "Removed %s revisions associated with this post."
 
 
 
 
 
 
93
  msgstr ""
94
 
95
  #. Plugin Name of the plugin/theme
1
+ # Copyright (C) 2021 Erick Hitter
2
  # This file is distributed under the same license as the WP Revisions Control package.
3
  msgid ""
4
  msgstr ""
5
+ "Project-Id-Version: WP Revisions Control 1.3\n"
6
  "Report-Msgid-Bugs-To: "
7
  "https://wordpress.org/support/plugin/wp-revisions-control\n"
8
+ "POT-Creation-Date: 2021-03-27 21:26:05+00:00\n"
9
  "MIME-Version: 1.0\n"
10
  "Content-Type: text/plain; charset=utf-8\n"
11
  "Content-Transfer-Encoding: 8bit\n"
12
+ "PO-Revision-Date: 2021-MO-DA HO:MI+ZONE\n"
13
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
  "Language-Team: LANGUAGE <LL@li.org>\n"
15
  "X-Generator: grunt-wp-i18n 0.5.4\n"
25
  "X-Poedit-Bookmarks: \n"
26
  "X-Textdomain-Support: yes\n"
27
 
28
+ #: inc/class-wp-revisions-control-bulk-actions.php:84
29
+ msgid "Purge excess revisions"
30
+ msgstr ""
31
+
32
+ #: inc/class-wp-revisions-control-bulk-actions.php:89
33
+ msgid "Purge ALL revisions"
34
+ msgstr ""
35
+
36
+ #: inc/class-wp-revisions-control-bulk-actions.php:269
37
+ msgid "Purged all revisions."
38
+ msgstr ""
39
+
40
+ #: inc/class-wp-revisions-control-bulk-actions.php:276
41
+ msgid "Purged excess revisions."
42
+ msgstr ""
43
+
44
+ #: inc/class-wp-revisions-control-bulk-actions.php:287
45
+ msgid "WP Revisions Control encountered an unspecified error."
46
+ msgstr ""
47
+
48
+ #: inc/class-wp-revisions-control.php:158
49
  msgid ""
50
  "Set the number of revisions to save for each post type listed. To retain "
51
  "all revisions for a given post type, leave the field empty."
52
  msgstr ""
53
 
54
+ #: inc/class-wp-revisions-control.php:159
55
  msgid "If a post type isn't listed, revisions are not enabled for that post type."
56
  msgstr ""
57
 
58
+ #: inc/class-wp-revisions-control.php:170
59
+ #. translators: 1. Filter tag.
60
  msgid ""
61
  "A local change is causing this plugin's functionality to run at a priority "
62
  "other than the default. If you experience difficulties with the plugin, "
63
+ "please unhook any functions from the %1$s filter."
64
  msgstr ""
65
 
66
+ #: inc/class-wp-revisions-control.php:287
67
  msgid "Revisions"
68
  msgstr ""
69
 
70
+ #: inc/class-wp-revisions-control.php:309
71
  msgid "Processing&hellip;"
72
  msgstr ""
73
 
74
+ #: inc/class-wp-revisions-control.php:310
75
  msgid "Are you sure you want to remove revisions from this post?"
76
  msgstr ""
77
 
78
+ #: inc/class-wp-revisions-control.php:311
79
  msgid "Autosave"
80
  msgstr ""
81
 
82
+ #: inc/class-wp-revisions-control.php:312
83
  msgid "There are no revisions to remove."
84
  msgstr ""
85
 
86
+ #: inc/class-wp-revisions-control.php:313
87
  msgid "An error occurred. Please refresh the page and try again."
88
  msgstr ""
89
 
90
+ #: inc/class-wp-revisions-control.php:334
91
  msgid "Purge these revisions"
92
  msgstr ""
93
 
94
+ #: inc/class-wp-revisions-control.php:340
95
+ #. translators: 1. Text input field.
96
  msgid ""
97
+ "Limit this post to %1$s revisions. Leave this field blank for default "
98
  "behavior."
99
  msgstr ""
100
 
101
+ #: inc/class-wp-revisions-control.php:365
102
  msgid "No post ID was provided. Please refresh the page and try again."
103
  msgstr ""
104
 
105
+ #: inc/class-wp-revisions-control.php:367
106
  msgid "Invalid request. Please refresh the page and try again."
107
  msgstr ""
108
 
109
+ #: inc/class-wp-revisions-control.php:369
110
  msgid "You are not allowed to edit this post."
111
  msgstr ""
112
 
113
+ #: inc/class-wp-revisions-control.php:401
114
+ #. translators: 1. Number of removed revisions, already formatted for locale.
115
+ msgid "Removed %1$s revisions associated with this post."
116
+ msgstr ""
117
+
118
+ #: inc/class-wp-revisions-control.php:427
119
+ #: inc/class-wp-revisions-control.php:439
120
+ msgid "No revisions to remove."
121
  msgstr ""
122
 
123
  #. Plugin Name of the plugin/theme
languages/wp_revisions_control-es_ES.mo CHANGED
Binary file
languages/wp_revisions_control-es_ES.pot CHANGED
@@ -1,19 +1,40 @@
1
- # Copyright (C) 2013 WP Revisions Control
2
  # This file is distributed under the same license as the WP Revisions Control package.
3
  msgid ""
4
  msgstr ""
5
- "Project-Id-Version: WP Revisions Control 1.2.1\n"
6
- "Report-Msgid-Bugs-To: http://wordpress.org/tag/wp-revisions-control\n"
7
- "POT-Creation-Date: 2013-12-05 22:43:04+00:00\n"
 
8
  "MIME-Version: 1.0\n"
9
  "Content-Type: text/plain; charset=UTF-8\n"
10
  "Content-Transfer-Encoding: 8bit\n"
11
- "PO-Revision-Date: 2013-12-10 10:40+0100\n"
12
  "Last-Translator: jelena kovacevic <jecajeca260@gmail.com>\n"
13
  "Language-Team: LANGUAGE <LL@li.org>\n"
14
- "X-Generator: Poedit 1.5.5\n"
15
 
16
- #: wp-revisions-control.php:148
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  msgid ""
18
  "Set the number of revisions to save for each post type listed. To retain all "
19
  "revisions for a given post type, leave the field empty."
@@ -22,83 +43,92 @@ msgstr ""
22
  "que aparece. Para conservar todas las revisiones para un tipo determinado de "
23
  "publicación, deje el campo vacío."
24
 
25
- #: wp-revisions-control.php:149
26
  msgid ""
27
  "If a post type isn't listed, revisions are not enabled for that post type."
28
  msgstr ""
29
  "Si un tipo de publicación no aparece en la lista, las revisiones no están "
30
- "habilitadas para ese tipo de publicación.."
31
 
32
- #: wp-revisions-control.php:155
 
33
  msgid ""
34
  "A local change is causing this plugin's functionality to run at a priority "
35
  "other than the default. If you experience difficulties with the plugin, "
36
- "please unhook any functions from the %s filter."
37
  msgstr ""
38
  "Un cambio local está causando que la funcionalidad de este plugin se ejecute "
39
  "con una prioridad distinta de la predeterminada . Si tiene alguna dificultad "
40
- "con el plugin, por favor desenganchar las funciones del filtro %s."
41
 
42
- #: wp-revisions-control.php:274
43
  msgid "Revisions"
44
  msgstr "Revisiones"
45
 
46
- #: wp-revisions-control.php:282
47
  msgid "Processing&hellip;"
48
- msgstr "Procesaando&hellip;..."
49
 
50
- #: wp-revisions-control.php:283
51
  msgid "Are you sure you want to remove revisions from this post?"
52
  msgstr "¿Está seguro de que desea eliminar las revisiones de esta publicación?"
53
 
54
- #: wp-revisions-control.php:284
55
  msgid "Autosave"
56
  msgstr "Autoguardado"
57
 
58
- #: wp-revisions-control.php:285
59
  msgid "There are no revisions to remove."
60
  msgstr "No hay revisiones para eliminar."
61
 
62
- #: wp-revisions-control.php:286
63
  msgid "An error occurred. Please refresh the page and try again."
64
  msgstr "Se ha producido un error. Actualice la página y vuelva a intentarlo."
65
 
66
- #: wp-revisions-control.php:312
67
  msgid "Purge these revisions"
68
  msgstr "Purgar estas revisiones"
69
 
70
- #: wp-revisions-control.php:315
 
71
  msgid ""
72
- "Limit this post to %s revisions. Leave this field blank for default behavior."
 
73
  msgstr ""
74
- "Limitar esta publicación a %s revisiones. Deje este campo en blanco para el "
75
- "comportamiento por defecto."
76
 
77
- #: wp-revisions-control.php:341
78
  msgid "No post ID was provided. Please refresh the page and try again."
79
  msgstr ""
80
  "No se proporcionó un ID de publicación. Actualice la página y vuelva a "
81
  "intentarlo."
82
 
83
- #: wp-revisions-control.php:343
84
  msgid "Invalid request. Please refresh the page and try again."
85
  msgstr "Petición no válida. Actualice la página y vuelva a intentarlo."
86
 
87
- #: wp-revisions-control.php:345
88
  msgid "You are not allowed to edit this post."
89
  msgstr "Usted no está autorizado a editar esta publicación."
90
 
91
- #: wp-revisions-control.php:357
92
- msgid "Removed %s revisions associated with this post."
93
- msgstr "Eliminadas %s revisiones asociadas a esta publicación."
 
 
 
 
 
 
94
 
95
  #. Plugin Name of the plugin/theme
96
  msgid "WP Revisions Control"
97
  msgstr "Control Revisiones WP"
98
 
99
  #. Plugin URI of the plugin/theme
100
- msgid "http://www.ethitter.com/plugins/wp-revisions-control/"
101
- msgstr "http://www.ethitter.com/plugins/wp-revisions-control/"
102
 
103
  #. Description of the plugin/theme
104
  msgid "Control how many revisions are stored for each post type"
@@ -110,5 +140,5 @@ msgid "Erick Hitter"
110
  msgstr "Erick Hitter"
111
 
112
  #. Author URI of the plugin/theme
113
- msgid "http://www.ethitter.com/"
114
- msgstr "http://www.ethitter.com/"
1
+ # Copyright (C) 2019 Erick Hitter
2
  # This file is distributed under the same license as the WP Revisions Control package.
3
  msgid ""
4
  msgstr ""
5
+ "Project-Id-Version: WP Revisions Control 1.3\n"
6
+ "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/wp-revisions-"
7
+ "control\n"
8
+ "POT-Creation-Date: 2019-05-26 23:37:00+00:00\n"
9
  "MIME-Version: 1.0\n"
10
  "Content-Type: text/plain; charset=UTF-8\n"
11
  "Content-Transfer-Encoding: 8bit\n"
12
+ "PO-Revision-Date: 2019-05-26 17:00-0700\n"
13
  "Last-Translator: jelena kovacevic <jecajeca260@gmail.com>\n"
14
  "Language-Team: LANGUAGE <LL@li.org>\n"
15
+ "X-Generator: Poedit 2.2.3\n"
16
 
17
+ #: inc/class-wp-revisions-control-bulk-actions.php:84
18
+ msgid "Purge excess revisions"
19
+ msgstr ""
20
+
21
+ #: inc/class-wp-revisions-control-bulk-actions.php:89
22
+ msgid "Purge ALL revisions"
23
+ msgstr ""
24
+
25
+ #: inc/class-wp-revisions-control-bulk-actions.php:269
26
+ msgid "Purged all revisions."
27
+ msgstr ""
28
+
29
+ #: inc/class-wp-revisions-control-bulk-actions.php:276
30
+ msgid "Purged excess revisions."
31
+ msgstr ""
32
+
33
+ #: inc/class-wp-revisions-control-bulk-actions.php:287
34
+ msgid "WP Revisions Control encountered an unspecified error."
35
+ msgstr ""
36
+
37
+ #: inc/class-wp-revisions-control.php:158
38
  msgid ""
39
  "Set the number of revisions to save for each post type listed. To retain all "
40
  "revisions for a given post type, leave the field empty."
43
  "que aparece. Para conservar todas las revisiones para un tipo determinado de "
44
  "publicación, deje el campo vacío."
45
 
46
+ #: inc/class-wp-revisions-control.php:159
47
  msgid ""
48
  "If a post type isn't listed, revisions are not enabled for that post type."
49
  msgstr ""
50
  "Si un tipo de publicación no aparece en la lista, las revisiones no están "
51
+ "habilitadas para ese tipo de publicación."
52
 
53
+ #. translators: 1. Filter tag.
54
+ #: inc/class-wp-revisions-control.php:170
55
  msgid ""
56
  "A local change is causing this plugin's functionality to run at a priority "
57
  "other than the default. If you experience difficulties with the plugin, "
58
+ "please unhook any functions from the %1$s filter."
59
  msgstr ""
60
  "Un cambio local está causando que la funcionalidad de este plugin se ejecute "
61
  "con una prioridad distinta de la predeterminada . Si tiene alguna dificultad "
62
+ "con el plugin, por favor desenganchar las funciones del filtro %1$s."
63
 
64
+ #: inc/class-wp-revisions-control.php:287
65
  msgid "Revisions"
66
  msgstr "Revisiones"
67
 
68
+ #: inc/class-wp-revisions-control.php:309
69
  msgid "Processing&hellip;"
70
+ msgstr "Procesando&hellip;"
71
 
72
+ #: inc/class-wp-revisions-control.php:310
73
  msgid "Are you sure you want to remove revisions from this post?"
74
  msgstr "¿Está seguro de que desea eliminar las revisiones de esta publicación?"
75
 
76
+ #: inc/class-wp-revisions-control.php:311
77
  msgid "Autosave"
78
  msgstr "Autoguardado"
79
 
80
+ #: inc/class-wp-revisions-control.php:312
81
  msgid "There are no revisions to remove."
82
  msgstr "No hay revisiones para eliminar."
83
 
84
+ #: inc/class-wp-revisions-control.php:313
85
  msgid "An error occurred. Please refresh the page and try again."
86
  msgstr "Se ha producido un error. Actualice la página y vuelva a intentarlo."
87
 
88
+ #: inc/class-wp-revisions-control.php:334
89
  msgid "Purge these revisions"
90
  msgstr "Purgar estas revisiones"
91
 
92
+ #. translators: 1. Text input field.
93
+ #: inc/class-wp-revisions-control.php:340
94
  msgid ""
95
+ "Limit this post to %1$s revisions. Leave this field blank for default "
96
+ "behavior."
97
  msgstr ""
98
+ "Limitar esta publicación a %1$s revisiones. Deje este campo en blanco para "
99
+ "el comportamiento por defecto."
100
 
101
+ #: inc/class-wp-revisions-control.php:365
102
  msgid "No post ID was provided. Please refresh the page and try again."
103
  msgstr ""
104
  "No se proporcionó un ID de publicación. Actualice la página y vuelva a "
105
  "intentarlo."
106
 
107
+ #: inc/class-wp-revisions-control.php:367
108
  msgid "Invalid request. Please refresh the page and try again."
109
  msgstr "Petición no válida. Actualice la página y vuelva a intentarlo."
110
 
111
+ #: inc/class-wp-revisions-control.php:369
112
  msgid "You are not allowed to edit this post."
113
  msgstr "Usted no está autorizado a editar esta publicación."
114
 
115
+ #. translators: 1. Number of removed revisions, already formatted for locale.
116
+ #: inc/class-wp-revisions-control.php:401
117
+ msgid "Removed %1$s revisions associated with this post."
118
+ msgstr "Eliminadas %1$s revisiones asociadas a esta publicación."
119
+
120
+ #: inc/class-wp-revisions-control.php:427
121
+ #: inc/class-wp-revisions-control.php:439
122
+ msgid "No revisions to remove."
123
+ msgstr ""
124
 
125
  #. Plugin Name of the plugin/theme
126
  msgid "WP Revisions Control"
127
  msgstr "Control Revisiones WP"
128
 
129
  #. Plugin URI of the plugin/theme
130
+ msgid "https://ethitter.com/plugins/wp-revisions-control/"
131
+ msgstr "https://ethitter.com/plugins/wp-revisions-control/"
132
 
133
  #. Description of the plugin/theme
134
  msgid "Control how many revisions are stored for each post type"
140
  msgstr "Erick Hitter"
141
 
142
  #. Author URI of the plugin/theme
143
+ msgid "https://ethitter.com/"
144
+ msgstr "https://ethitter.com/"
readme.txt CHANGED
@@ -3,8 +3,8 @@ Contributors: ethitter
3
  Donate link: https://ethitter.com/donate/
4
  Tags: revision, revisions, admin
5
  Requires at least: 3.6
6
- Tested up to: 5.2
7
- Stable tag: 1.2.1
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
10
 
@@ -20,7 +20,7 @@ Why is this helpful? Revisions are stored in the database, and if many are store
20
 
21
  Thanks to Maria Ramos at [WebHostingHub](http://www.webhostinghub.com/), the plugin is also available in Spanish. Many thanks to her for her efforts!
22
 
23
- **Development is over on GitHub: https://github.com/ethitter/WP-Revisions-Control.**
24
 
25
  == Installation ==
26
 
@@ -35,6 +35,11 @@ Navigate to **Settings > Writing** in your WordPress Dashboard, and look for the
35
 
36
  == Changelog ==
37
 
 
 
 
 
 
38
  = 1.2.1 =
39
  * Introduce Spanish translation thanks to Maria Ramos at [WebHostingHub](http://www.webhostinghub.com/).
40
 
@@ -46,6 +51,9 @@ Navigate to **Settings > Writing** in your WordPress Dashboard, and look for the
46
 
47
  == Upgrade Notice ==
48
 
 
 
 
49
  = 1.2.1 =
50
  Introduces Spanish translation thanks to Maria Ramos at [WebHostingHub](http://www.webhostinghub.com/).
51
 
3
  Donate link: https://ethitter.com/donate/
4
  Tags: revision, revisions, admin
5
  Requires at least: 3.6
6
+ Tested up to: 5.9
7
+ Stable tag: 1.3
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
10
 
20
 
21
  Thanks to Maria Ramos at [WebHostingHub](http://www.webhostinghub.com/), the plugin is also available in Spanish. Many thanks to her for her efforts!
22
 
23
+ **Development is at https://git.ethitter.com/wp-plugins/wp-revisions-control.**
24
 
25
  == Installation ==
26
 
35
 
36
  == Changelog ==
37
 
38
+ = 1.3 =
39
+ * Add bulk actions to purge excess or all revisions.
40
+ * Introduce unit tests.
41
+ * Conform to coding standards.
42
+
43
  = 1.2.1 =
44
  * Introduce Spanish translation thanks to Maria Ramos at [WebHostingHub](http://www.webhostinghub.com/).
45
 
51
 
52
  == Upgrade Notice ==
53
 
54
+ = 1.3 =
55
+ Introduces bulk actions for purging revisions, along with unit tests. The plugin also conforms to coding standards.
56
+
57
  = 1.2.1 =
58
  Introduces Spanish translation thanks to Maria Ramos at [WebHostingHub](http://www.webhostinghub.com/).
59
 
wp-revisions-control.php CHANGED
@@ -1,508 +1,34 @@
1
  <?php
2
- /*
3
- Plugin Name: WP Revisions Control
4
- Plugin URI: https://ethitter.com/plugins/wp-revisions-control/
5
- Description: Control how many revisions are stored for each post type
6
- Author: Erick Hitter
7
- Version: 1.2.1
8
- Author URI: https://ethitter.com/
9
- Text Domain: wp_revisions_control
10
- Domain Path: /languages/
11
-
12
- This program is free software; you can redistribute it and/or modify
13
- it under the terms of the GNU General Public License as published by
14
- the Free Software Foundation; either version 2 of the License, or
15
- (at your option) any later version.
16
-
17
- This program is distributed in the hope that it will be useful,
18
- but WITHOUT ANY WARRANTY; without even the implied warranty of
19
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20
- GNU General Public License for more details.
21
-
22
- You should have received a copy of the GNU General Public License
23
- along with this program; if not, write to the Free Software
24
- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
25
- */
26
-
27
- class WP_Revisions_Control {
28
- /**
29
- * Singleton
30
- */
31
- private static $__instance = null;
32
-
33
- /**
34
- * Class variables
35
- */
36
- private static $priority = null; // use $this->plugin_priority()
37
- private $priority_default = 50;
38
-
39
- private static $post_types = array(); // use $this->get_post_types()
40
- private static $settings = array(); // use $this->get_settings()
41
-
42
- private $settings_page = 'writing';
43
- private $settings_section = 'wp_revisions_control';
44
-
45
- private $meta_key_limit = '_wp_rev_ctl_limit';
46
-
47
- /**
48
- * Silence is golden!
49
- */
50
- private function __construct() {}
51
-
52
- /**
53
- * Singleton implementation
54
- *
55
- * @uses self::setup
56
- * @return object
57
- */
58
- public static function get_instance() {
59
- if ( ! is_a( self::$__instance, __CLASS__ ) ) {
60
- self::$__instance = new self;
61
-
62
- self::$__instance->setup();
63
- }
64
-
65
- return self::$__instance;
66
- }
67
-
68
- /**
69
- * Register actions and filters at `init` so others can interact, if desired.
70
- *
71
- * @uses add_action
72
- * @return null
73
- */
74
- private function setup() {
75
- add_action( 'plugins_loaded', array( $this, 'action_plugins_loaded' ) );
76
- add_action( 'init', array( $this, 'action_init' ) );
77
- }
78
-
79
- /**
80
- * Load plugin translations
81
- *
82
- * @uses load_plugin_textdomain
83
- * @uses plugin_basename
84
- * @action plugins_loaded
85
- * @return null
86
- */
87
- public function action_plugins_loaded() {
88
- load_plugin_textdomain( 'wp_revisions_control', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' );
89
- }
90
-
91
- /**
92
- * Register actions and filters
93
- *
94
- * @uses add_action
95
- * @uses add_filter
96
- * @uses this::plugin_priority
97
- * @return null
98
- */
99
- public function action_init() {
100
- add_action( 'admin_init', array( $this, 'action_admin_init' ) );
101
-
102
- add_filter( 'wp_revisions_to_keep', array( $this, 'filter_wp_revisions_to_keep' ), $this->plugin_priority(), 2 );
103
- }
104
-
105
- /**
106
- * Register plugin's admin-specific elements
107
- *
108
- * Plugin title is intentionally not translatable.
109
- *
110
- * @uses register_setting
111
- * @uses add_settings_section
112
- * @uses __
113
- * @uses this::get_post_types
114
- * @uses add_settings_field
115
- * @action admin_init
116
- * @return null
117
- */
118
- public function action_admin_init() {
119
- // Plugin setting section
120
- register_setting( $this->settings_page, $this->settings_section, array( $this, 'sanitize_options' ) );
121
-
122
- add_settings_section( $this->settings_section, 'WP Revisions Control', array( $this, 'settings_section_intro' ), $this->settings_page );
123
-
124
- foreach ( $this->get_post_types() as $post_type => $name ) {
125
- add_settings_field( $this->settings_section . '-' . $post_type, $name, array( $this, 'field_post_type' ), $this->settings_page, $this->settings_section, array( 'post_type' => $post_type ) );
126
- }
127
-
128
- // Post-level functionality
129
- add_action( 'add_meta_boxes', array( $this, 'action_add_meta_boxes' ), 10, 2 );
130
- add_action( 'wp_ajax_' . $this->settings_section . '_purge', array( $this, 'ajax_purge' ) );
131
- add_action( 'save_post', array( $this, 'action_save_post' ) );
132
- }
133
-
134
- /**
135
- ** PLUGIN SETTINGS SECTION
136
- ** FOUND UNDER SETTINGS > WRITING
137
- **/
138
-
139
- /**
140
- * Display assistive text in settings section
141
- *
142
- * @uses _e
143
- * @uses this::plugin_priority
144
- * @return string
145
- */
146
- public function settings_section_intro() {
147
- ?>
148
- <p><?php _e( 'Set the number of revisions to save for each post type listed. To retain all revisions for a given post type, leave the field empty.', 'wp_revisions_control' ); ?></p>
149
- <p><?php _e( "If a post type isn't listed, revisions are not enabled for that post type.", 'wp_revisions_control' ); ?></p>
150
- <?php
151
-
152
- // Display a note if the plugin priority is other than the default.
153
- // Will be useful when debugging issues later.
154
- if ( $this->plugin_priority() !== $this->priority_default ) : ?>
155
- <p><?php printf( __( "A local change is causing this plugin's functionality to run at a priority other than the default. If you experience difficulties with the plugin, please unhook any functions from the %s filter.", 'wp_revisions_control' ), '<code>wp_revisions_control_priority</code>' ); ?></p>
156
- <?php endif;
157
- }
158
-
159
- /**
160
- * Render field for each post type
161
- *
162
- * @param array $args
163
- * @uses this::get_revisions_to_keep
164
- * @uses esc_attr
165
- * @return string
166
- */
167
- public function field_post_type( $args ) {
168
- $revisions_to_keep = $this->get_revisions_to_keep( $args['post_type'], true );
169
- ?>
170
- <input type="text" name="<?php echo esc_attr( $this->settings_section . '[' . $args['post_type'] . ']' ); ?>" value="<?php echo esc_attr( $revisions_to_keep ); ?>" class="small-text" />
171
- <?php
172
- }
173
-
174
- /**
175
- * Sanitize plugin settings
176
- *
177
- * @param array $options
178
- * @return array
179
- */
180
- public function sanitize_options( $options ) {
181
- $options_sanitized = array();
182
-
183
- if ( is_array( $options ) ) {
184
- foreach ( $options as $post_type => $to_keep ) {
185
- if ( 0 === strlen( $to_keep ) )
186
- $to_keep = -1;
187
- else
188
- $to_keep = intval( $to_keep );
189
-
190
- // Lowest possible value is -1, used to indicate infinite revisions are stored
191
- if ( -1 > $to_keep )
192
- $to_keep = -1;
193
-
194
- $options_sanitized[ $post_type ] = $to_keep;
195
- }
196
- }
197
-
198
- return $options_sanitized;
199
- }
200
-
201
- /**
202
- ** REVISIONS QUANTITY OVERRIDES
203
- **/
204
-
205
- /**
206
- * Allow others to change the priority this plugin's functionality runs at
207
- *
208
- * @uses apply_filters
209
- * @return int
210
- */
211
- private function plugin_priority() {
212
- if ( is_null( self::$priority ) ) {
213
- $plugin_priority = apply_filters( 'wp_revisions_control_priority', $this->priority_default );
214
-
215
- self::$priority = is_numeric( $plugin_priority ) ? (int) $plugin_priority : $this->priority_default;
216
- }
217
-
218
- return self::$priority;
219
- }
220
-
221
- /**
222
- * Override number of revisions to keep using plugin's settings
223
- *
224
- * Can either be post-specific or universal
225
- *
226
- * @uses get_post_meta
227
- * @uses get_post_type
228
- * @uses this::get_settings
229
- * @filter wp_revisions_to_keep
230
- * @return mixed
231
- */
232
- public function filter_wp_revisions_to_keep( $qty, $post ) {
233
- $post_limit = get_post_meta( $post->ID, $this->meta_key_limit, true );
234
-
235
- if ( 0 < strlen( $post_limit ) ) {
236
- $qty = $post_limit;
237
- } else {
238
- $post_type = get_post_type( $post ) ? get_post_type( $post ) : $post->post_type;
239
- $settings = $this->get_settings();
240
-
241
- if ( array_key_exists( $post_type, $settings ) )
242
- $qty = $settings[ $post_type ];
243
- }
244
-
245
- return $qty;
246
- }
247
-
248
- /**
249
- ** POST-LEVEL FUNCTIONALITY
250
- **/
251
-
252
- /**
253
- * Override Core's revisions metabox
254
- *
255
- * @param string $post_type
256
- * @param object $post
257
- * @uses post_type_supports
258
- * @uses get_post_status
259
- * @uses wp_get_post_revisions
260
- * @uses remove_meta_box
261
- * @uses add_meta_box
262
- * @uses wp_enqueue_script
263
- * @uses plugins_url
264
- * @uses wp_localize_script
265
- * @uses wpautop
266
- * @uses add_action
267
- * @action add_meta_boxes
268
- * @return null
269
- */
270
- public function action_add_meta_boxes( $post_type, $post ) {
271
- if ( post_type_supports( $post_type, 'revisions' ) && 'auto-draft' != get_post_status() && count( wp_get_post_revisions( $post ) ) > 1 ) {
272
- // Replace the metabox
273
- remove_meta_box( 'revisionsdiv', null, 'normal' );
274
- add_meta_box( 'revisionsdiv-wp-rev-ctl', __('Revisions', 'wp_revisions_control'), array( $this, 'revisions_meta_box' ), null, 'normal', 'core' );
275
-
276
- // A bit of JS for us
277
- $handle = 'wp-revisions-control-post';
278
- wp_enqueue_script( $handle, plugins_url( 'js/post.js', __FILE__ ), array( 'jquery' ), '20131205', true );
279
- wp_localize_script( $handle, $this->settings_section, array(
280
- 'namespace' => $this->settings_section,
281
- 'action_base' => $this->settings_section,
282
- 'processing_text' => __( 'Processing&hellip;', 'wp_revisions_control' ),
283
- 'ays' => __( 'Are you sure you want to remove revisions from this post?', 'wp_revisions_control' ),
284
- 'autosave' => __( 'Autosave', 'wp_revisions_control' ),
285
- 'nothing_text' => wpautop( __( 'There are no revisions to remove.', 'wp_revisions_control' ) ),
286
- 'error' => __( 'An error occurred. Please refresh the page and try again.', 'wp_revisions_control' )
287
- ) );
288
-
289
- // Add some styling to our metabox additions
290
- add_action( 'admin_head', array( $this, 'action_admin_head' ), 999 );
291
- }
292
- }
293
-
294
- /**
295
- * Render Revisions metabox with plugin's additions
296
- *
297
- * @uses post_revisions_meta_box
298
- * @uses the_ID
299
- * @uses esc_attr
300
- * @uses wp_create_nonce
301
- * @uses this::get_post_revisions_to_keep
302
- * @uses wp_nonce_field
303
- * @return string
304
- */
305
- public function revisions_meta_box( $post ) {
306
- post_revisions_meta_box( $post );
307
-
308
- ?>
309
- <div id="<?php echo esc_attr( $this->settings_section ); ?>">
310
- <h4>WP Revisions Control</h4>
311
-
312
- <p class="button purge" data-postid="<?php the_ID(); ?>" data-nonce="<?php echo esc_attr( wp_create_nonce( $this->settings_section . '_purge' ) ); ?>"><?php _e( 'Purge these revisions', 'wp_revisions_control' ); ?></p>
313
-
314
- <p>
315
- <?php printf( __( 'Limit this post to %s revisions. Leave this field blank for default behavior.', 'wp_revisions_control' ), '<input type="text" name="' . $this->settings_section . '_qty" value="' . $this->get_post_revisions_to_keep( $post->ID ) . '" id="' . $this->settings_section . '_qty" size="2" />' ); ?>
316
-
317
- <?php wp_nonce_field( $this->settings_section . '_limit', $this->settings_section . '_limit_nonce', false ); ?>
318
- </p>
319
- </div><!-- #<?php echo esc_attr( $this->settings_section ); ?> -->
320
- <?php
321
- }
322
-
323
- /**
324
- * Process a post-specific request to purge revisions
325
- *
326
- * @uses __
327
- * @uses check_ajax_referer
328
- * @uses current_user_can
329
- * @uses wp_get_post_revisions
330
- * @uses number_format_i18n
331
- * @return string
332
- */
333
- public function ajax_purge() {
334
- $post_id = isset( $_REQUEST['post_id'] ) ? (int) $_REQUEST['post_id'] : false;
335
-
336
- // Hold the current state of this Ajax request
337
- $response = array();
338
-
339
- // Check for necessary data and capabilities
340
- if ( ! $post_id )
341
- $response['error'] = __( 'No post ID was provided. Please refresh the page and try again.', 'wp_revisions_control' );
342
- elseif ( ! check_ajax_referer( $this->settings_section . '_purge', 'nonce', false ) )
343
- $response['error'] = __( 'Invalid request. Please refresh the page and try again.', 'wp_revisions_control' );
344
- elseif ( ! current_user_can( 'edit_post', $post_id ) )
345
- $response['error'] = __( 'You are not allowed to edit this post.', 'wp_revisions_control' );
346
-
347
- // Request is valid if $response is still empty, as no errors arose above
348
- if ( empty( $response ) ) {
349
- $revisions = wp_get_post_revisions( $post_id );
350
-
351
- $count = count( $revisions );
352
-
353
- foreach ( $revisions as $revision ) {
354
- wp_delete_post_revision( $revision->ID );
355
- }
356
-
357
- $response['success'] = sprintf( __( 'Removed %s revisions associated with this post.', 'wp_revisions_control' ), number_format_i18n( $count, 0 ) );
358
- $response['count'] = $count;
359
- }
360
-
361
- // Pass the response back to JS
362
- echo json_encode( $response );
363
- exit;
364
- }
365
-
366
- /**
367
- * Sanitize and store post-specifiy revisions quantity
368
- *
369
- * @uses wp_verify_nonce
370
- * @uses update_post_meta
371
- * @action save_post
372
- * @return null
373
- */
374
- public function action_save_post( $post_id ) {
375
- if ( isset( $_POST[ $this->settings_section . '_limit_nonce' ] ) && wp_verify_nonce( $_POST[ $this->settings_section . '_limit_nonce' ], $this->settings_section . '_limit' ) && isset( $_POST[ $this->settings_section . '_qty' ] ) ) {
376
- $limit = $_POST[ $this->settings_section . '_qty' ];
377
-
378
- if ( -1 == $limit || empty( $limit ) )
379
- delete_post_meta( $post_id, $this->meta_key_limit );
380
- else
381
- update_post_meta( $post_id, $this->meta_key_limit, absint( $limit ) );
382
- }
383
- }
384
-
385
- /**
386
- * Add a border between the regular revisions list and this plugin's additions
387
- *
388
- * @uses esc_attr
389
- * @action admin_head
390
- * @return string
391
- */
392
- public function action_admin_head() {
393
- ?>
394
- <style type="text/css">
395
- #revisionsdiv-wp-rev-ctl #<?php echo esc_attr( $this->settings_section ); ?> {
396
- border-top: 1px solid #dfdfdf;
397
- padding-top: 0;
398
- margin-top: 20px;
399
- }
400
-
401
- #revisionsdiv-wp-rev-ctl #<?php echo esc_attr( $this->settings_section ); ?> h4 {
402
- border-top: 1px solid #fff;
403
- padding-top: 1.33em;
404
- margin-top: 0;
405
- }
406
- </style>
407
- <?php
408
- }
409
-
410
- /**
411
- ** PLUGIN UTILITIES
412
- **/
413
-
414
- /**
415
- * Retrieve plugin settings
416
- *
417
- * @uses this::get_post_types
418
- * @uses get_option
419
- * @return array
420
- */
421
- private function get_settings() {
422
- if ( empty( self::$settings ) ) {
423
- $post_types = $this->get_post_types();
424
-
425
- $settings = get_option( $this->settings_section, array() );
426
-
427
- $merged_settings = array();
428
-
429
- foreach ( $post_types as $post_type => $name ) {
430
- if ( array_key_exists( $post_type, $settings ) )
431
- $merged_settings[ $post_type ] = (int) $settings[ $post_type ];
432
- else
433
- $merged_settings[ $post_type ] = -1;
434
- }
435
-
436
- self::$settings = $merged_settings;
437
- }
438
-
439
- return self::$settings;
440
- }
441
-
442
- /**
443
- * Retrieve array of supported post types and their labels
444
- *
445
- * @uses get_post_types
446
- * @uses post_type_supports
447
- * @uses get_post_type_object
448
- * @return array
449
- */
450
- private function get_post_types() {
451
- if ( empty( self::$post_types ) ) {
452
- $types = get_post_types();
453
-
454
- foreach ( $types as $type ) {
455
- if ( post_type_supports( $type, 'revisions' ) ) {
456
- $object = get_post_type_object( $type );
457
-
458
- if ( property_exists( $object, 'labels' ) && property_exists( $object->labels, 'name' ) )
459
- $name = $object->labels->name;
460
- else
461
- $name = $object->name;
462
-
463
- self::$post_types[ $type ] = $name;
464
- }
465
- }
466
- }
467
-
468
- return self::$post_types;
469
- }
470
-
471
- /**
472
- * Retrieve number of revisions to keep for a given post type
473
- *
474
- * @uses WP_Post
475
- * @uses wp_revisions_to_keep
476
- * @return mixed
477
- */
478
- private function get_revisions_to_keep( $post_type, $blank_for_all = false ) {
479
- // wp_revisions_to_keep() accepts a post object, not just the post type
480
- // We construct a new WP_Post object to ensure anything hooked to the wp_revisions_to_keep filter has the same basic data WP provides.
481
- $_post = new WP_Post( (object) array( 'post_type' => $post_type ) );
482
- $to_keep = wp_revisions_to_keep( $_post );
483
-
484
- if ( $blank_for_all && -1 == $to_keep )
485
- return '';
486
- else
487
- return (int) $to_keep;
488
- }
489
-
490
- /**
491
- * Retrieve number of revisions to keep for a give post
492
- *
493
- * @param int $post_id
494
- * @uses get_post_meta
495
- * @return mixed
496
- */
497
- private function get_post_revisions_to_keep( $post_id ) {
498
- $to_keep = get_post_meta( $post_id, $this->meta_key_limit, true );
499
-
500
- if ( -1 == $to_keep || empty( $to_keep ) )
501
- $to_keep = '';
502
- else
503
- $to_keep = (int) $to_keep;
504
-
505
- return $to_keep;
506
- }
507
- }
508
- WP_Revisions_Control::get_instance();
1
  <?php
2
+ /**
3
+ * Load plugin.
4
+ *
5
+ * @package WP_Revisions_Control
6
+ */
7
+
8
+ /**
9
+ * Plugin Name: WP Revisions Control
10
+ * Plugin URI: https://ethitter.com/plugins/wp-revisions-control/
11
+ * Description: Control how many revisions are stored for each post type
12
+ * Author: Erick Hitter
13
+ * Version: 1.3
14
+ * Author URI: https://ethitter.com/
15
+ * Text Domain: wp_revisions_control
16
+ * Domain Path: /languages/
17
+ *
18
+ * This program is free software; you can redistribute it and/or modify
19
+ * it under the terms of the GNU General Public License as published by
20
+ * the Free Software Foundation; either version 2 of the License, or
21
+ * (at your option) any later version.
22
+ *
23
+ * This program is distributed in the hope that it will be useful,
24
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
25
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26
+ * GNU General Public License for more details.
27
+ *
28
+ * You should have received a copy of the GNU General Public License
29
+ * along with this program; if not, write to the Free Software
30
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
31
+ */
32
+
33
+ require_once __DIR__ . '/inc/class-wp-revisions-control-bulk-actions.php';
34
+ require_once __DIR__ . '/inc/class-wp-revisions-control.php';