PluginProbe ʕ •ᴥ•ʔ
Simple Page Ordering / 2.3.4
Simple Page Ordering v2.3.4
2.3.1 2.3.2 2.3.3 2.3.4 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.5.0 2.5.1 2.6.0 2.6.1 2.6.2 2.6.3 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.8.0 trunk 0.8.4 0.9 0.9.1 0.9.5 0.9.6 1.0 2.0 2.1 2.1.1 2.1.2 2.2 2.2.1 2.2.2 2.2.3 2.2.4 2.3
simple-page-ordering / simple-page-ordering.php
simple-page-ordering Last commit date
assets 6 years ago composer.json 6 years ago readme.txt 4 years ago simple-page-ordering.php 6 years ago
simple-page-ordering.php
346 lines
1 <?php
2 /**
3 * Plugin Name: Simple Page Ordering
4 * Plugin URI: http://10up.com/plugins/simple-page-ordering-wordpress/
5 * Description: Order your pages and hierarchical post types using drag and drop on the built in page list. For further instructions, open the "Help" tab on the Pages screen.
6 * Version: 2.3.4
7 * Requires at least: 3.8
8 * Author: Jake Goldman, 10up
9 * Author URI: https://10up.com
10 * License: GPLv2 or later
11 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
12 * Text Domain: simple-page-ordering
13 */
14
15 if ( ! class_exists( 'Simple_Page_Ordering' ) ) :
16
17 class Simple_Page_Ordering {
18
19 /**
20 * Handles initializing this class and returning the singleton instance after it's been cached.
21 *
22 * @return null|Simple_page_Ordering
23 */
24 public static function get_instance() {
25 // Store the instance locally to avoid private static replication
26 static $instance = null;
27
28 if ( null === $instance ) {
29 $instance = new self();
30 self::_add_actions();
31 }
32
33 return $instance;
34 }
35
36 /**
37 * An empty constructor
38 *
39 * Purposely do nothing here
40 */
41 public function __construct() {}
42
43 /**
44 * Handles registering hooks that initialize this plugin.
45 */
46 public static function _add_actions() {
47 add_action( 'load-edit.php', array( __CLASS__, 'load_edit_screen' ) );
48 add_action( 'wp_ajax_simple_page_ordering', array( __CLASS__, 'ajax_simple_page_ordering' ) );
49 add_action( 'plugins_loaded', array( __CLASS__, 'load_textdomain' ) );
50 }
51
52 /**
53 * Loads the plugin textdomain
54 */
55 public static function load_textdomain() {
56 load_plugin_textdomain( 'simple-page-ordering', false, dirname( plugin_basename( __FILE__ ) ) . '/localization/' );
57 }
58
59 /**
60 * Load up page ordering scripts for the edit screen
61 */
62 public static function load_edit_screen() {
63 $screen = get_current_screen();
64 $post_type = $screen->post_type;
65
66 // is post type sortable?
67 $sortable = ( post_type_supports( $post_type, 'page-attributes' ) || is_post_type_hierarchical( $post_type ) ); // check permission
68 $sortable = apply_filters( 'simple_page_ordering_is_sortable', $sortable, $post_type );
69 if ( ! $sortable ) {
70 return;
71 }
72
73 // does user have the right to manage these post objects?
74 if ( ! self::check_edit_others_caps( $post_type ) ) {
75 return;
76 }
77
78 add_filter( 'views_' . $screen->id, array(
79 __CLASS__,
80 'sort_by_order_link',
81 ) ); // add view by menu order to views
82 add_action( 'wp', array( __CLASS__, 'wp' ) );
83 add_action( 'admin_head', array( __CLASS__, 'admin_head' ) );
84 }
85
86 /**
87 * when we load up our posts query, if we're actually sorting by menu order, initialize sorting scripts
88 */
89 public static function wp() {
90 $orderby = get_query_var( 'orderby' );
91 $screen = get_current_screen();
92 if ( ( is_string( $orderby ) && 0 === strpos( $orderby, 'menu_order' ) ) || ( isset( $orderby['menu_order'] ) && 'ASC' === $orderby['menu_order'] ) ) {
93 $script_name = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '/assets/js/src/simple-page-ordering.js' : '/assets/js/simple-page-ordering.min.js';
94 wp_enqueue_script( 'simple-page-ordering', plugins_url( $script_name, __FILE__ ), array( 'jquery-ui-sortable' ), '2.1', true );
95 wp_localize_script(
96 'simple-page-ordering',
97 'simple_page_ordering_localized_data',
98 array(
99 '_wpnonce' => wp_create_nonce( 'simple-page-ordering_' . $screen->id ),
100 'screen_id' => (string) $screen->id,
101 )
102 );
103 wp_enqueue_style( 'simple-page-ordering', plugins_url( '/assets/css/simple-page-ordering.css', __FILE__ ) );
104 }
105 }
106
107 /**
108 * Add page ordering help to the help tab
109 */
110 public static function admin_head() {
111 $screen = get_current_screen();
112 $screen->add_help_tab( array(
113 'id' => 'simple_page_ordering_help_tab',
114 'title' => 'Simple Page Ordering',
115 'content' => '<p>' . __( 'To reposition an item, simply drag and drop the row by "clicking and holding" it anywhere (outside of the links and form controls) and moving it to its new position.', 'simple-page-ordering' ) . '</p>',
116 ) );
117 }
118
119 public static function ajax_simple_page_ordering() {
120 // check and make sure we have what we need
121 if ( empty( $_POST['id'] ) || ( ! isset( $_POST['previd'] ) && ! isset( $_POST['nextid'] ) ) ) {
122 die( - 1 );
123 }
124
125 // do we have a nonce that verifies?
126 if ( empty( $_POST['_wpnonce'] ) || empty( $_POST['screen_id'] ) ) {
127 // no nonce to verify...
128 die( -1 );
129 }
130
131 check_admin_referer( 'simple-page-ordering_' . sanitize_key( $_POST['screen_id'] ) );
132
133 // real post?
134 $post = empty( $_POST['id'] ) ? false : get_post( (int) $_POST['id'] );
135 if ( ! $post ) {
136 die( - 1 );
137 }
138
139 // does user have the right to manage these post objects?
140 if ( ! self::check_edit_others_caps( $post->post_type ) ) {
141 die( - 1 );
142 }
143
144 // Badly written plug-in hooks for save post can break things.
145 if ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) {
146 error_reporting( 0 ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_error_reporting
147 }
148
149 global $wp_version;
150
151 $previd = empty( $_POST['previd'] ) ? false : (int) $_POST['previd'];
152 $nextid = empty( $_POST['nextid'] ) ? false : (int) $_POST['nextid'];
153 $start = empty( $_POST['start'] ) ? 1 : (int) $_POST['start'];
154 $excluded = empty( $_POST['excluded'] ) ? array( $post->ID ) : array_filter( (array) json_decode( $_POST['excluded'] ), 'intval' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
155
156 $new_pos = array(); // store new positions for ajax
157 $return_data = new stdClass;
158
159 do_action( 'simple_page_ordering_pre_order_posts', $post, $start );
160
161 // attempt to get the intended parent... if either sibling has a matching parent ID, use that
162 $parent_id = $post->post_parent;
163 $next_post_parent = $nextid ? wp_get_post_parent_id( $nextid ) : false;
164 if ( $previd === $next_post_parent ) { // if the preceding post is the parent of the next post, move it inside
165 $parent_id = $next_post_parent;
166 } elseif ( $next_post_parent !== $parent_id ) { // otherwise, if the next post's parent isn't the same as our parent, we need to study
167 $prev_post_parent = $previd ? wp_get_post_parent_id( $previd ) : false;
168 if ( $prev_post_parent !== $parent_id ) { // if the previous post is not our parent now, make it so!
169 $parent_id = ( false !== $prev_post_parent ) ? $prev_post_parent : $next_post_parent;
170 }
171 }
172 // if the next post's parent isn't our parent, it might as well be false (irrelevant to our query)
173 if ( $next_post_parent !== $parent_id ) {
174 $nextid = false;
175 }
176
177 $max_sortable_posts = (int) apply_filters( 'simple_page_ordering_limit', 50 ); // should reliably be able to do about 50 at a time
178 if ( $max_sortable_posts < 5 ) { // don't be ridiculous!
179 $max_sortable_posts = 50;
180 }
181
182 // we need to handle all post stati, except trash (in case of custom stati)
183 $post_stati = get_post_stati( array(
184 'show_in_admin_all_list' => true,
185 ) );
186
187 $siblings_query = array(
188 'depth' => 1,
189 'posts_per_page' => $max_sortable_posts,
190 'post_type' => $post->post_type,
191 'post_status' => $post_stati,
192 'post_parent' => $parent_id,
193 'post__not_in' => $excluded, // phpcs:ignore
194 'orderby' => array(
195 'menu_order' => 'ASC',
196 'title' => 'ASC',
197 ),
198 'update_post_term_cache' => false,
199 'update_post_meta_cache' => false,
200 'suppress_filters' => true, // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.SuppressFiltersTrue
201 'ignore_sticky_posts' => true,
202 );
203
204 if ( version_compare( $wp_version, '4.0', '<' ) ) {
205 $siblings_query['orderby'] = 'menu_order title';
206 $siblings_query['order'] = 'ASC';
207 }
208
209 $siblings = new WP_Query( $siblings_query ); // fetch all the siblings (relative ordering)
210
211 // don't waste overhead of revisions on a menu order change (especially since they can't *all* be rolled back at once)
212 remove_action( 'post_updated', 'wp_save_post_revision' );
213
214 foreach ( $siblings->posts as $sibling ) :
215 // don't handle the actual post
216 if ( $sibling->ID === $post->ID ) {
217 continue;
218 }
219
220 // if this is the post that comes after our repositioned post, set our repositioned post position and increment menu order
221 if ( $nextid === $sibling->ID ) {
222 wp_update_post( array(
223 'ID' => $post->ID,
224 'menu_order' => $start,
225 'post_parent' => $parent_id,
226 ) );
227 $ancestors = get_post_ancestors( $post->ID );
228 $new_pos[ $post->ID ] = array(
229 'menu_order' => $start,
230 'post_parent' => $parent_id,
231 'depth' => count( $ancestors ),
232 );
233 $start ++;
234 }
235
236 // if repositioned post has been set, and new items are already in the right order, we can stop
237 if ( isset( $new_pos[ $post->ID ] ) && $sibling->menu_order >= $start ) {
238 $return_data->next = false;
239 break;
240 }
241
242 // set the menu order of the current sibling and increment the menu order
243 if ( $sibling->menu_order !== $start ) {
244 wp_update_post( array(
245 'ID' => $sibling->ID,
246 'menu_order' => $start,
247 ) );
248 }
249 $new_pos[ $sibling->ID ] = $start;
250 $start ++;
251
252 if ( ! $nextid && $previd === $sibling->ID ) {
253 wp_update_post( array(
254 'ID' => $post->ID,
255 'menu_order' => $start,
256 'post_parent' => $parent_id,
257 ) );
258 $ancestors = get_post_ancestors( $post->ID );
259 $new_pos[ $post->ID ] = array(
260 'menu_order' => $start,
261 'post_parent' => $parent_id,
262 'depth' => count( $ancestors ),
263 );
264 $start ++;
265 }
266
267 endforeach;
268
269 // max per request
270 if ( ! isset( $return_data->next ) && $siblings->max_num_pages > 1 ) {
271 $return_data->next = array(
272 'id' => $post->ID,
273 'previd' => $previd,
274 'nextid' => $nextid,
275 'start' => $start,
276 'excluded' => array_merge( array_keys( $new_pos ), $excluded ),
277 );
278 } else {
279 $return_data->next = false;
280 }
281
282 do_action( 'simple_page_ordering_ordered_posts', $post, $new_pos );
283
284 if ( ! $return_data->next ) {
285 // if the moved post has children, we need to refresh the page (unless we're continuing)
286 $children = new WP_Query(
287 array(
288 'posts_per_page' => 1,
289 'post_type' => $post->post_type,
290 'post_status' => $post_stati,
291 'post_parent' => $post->ID,
292 'fields' => 'ids',
293 'update_post_term_cache' => false,
294 'update_post_meta_cache' => false,
295 'ignore_sticky' => true,
296 'no_found_rows' => true,
297 )
298 );
299
300 if ( $children->have_posts() ) {
301 die( 'children' );
302 }
303 }
304
305 $return_data->new_pos = $new_pos;
306 die( wp_json_encode( $return_data ) );
307 }
308
309 /**
310 * Append a sort by order link to the post actions
311 *
312 * @param array $views
313 *
314 * @return array
315 */
316 public static function sort_by_order_link( $views ) {
317 $class = ( get_query_var( 'orderby' ) === 'menu_order title' ) ? 'current' : '';
318 $query_string = remove_query_arg( array( 'orderby', 'order' ) );
319 if ( ! is_post_type_hierarchical( get_post_type() ) ) {
320 $query_string = add_query_arg( 'orderby', 'menu_order title', $query_string );
321 $query_string = add_query_arg( 'order', 'asc', $query_string );
322 }
323 $views['byorder'] = sprintf( '<a href="%s" class="%s">%s</a>', esc_url( $query_string ), $class, __( 'Sort by Order', 'simple-page-ordering' ) );
324
325 return $views;
326 }
327
328 /**
329 * Checks to see if the current user has the capability to "edit others" for a post type
330 *
331 * @param string $post_type Post type name
332 *
333 * @return bool True or false
334 */
335 private static function check_edit_others_caps( $post_type ) {
336 $post_type_object = get_post_type_object( $post_type );
337 $edit_others_cap = empty( $post_type_object ) ? 'edit_others_' . $post_type . 's' : $post_type_object->cap->edit_others_posts;
338
339 return apply_filters( 'simple_page_ordering_edit_rights', current_user_can( $edit_others_cap ), $post_type );
340 }
341 }
342
343 Simple_Page_Ordering::get_instance();
344
345 endif;
346