Code

< 40 %
40-60 %
> 60 %
1
<?php
2
/**
3
 * Plugin Name: Post Glue
4
 * Plugin URI: https://github.com/log-oscon/post-glue/
5
 * Description: Sticky posts for WordPress, improved.
6
 * Version: 1.0.0
7
 * Author: log.OSCON, Lda.
8
 * Author URI: https://log.pt/
9
 * License: GPL-2.0+
10
 * License URI: http://www.gnu.org/licenses/gpl-2.0.txt
11
 * Text Domain: post-glue
12
 * Domain Path: /languages
13
 * GitHub Plugin URI: https://github.com/log-oscon/post-glue
14
 * GitHub Branch: master
15
 */
16
17
if ( ! defined( 'WPINC' ) ) {
18
	die;
19
}
20
21
/**
22
 * Implements plugin functionality.
23
 */
24
class Post_Glue {
25
26
	/**
27
	 * Set sticky meta values on plugin activation.
28
	 */
29 1
	public static function activation() {
30 1
		self::stick_posts( get_option( 'sticky_posts', array() ) );
31 1
	}
32
33
	/**
34
	 * Initialize the plugin.
35
	 */
36 1
	public static function plugins_loaded() {
37 1
		$plugin_basename = plugin_basename( dirname( __FILE__ ) );
38
39 1
		load_plugin_textdomain( 'post-glue', false, $plugin_basename . '/languages' );
40
41 1
		add_action( 'admin_init', array( __CLASS__, 'admin_init' ) );
42 1
		add_action( 'update_option_sticky_posts', array( __CLASS__, 'update_option_sticky_posts' ), 10, 3 );
43 1
		add_action( 'pre_get_posts', array( __CLASS__, 'pre_get_posts' ) );
44 1
		add_filter( 'post_class', array( __CLASS__, 'post_class' ), 10, 3 );
45 1
	}
46
47
	/**
48
	 * Initialize the admin-specific parts of the plugin.
49
	 *
50
	 * Registers a Sticky metabox for every non-hierarchical post type and
51
	 * adds a view filter to the post edit screen.
52
	 */
53 2
	public static function admin_init() {
54
55
		// Get all public, non-hierarchical post types:
56 2
		$post_types = get_post_types( array( 'hierarchical' => false, 'public' => true ) );
57
58
		// Bypass the core post type:
59 2
		$post_types = array_diff( $post_types, array( 'post' ) );
60
61
		/**
62
		 * Filter the list of post types that support stickiness.
63
		 *
64
		 * Defaults to the list of public, non-hierarchical post types.
65
		 *
66
		 * @param array $post_types Post types that support stickiness.
67
		 */
68 2
		$post_types = apply_filters( 'post_glue_post_types', $post_types );
69
70 2
		add_meta_box(
71 2
			'post_glue_meta',
72 2
			__( 'Post Glue', 'post-glue' ),
73 2
			array( __CLASS__, 'admin_meta_box' ),
74 2
			$post_types,
75 2
			'side',
76
			'high'
77 2
		);
78
79 2
		foreach( $post_types as $post_type ) {
80 2
			add_filter( 'views_edit-' . $post_type, array( __CLASS__, 'views_edit' ) );
81 2
		}
82 2
	}
83
84
	/**
85
	 * Render the sticky meta box.
86
	 */
87 1
	public static function admin_meta_box() {
88
		?>
89
		<label for="post-glue-sticky" class="selectit">
90
			<input id="post-glue-sticky" name="sticky" type="checkbox"
91
				value="sticky" <?php checked( is_sticky() ) ?>>
92
			<?php _e( 'Make this post sticky', 'post-glue' ) ?>
93
		</label>
94
		<?php
95 1
	}
96
97
	/**
98
	 * Add sticky post view to the post edit page in the admin.
99
	 *
100
	 * @param  array $views Admin post edit views.
101
	 * @return array        Filtered admin post edit views.
102
	 */
103 1
	public static function views_edit( $views ) {
104 1
		global $wp_query;
105
106 1
		$post_type    = $wp_query->get( 'post_type' );
107 1
		$sticky_posts = array();
108
109 1
		foreach( get_option( 'sticky_posts', array() ) as $post_id ) {
110 1
			if ( get_post_type( $post_id ) === $post_type ) {
111 1
				$sticky_posts[] = $post_id;
112 1
			}
113 1
		}
114
115 1
		$sticky_posts_count = count( $sticky_posts );
116
117 1
		if ( ! $sticky_posts_count ) {
118 1
			return $views;
119
		}
120
121 1
		$sticky_inner_html = sprintf(
122 1
			_nx(
123 1
				'Sticky <span class="count">(%s)</span>',
124 1
				'Sticky <span class="count">(%s)</span>',
125 1
				$sticky_posts_count,
126 1
				'sticky view link',
127
				'post-glue'
128 1
			),
129 1
			number_format_i18n( $sticky_posts_count )
130 1
		);
131
132 1
		$views['sticky'] = sprintf(
133 1
			'<a href="%sedit.php?post_type=%s&show_sticky=1">%s</a>',
134 1
			get_admin_url(),
135 1
			$post_type,
136
			$sticky_inner_html
137 1
		);
138
139 1
		return $views;
140
	}
141
142
	/**
143
	 * Saves post stickiness to the `_sticky` post meta key.
144
	 *
145
	 * @param  mixed  $old_value Previous option value.
146
	 * @param  mixed  $value     New option value.
147
	 * @param  string $option    Option name.
148
	 */
149 1
	public static function update_option_sticky_posts( $old_value, $value, $option ) {
150 1
		$added   = array_diff( $value, $old_value );
151 1
		$removed = array_diff( $old_value, $value );
152
153 1
		self::stick_posts( $added );
154 1
		self::unstick_posts( $removed );
155 1
	}
156
157
	/**
158
	 * Sort posts by stickiness.
159
	 *
160
	 * Changes queries to include sticky posts on the default sort order.
161
	 * Honours the `ignore_sticky_posts` query argument.
162
	 *
163
	 * The meta query added by this action translates to a `LEFT JOIN` where the
164
	 * `_sticky` meta key is checked for both existence and non-existence. The
165
	 * point is to force the WordPress SQL builder to perform a `CAST(meta_value
166
	 * AS SIGNED)` in the `ORDER BY` clause as a sort of poor man's `COALESCE()`.
167
	 *
168
	 * @param WP_Query $query The current query instance, passed by reference.
169
	 */
170 2
	public static function pre_get_posts( $query ) {
171
172
		// Don't alter admin queries:
173 2
		if ( is_admin() ) {
174 1
			return;
175
		}
176
177
		// Ignore sticky posts:
178 1
		if ( $query->get( 'ignore_sticky_posts' ) ) {
179 1
			return;
180
		}
181
182
		// Don't show stickies outside of home, post type or taxonomy archives:
183 1
		if ( ! $query->is_home() && ! $query->is_post_type_archive() && ! $query->is_tax() ) {
184 1
			return;
185
		}
186
187
		// Ignore when querying specific posts:
188 1
		if ( $query->get( 'post__in' ) ) {
189 1
			return;
190
		}
191
192
		// Ignore queries that already provide an order:
193 1
		if ( $query->get( 'orderby' ) ) {
194 1
			return;
195
		}
196
197
		// Ignore queries that already provide a meta query:
198 1
		if ( $query->get( 'meta_query' ) ) {
199 1
			return;
200
		}
201
202
		// Ignore core stickies now:
203 1
		$query->set( 'ignore_sticky_posts', 1 );
204
205 1
		$query->set( 'meta_query', array(
206
			array(
207 1
				'relation' => 'OR',
208
				array(
209 1
					'key'     => '_sticky',
210 1
					'type'    => 'BINARY',
211 1
					'compare' => 'EXISTS',
212 1
				),
213
				'sticky_clause' => array(
214 1
					'key'     => '_sticky',
215 1
					'type'    => 'BINARY',
216 1
					'compare' => 'NOT EXISTS',
217 1
				),
218 1
			),
219 1
		) );
220
221 1
		$query->set( 'orderby', array(
222 1
			'sticky_clause' => 'DESC',
223 1
			'date'          => 'DESC',
224 1
		) );
225 1
	}
226
227
	/**
228
	 * Add a `sticky` HTML class to posts.
229
	 *
230
	 * @param  array  $classes  An array of post classes.
231
	 * @param  array  $class    An array of additional classes added to the post.
232
	 * @param  int    $post_id  The post ID.
233
	 * @return array            Filtered class list.
234
	 */
235 1
	public static function post_class( $classes, $class, $post_id ) {
236 1
		if ( is_sticky( $post_id ) ) {
237 1
			if ( is_home() || is_post_type_archive() || is_tax() ) {
238 1
				$classes[] = 'sticky';
239 1
			}
240 1
		}
241
242 1
		return $classes;
243
	}
244
245
	/**
246
	 * Bulk update _sticky meta values for a group of post IDs.
247
	 *
248
	 * @param  array  $posts List of post IDs.
249
	 */
250 1
	private static function stick_posts( $posts ) {
251 1
		foreach ( $posts as $post_id ) {
252 1
			update_post_meta( $post_id, '_sticky', 1 );
253 1
		}
254 1
	}
255
256
	/**
257
	 * Bulk delete _sticky meta values for a group of post IDs.
258
	 *
259
	 * @param  array  $posts List of post IDs.
260
	 */
261 1
	private static function unstick_posts( $posts ) {
262 1
		foreach ( $posts as $post_id ) {
263 1
			delete_post_meta( $post_id, '_sticky' );
264 1
		}
265 1
	}
266
267
}
268
269
register_activation_hook( __FILE__, array( 'Post_Glue', 'activation' ) );
270
271
add_action( 'plugins_loaded', array( 'Post_Glue', 'plugins_loaded' ) );
272