Changeset 3750663
- Timestamp:
- 10/16/2026 03:19:25 PM (13 days ago)
- Location:
- shy-posts
- Files:
-
- 8 added
- 2 edited
-
tags/2.0.0 (added)
-
tags/2.0.0/LICENSE (added)
-
tags/2.0.0/js (added)
-
tags/2.0.0/js/shy-posts-editor.js (added)
-
tags/2.0.0/readme.txt (added)
-
tags/2.0.0/shy-posts.php (added)
-
trunk/js (added)
-
trunk/js/shy-posts-editor.js (added)
-
trunk/readme.txt (modified) (3 diffs)
-
trunk/shy-posts.php (modified) (7 diffs)
Legend:
- Unmodified
- Added
- Removed
-
shy-posts/trunk/readme.txt
r2086988 r3750663 2 2 Contributors: topher1kenobe 3 3 Tags: posts 4 Requires at least: 3.0 5 Tested up to: 5.2 6 Stable tag: 1.3.3 4 Requires at least: 5.3 5 Requires PHP: 5.6 6 Tested up to: 7.1 7 Stable tag: 2.0.0 7 8 License: GPLv2 or later 8 9 License URI: http://www.gnu.org/licenses/gpl-2.0.html … … 16 17 This only works if your blog is on your homepage. 17 18 18 Requires the Classic Editor.19 Works in both the block editor (in the post status panel of the sidebar) and the Classic Editor (in the Publish box). 19 20 20 21 == Installation == … … 34 35 == Screenshots == 35 36 36 1. The checkbox in the Publish box on the Post editing page 37 1. The checkbox in the Publish box on the Post editing page (Classic Editor) 37 38 38 39 == Changelog == 40 41 = 2.0.0 = 42 * Block editor support: the checkbox now appears in the post status panel of the sidebar 43 * Register the shy_post meta for the REST API 44 * Keep the stored list in sync from post meta changes, so classic, block editor and deleted posts are all handled the same way 45 * Requires WordPress 5.3 or later 46 * Fix activation hook never firing, so existing shy posts are now picked up on activation 47 * Store shy post IDs in an option instead of a transient, so they can't be evicted by an object cache 48 * Rebuild the stored list from post meta automatically if it's missing (this also migrates from 1.3.x) 49 * Fix PHP notice when saving a post with the checkbox unchecked 50 * Fix malformed checkbox HTML 51 * Skip autosaves and revisions on save, and stop storing duplicate IDs 52 * Remove permanently deleted posts from the stored list 53 * Merge with existing post__not_in exclusions on the homepage instead of replacing them 54 * Remove unused composer.json 39 55 40 56 = 1.3.3 = -
shy-posts/trunk/shy-posts.php
r2086988 r3750663 1 1 <?php 2 /*3 Plugin Name: Shy Posts4 Plugin URI: http://codeventure.net5 Description: Provides a mechanism for preventing posts from being rendered on the homepage loop6 Author: Topher7 Version: 1.3.38 Author URI: http://codeventure.net9 */10 11 2 /** 12 * Provides a mechanism for preventing posts from being rendered on the homepage loop 3 * Plugin Name: Shy Posts 4 * Plugin URI: http://codeventure.net 5 * Description: Provides a mechanism for preventing posts from being rendered on the homepage loop 6 * Author: Topher 7 * Version: 2.0.0 8 * Author URI: http://codeventure.net 9 * Text Domain: shy-posts 10 * License: GPLv2 or later 13 11 * 14 12 * @package Shy_Posts … … 17 15 */ 18 16 17 // The main plugin file can't be renamed to class-shy-posts.php without deactivating the plugin on existing sites. 18 // phpcs:disable WordPress.Files.FileName 19 20 /** 21 * Build the list of shy post IDs on activation. 22 * This must be registered when the file loads, not on plugins_loaded, or it never fires. 23 * 24 * @since Shy_Posts 2.0 25 */ 26 register_activation_hook( __FILE__, array( 'Shy_Posts', 'install' ) ); 19 27 20 28 /** 21 29 * Instantiate the Shy_Posts instance 30 * 22 31 * @since Shy_Posts 1.0 23 32 */ … … 30 39 * 31 40 * @class Shy_Posts 32 * @version 1.3.341 * @version 2.0.0 33 42 * @since 1.0 34 43 * @package Shy_Posts … … 38 47 39 48 /** 40 * Holds array of post IDs that are shy. 41 * 42 * @access private 43 * @since 1.2 44 * @var array 45 */ 46 private $shy_post_ids = NULL; 47 48 /** 49 * Transient name 50 * 51 * @access private 52 * @since 1.2 53 * @var string 54 */ 55 private $shy_post_transient_name = 'shy_posts_transient'; 56 57 /** 58 * Instance handle 59 * 60 * @static 61 * @since 1.2 62 * @var string 63 */ 64 private static $__instance = null; 65 49 * Holds array of post IDs that are shy. NULL until loaded. 50 * 51 * @access private 52 * @since 1.2 53 * @var array|null 54 */ 55 private $shy_post_ids = null; 56 57 /** 58 * Option name the shy post IDs are stored in 59 * 60 * @since 2.0 61 * @var string 62 */ 63 const OPTION_NAME = 'shy_posts_ids'; 64 65 /** 66 * Transient name used by versions before 2.0, deleted on rebuild 67 * 68 * @since 2.0 69 * @var string 70 */ 71 const LEGACY_TRANSIENT_NAME = 'shy_posts_transient'; 72 73 /** 74 * Post meta key that marks a post as shy 75 * 76 * @since 2.0 77 * @var string 78 */ 79 const META_KEY = 'shy_post'; 80 81 /** 82 * Plugin version, used to bust the editor script cache 83 * 84 * @since 2.0 85 * @var string 86 */ 87 const VERSION = '2.0.0'; 88 89 /** 90 * Instance handle 91 * 92 * @static 93 * @since 1.2 94 * @var Shy_Posts 95 */ 96 private static $instance = null; 97 66 98 /** 67 99 * Shy_Posts Constructor, actually contains nothing 68 100 * 69 * @access p ublic70 * @return void 71 */ 72 private function __construct() {}73 101 * @access private 102 * @return void 103 */ 104 private function __construct() {} 105 74 106 /** 75 107 * Instance initiator, runs setup etc. … … 78 110 * @return self 79 111 */ 80 public static function instance() {81 if ( ! is_a( self::$__instance, __CLASS__ ) ) {82 self::$__instance = new self;83 self::$__instance->setup();84 }85 86 return self::$__instance;87 }88 112 public static function instance() { 113 if ( ! is_a( self::$instance, __CLASS__ ) ) { 114 self::$instance = new self(); 115 self::$instance->setup(); 116 } 117 118 return self::$instance; 119 } 120 89 121 /** 90 122 * Runs things that would normally be in __construct … … 93 125 * @return void 94 126 */ 95 private function setup() { 96 // get the shy posts transient 97 $this->get_shy_post_ids(); 98 99 register_activation_hook( __FILE__, array( 'Shy_Posts', 'install' ) ); 100 101 // only do this in the admin area 127 private function setup() { 128 // The meta is exposed to the REST API for the block editor, so register it everywhere. 129 add_action( 'init', array( $this, 'register_meta' ) ); 130 131 // Keep the stored list in sync however the meta changes: classic editor, block editor (REST), or post deletion. 132 add_action( 'added_post_meta', array( $this, 'meta_changed' ), 10, 4 ); 133 add_action( 'updated_post_meta', array( $this, 'meta_changed' ), 10, 4 ); 134 add_action( 'deleted_post_meta', array( $this, 'meta_deleted' ), 10, 3 ); 135 136 // Only do this in the admin area. 102 137 if ( is_admin() ) { 103 138 add_action( 'save_post', array( $this, 'save' ) ); 104 139 add_action( 'post_submitbox_misc_actions', array( $this, 'option_hide_in_publish' ) ); 140 add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_block_editor_assets' ) ); 105 141 add_filter( 'is_protected_meta', array( $this, 'hide_field' ), 10, 2 ); 106 142 add_action( 'init', array( $this, 'load_textdomain' ) ); 107 143 } 108 144 109 // only do this NOT in the admin area110 if ( ! is_admin() ) {111 // filter the homepage loop145 // Only do this NOT in the admin area. 146 if ( ! is_admin() ) { 147 // Filter the homepage loop. 112 148 add_action( 'pre_get_posts', array( $this, 'exclude_shy_posts_from_homepage' ) ); 113 149 } 114 }115 116 /** 117 * On activation, get all the post ids from post meta data and store them in the transient150 } 151 152 /** 153 * On activation, rebuild the stored list of shy post IDs from post meta 118 154 * 119 155 * @static 120 156 * @return void 121 157 */ 122 static function install() { 158 public static function install() { 159 self::rebuild_shy_post_ids(); 160 } 161 162 /** 163 * Query post meta for every shy post and store the IDs in the option. 164 * Post meta is the source of truth; the option is just a fast lookup. 165 * 166 * @static 167 * @since 2.0 168 * @return array 169 */ 170 public static function rebuild_shy_post_ids() { 123 171 124 172 $args = array( 125 'post_status' => 'publish' 126 ,'post_type' => 'post' 127 ,'no_found_rows' => true 128 ,'nopaging' => true 129 ,'fields' => 'ids' 130 ,'meta_query' => array( 173 'post_type' => get_post_types(), 174 'post_status' => 'any', 175 'no_found_rows' => true, 176 'nopaging' => true, 177 'fields' => 'ids', 178 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Only runs on activation or when the stored list is missing. 179 'meta_query' => array( 131 180 array( 132 'key' => 'shy_post', 133 'value' => '1', 134 'compare' => '=' 135 ) 181 'key' => self::META_KEY, 182 'value' => '1', 183 'compare' => '=', 184 ), 185 ), 186 ); 187 188 $shy_post_ids = array_map( 'intval', get_posts( $args ) ); 189 190 update_option( self::OPTION_NAME, $shy_post_ids ); 191 delete_transient( self::LEGACY_TRANSIENT_NAME ); 192 193 return $shy_post_ids; 194 } 195 196 /** 197 * Load plugin textdomain. 198 * 199 * @access public 200 * @return void 201 */ 202 public function load_textdomain() { 203 load_plugin_textdomain( 'shy-posts', false, plugin_basename( __DIR__ ) ); 204 } 205 206 /** 207 * Filter the post meta field so it doesn't appear in the Custom Fields list 208 * 209 * @access public 210 * @param bool $is_protected Whether the meta key is protected. 211 * @param string $meta_key Meta key being checked. 212 * @return bool 213 */ 214 public function hide_field( $is_protected, $meta_key ) { 215 if ( self::META_KEY === $meta_key ) { 216 $is_protected = true; 217 } 218 return $is_protected; 219 } 220 221 /** 222 * Get shy post IDs, rebuilding the stored list if it's missing 223 * 224 * @access public 225 * @return array 226 */ 227 public function get_shy_post_ids() { 228 229 if ( is_array( $this->shy_post_ids ) ) { 230 return $this->shy_post_ids; 231 } 232 233 $shy_post_ids = get_option( self::OPTION_NAME, false ); 234 235 // Missing (fresh install, or upgrading from the transient) so build it from post meta. 236 if ( ! is_array( $shy_post_ids ) ) { 237 $shy_post_ids = self::rebuild_shy_post_ids(); 238 } 239 240 $this->shy_post_ids = $shy_post_ids; 241 242 return $this->shy_post_ids; 243 } 244 245 /** 246 * Add or remove a post ID from the stored list 247 * 248 * @access private 249 * @param int $post_id Post to add or remove. 250 * @param bool $is_shy True to add the post, false to remove it. 251 * @return void 252 */ 253 private function update_shy_post_ids( $post_id, $is_shy ) { 254 255 $post_id = (int) $post_id; 256 $shy_post_ids = $this->get_shy_post_ids(); 257 258 if ( $is_shy ) { 259 $shy_post_ids[] = $post_id; 260 } else { 261 $shy_post_ids = array_diff( $shy_post_ids, array( $post_id ) ); 262 } 263 264 $this->shy_post_ids = array_values( array_unique( array_map( 'intval', $shy_post_ids ) ) ); 265 266 // Save it back. 267 update_option( self::OPTION_NAME, $this->shy_post_ids ); 268 } 269 270 /** 271 * Places checkbox in the Publish meta box 272 * 273 * @access public 274 * @return void 275 */ 276 public function option_hide_in_publish() { 277 278 global $post; 279 280 // Use nonce for verification. 281 wp_nonce_field( plugin_basename( __FILE__ ), 'shyposts_nonce' ); 282 283 // Get the data. 284 $value = get_post_meta( $post->ID, self::META_KEY, true ); 285 286 $title = __( 'Removes this post from the homepage, but NOT from any other page', 'shy-posts' ); 287 288 // Echo the meta box. 289 echo '<div class="misc-pub-section misc-pub-section-last">'; 290 echo '<input type="checkbox" id="shyposts_hide_field" name="shyposts_hide_field" value="1" ' . checked( $value, '1', false ) . ' title="' . esc_attr( $title ) . '"> '; 291 echo '<label for="shyposts_hide_field" title="' . esc_attr( $title ) . '">'; 292 echo esc_html__( 'Hide on the homepage?', 'shy-posts' ); 293 echo '</label> '; 294 echo '</div>'; 295 } 296 297 298 /** 299 * Updates post meta and the stored list with the form data 300 * 301 * @access public 302 * @param int $post_id Post being saved. 303 * @return void 304 */ 305 public function save( $post_id ) { 306 307 // Autosaves and revisions don't carry the checkbox's meaning. 308 if ( ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) || wp_is_post_revision( $post_id ) ) { 309 return; 310 } 311 312 // Check if the user intended to change this value. 313 if ( ! isset( $_POST['shyposts_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['shyposts_nonce'] ) ), plugin_basename( __FILE__ ) ) ) { 314 return; 315 } 316 317 // Check if the current user is authorised to do this action. 318 $post = get_post( $post_id ); 319 if ( ! $post ) { 320 return; 321 } 322 323 $post_type = get_post_type_object( $post->post_type ); 324 if ( ! $post_type || ! current_user_can( $post_type->cap->edit_post, $post_id ) ) { 325 return; 326 } 327 328 // Unchecked checkboxes aren't submitted at all. 329 $is_shy = isset( $_POST['shyposts_hide_field'] ) && '1' === $_POST['shyposts_hide_field']; 330 331 // The meta hooks update the stored list. 332 if ( $is_shy ) { 333 update_post_meta( $post_id, self::META_KEY, '1' ); 334 } else { 335 delete_post_meta( $post_id, self::META_KEY ); 336 } 337 } 338 339 /** 340 * Register the meta so the block editor can read and write it over REST 341 * 342 * @access public 343 * @since 2.0 344 * @return void 345 */ 346 public function register_meta() { 347 register_post_meta( 348 '', 349 self::META_KEY, 350 array( 351 'type' => 'boolean', 352 'single' => true, 353 'default' => false, 354 'show_in_rest' => true, 355 'sanitize_callback' => array( $this, 'sanitize_meta' ), 356 // The key is protected, so editing over REST needs an explicit auth callback. 357 'auth_callback' => array( $this, 'can_edit_meta' ), 136 358 ) 137 359 ); 138 139 $shy_post_ids = get_posts($args); 140 141 set_transient( 'shy_posts_transient', $shy_post_ids, 60 * 60 * 24 * 365 ); 142 143 } 144 /** 145 * Load plugin textdomain. 146 * 147 * @access public 148 * @return void 149 */ 150 public function load_textdomain() { 151 load_plugin_textdomain( 'shy-posts', false, plugin_basename( dirname( __FILE__ ) ) ); 152 } 153 154 /** 155 * Filter the post meta field so it doesn't appear in the Custom Fields list 156 * 157 * @access private 360 } 361 362 /** 363 * Store the flag the same way the classic editor always has: '1' or '' 364 * 365 * @access public 366 * @since 2.0 367 * @param mixed $value Submitted meta value. 368 * @return string 369 */ 370 public function sanitize_meta( $value ) { 371 return rest_sanitize_boolean( $value ) ? '1' : ''; 372 } 373 374 /** 375 * Only users who can edit the post can change its shy flag 376 * 377 * @access public 378 * @since 2.0 379 * @param bool $allowed Whether the user can edit the meta, before this check. 380 * @param string $meta_key Meta key being edited. 381 * @param int $post_id Post the meta belongs to. 158 382 * @return bool 159 383 */ 160 public function hide_field( $protected, $meta_key ) { 161 if ( 'shy_post' == $meta_key ) { 162 $protected = true; 163 } 164 return $protected; 165 } 166 167 /** 168 * Get shy post IDs 169 * 170 * @access public 171 * @return array 172 */ 173 public function get_shy_post_ids() { 174 175 $shy_post_ids = get_transient( $this->shy_post_transient_name ); 176 177 if(!is_array($shy_post_ids)) { 178 $shy_post_ids = array(); 179 } 180 181 $this->shy_post_ids = $shy_post_ids; 182 183 } 184 185 /** 186 * Update the shy_posts_transient 187 * 188 * @access private 189 * @return void 190 */ 191 private function update_transient( $post_id, $shydata) { 192 193 194 if($shydata != 1) { 195 // invert the post ids array so we can delete by key 196 $shy_post_ids_flipped = array_flip($this->shy_post_ids); 197 198 // delete the one we don't want anymore 199 unset($shy_post_ids_flipped[$post_id]); 200 201 // flip it back 202 $this->shy_post_ids = array_flip($shy_post_ids_flipped); 203 } else { 204 // add this post_id to the array 205 $this->shy_post_ids[] = $post_id; 206 } 207 208 // save it back 209 set_transient( $this->shy_post_transient_name, $this->shy_post_ids, 60 * 60 * 24 * 365 ); 210 211 } 212 213 /** 214 * Places checkbox in the Publish meta box 215 * 216 * @access public 217 * @return void 218 */ 219 public function option_hide_in_publish() { 220 221 global $post; 222 223 // Use nonce for verification 224 wp_nonce_field( plugin_basename( __FILE__ ), 'shyposts_nonce' ); 225 226 // The actual fields for data entry 227 // Get the data 228 $value = get_post_meta( $post->ID, 'shy_post', true ); 229 230 // get the value of the option we want 231 $checked = $value; 232 233 // echo the meta box 234 echo '<div class="misc-pub-section misc-pub-section-last">'; 235 echo '<input type="checkbox" id="shyposts_hide_field" name="shyposts_hide_field" value="1" ' . checked( $checked, true, false ) . '" title="' . esc_attr__('Removes this post from the homepage, but NOT from any other page', 'shy-posts') . '"> '; 236 echo '<label for="shyposts_hide_field" title="' . esc_attr__('Removes this post from the homepage, but NOT from any other page', 'shy-posts') . '">'; 237 echo __( 'Hide on the homepage?', 'shy-posts'); 238 echo '</label> '; 239 echo '</div>'; 240 } 241 242 243 /** 244 * Updates the options table with the form data 245 * 246 * @access public 247 * @param int $post_id 248 * @return void 249 */ 250 public function save( $post_id ) { 251 252 // Check if the current user is authorised to do this action. 253 $post_type = get_post_type_object( get_post( $post_id )->post_type ); 254 if ( ! current_user_can( $post_type->cap->edit_post, $post_id ) ) { 255 return; 256 } 257 258 // Check if the user intended to change this value. 259 if ( ! isset( $_POST['shyposts_nonce'] ) || ! wp_verify_nonce( $_POST['shyposts_nonce'], plugin_basename( __FILE__ ) ) ) 260 return; 261 262 // Sanitize user input 263 $shydata = sanitize_text_field( $_POST['shyposts_hide_field'] ); 264 265 // Update or create the key/value 266 update_post_meta( $post_id, 'shy_post', $shydata ); 267 268 $this->update_transient( $post_id, $shydata ); 269 384 public function can_edit_meta( $allowed, $meta_key, $post_id ) { 385 return current_user_can( 'edit_post', $post_id ); 386 } 387 388 /** 389 * Load the block editor sidebar control 390 * 391 * @access public 392 * @since 2.0 393 * @return void 394 */ 395 public function enqueue_block_editor_assets() { 396 // This hook also fires in the site and widget editors, which have no post to flag. 397 $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; 398 if ( ! $screen || 'post' !== $screen->base ) { 399 return; 400 } 401 402 wp_enqueue_script( 403 'shy-posts-editor', 404 plugins_url( 'js/shy-posts-editor.js', __FILE__ ), 405 array( 'wp-plugins', 'wp-edit-post', 'wp-editor', 'wp-element', 'wp-components', 'wp-data', 'wp-i18n' ), 406 self::VERSION, 407 true 408 ); 409 410 wp_set_script_translations( 'shy-posts-editor', 'shy-posts' ); 411 } 412 413 /** 414 * Update the stored list when the shy meta is added or changed 415 * 416 * @access public 417 * @since 2.0 418 * @param int $meta_id ID of the meta row. 419 * @param int $post_id Post the meta belongs to. 420 * @param string $meta_key Meta key that changed. 421 * @param mixed $meta_value New meta value. 422 * @return void 423 */ 424 public function meta_changed( $meta_id, $post_id, $meta_key, $meta_value ) { 425 if ( self::META_KEY !== $meta_key || wp_is_post_revision( $post_id ) ) { 426 return; 427 } 428 429 $this->update_shy_post_ids( $post_id, '1' === (string) $meta_value ); 430 } 431 432 /** 433 * Update the stored list when the shy meta is deleted, including when its post is deleted. 434 * Trashed posts keep their meta, so they're still shy if restored. 435 * 436 * @access public 437 * @since 2.0 438 * @param array $meta_ids IDs of the deleted meta rows. 439 * @param int $post_id Post the meta belonged to. 440 * @param string $meta_key Meta key that was deleted. 441 * @return void 442 */ 443 public function meta_deleted( $meta_ids, $post_id, $meta_key ) { 444 if ( self::META_KEY !== $meta_key ) { 445 return; 446 } 447 448 $this->update_shy_post_ids( $post_id, false ); 270 449 } 271 450 … … 274 453 * 275 454 * @access public 276 * @param mixed $query455 * @param WP_Query $query Query about to run. 277 456 * @return void 278 457 */ 279 458 public function exclude_shy_posts_from_homepage( $query ) { 280 // make sure we're looking in the right place 281 if ( is_front_page() AND $query->is_main_query() ) { 282 283 // exclude all posts in the array of hidden posts 284 $query->set('post__not_in', $this->shy_post_ids); 285 286 } 287 } 288 289 // end class 459 // Make sure we're looking in the right place. 460 if ( ! $query->is_main_query() || ! $query->is_front_page() ) { 461 return; 462 } 463 464 $shy_post_ids = $this->get_shy_post_ids(); 465 if ( empty( $shy_post_ids ) ) { 466 return; 467 } 468 469 // Add to any exclusions something else already set, rather than replacing them. 470 $not_in = array_merge( (array) $query->get( 'post__not_in' ), $shy_post_ids ); 471 472 $query->set( 'post__not_in', array_values( array_unique( array_map( 'intval', $not_in ) ) ) ); 473 } 474 290 475 }
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)