Plugin Directory

Changeset 3648066


Ignore:
Timestamp:
08/15/2026 12:13:55 AM (2 months ago)
Author:
fernandot
Message:

Visibility options now show in the block editor in any admin language, hidden posts no longer vanish from unrelated queries, and a PHP 8.5 deprecation notice is fixed.

Location:
post-visibility-control
Files:
18 added
8 edited

Legend:

Unmodified
Added
Removed
  • post-visibility-control

    • Property svn:ignore
      •  

        old new  
        11AUDITORIA-SEGURIDAD-2026-06-01.md
         2AUDITORIA-SEGURIDAD-2026-07-03.md
         3.claude
         4roadmap-post-visibility-control.md
  • post-visibility-control/trunk/assets/css/admin.css

    r3441562 r3648066  
    2929}
    3030
    31 /* Block Editor - Options container in Status popup */
    32 .ayudawp-pvc-block-options {
    33         padding: 0 0 16px 0;
    34         border-bottom: 1px solid #e0e0e0;
    35         margin-bottom: 16px;
     31/* Block Editor - Options row in the Summary panel (PluginPostStatusInfo) */
     32.ayudawp-pvc-block-options .ayudawp-pvc-block-options-inner {
     33        width: 100%;
    3634}
    37 
    38 .ayudawp-pvc-block-options .ayudawp-pvc-title {
    39         font-size: 13px;
    40         font-weight: 600;
    41         margin: 0 0 12px;
    42         color: #1e1e1e;
    43 }
    44 
    45 .ayudawp-pvc-block-options .ayudawp-pvc-description {
    46         color: #757575;
    47         font-size: 12px;
    48         margin: 0 0 12px;
    49         line-height: 1.4;
    50 }
    51 
    52 .ayudawp-pvc-block-options label {
    53         display: flex;
    54         align-items: center;
    55         margin-bottom: 8px;
    56         cursor: pointer;
    57         font-size: 13px;
    58 }
    59 
    60 .ayudawp-pvc-block-options input[type="radio"] {
    61         margin: 0 8px 0 0;
    62 }
  • post-visibility-control/trunk/assets/js/block-editor.js

    r3441562 r3648066  
    22 * Block Editor JavaScript for Post Visibility Control.
    33 *
    4  * Injects visibility options into the Status & Visibility popup.
     4 * Registers a panel in the Summary section of the document sidebar
     5 * using the official PluginPostStatusInfo API.
    56 *
    67 * @package PostVisibilityControl
    … …  
    89 */
    910
    10 ( function() {
     11( function( wp, config ) {
    1112        'use strict';
    1213
    13         var COMPONENT_ID = 'ayudawp-pvc-visibility-options';
    14         var currentVisibility = 'visible';
     14        if ( ! wp || ! config ) {
     15                return;
     16        }
     17
     18        // Prefer the wp.editor slot (WP 6.6+); fall back to wp.editPost on older versions.
     19        var slotPackage = ( wp.editor && wp.editor.PluginPostStatusInfo ) ? wp.editor : wp.editPost;
     20
     21        if ( ! wp.plugins || ! slotPackage || ! slotPackage.PluginPostStatusInfo ) {
     22                return;
     23        }
     24
     25        var el                   = wp.element.createElement;
     26        var PluginPostStatusInfo = slotPackage.PluginPostStatusInfo;
     27        var RadioControl         = wp.components.RadioControl;
     28        var compose              = wp.compose.compose;
     29        var withSelect           = wp.data.withSelect;
     30        var withDispatch         = wp.data.withDispatch;
     31
     32        var radioOptions = Object.keys( config.options ).map( function( value ) {
     33                return { value: value, label: config.options[ value ] };
     34        } );
    1535
    1636        /**
    17          * Get current visibility from post meta.
     37         * Render the visibility options row.
     38         *
     39         * @param {Object} props Component props.
     40         * @return {Object} Element.
    1841         */
    19         function getCurrentVisibility() {
    20                 var meta = wp.data.select( 'core/editor' ).getEditedPostAttribute( 'meta' );
    21                 if ( meta && meta[ ayudawpPVCBlock.metaKey ] ) {
    22                         return meta[ ayudawpPVCBlock.metaKey ];
    23                 }
    24                 return 'visible';
     42        function VisibilityPanel( props ) {
     43                return el(
     44                        PluginPostStatusInfo,
     45                        { className: 'ayudawp-pvc-block-options' },
     46                        el(
     47                                'div',
     48                                { className: 'ayudawp-pvc-block-options-inner' },
     49                                el( RadioControl, {
     50                                        label: config.strings.title,
     51                                        help: config.strings.description,
     52                                        selected: props.visibility,
     53                                        options: radioOptions,
     54                                        onChange: props.setVisibility
     55                                } )
     56                        )
     57                );
    2558        }
    2659
    27         /**
    28          * Update visibility in post meta.
    29          */
    30         function updateVisibility( newValue ) {
    31                 var meta = {};
    32                 meta[ ayudawpPVCBlock.metaKey ] = newValue;
    33                 wp.data.dispatch( 'core/editor' ).editPost( { meta: meta } );
    34                 currentVisibility = newValue;
    35         }
     60        var VisibilityPanelWithData = compose(
     61                withSelect( function( select ) {
     62                        var meta = select( 'core/editor' ).getEditedPostAttribute( 'meta' );
    3663
    37         /**
    38          * Create the visibility options HTML.
    39          */
    40         function createOptionsElement() {
    41                 currentVisibility = getCurrentVisibility();
     64                        return {
     65                                visibility: ( meta && meta[ config.metaKey ] ) ? meta[ config.metaKey ] : 'visible'
     66                        };
     67                } ),
     68                withDispatch( function( dispatch ) {
     69                        return {
     70                                setVisibility: function( value ) {
     71                                        var meta = {};
     72                                        meta[ config.metaKey ] = value;
     73                                        dispatch( 'core/editor' ).editPost( { meta: meta } );
     74                                }
     75                        };
     76                } )
     77        )( VisibilityPanel );
    4278
    43                 var wrapper = document.createElement( 'div' );
    44                 wrapper.id = COMPONENT_ID;
    45                 wrapper.className = 'ayudawp-pvc-block-options';
     79        wp.plugins.registerPlugin( 'ayudawp-post-visibility-control', {
     80                render: VisibilityPanelWithData
     81        } );
    4682
    47                 // Title.
    48                 var title = document.createElement( 'div' );
    49                 title.className = 'ayudawp-pvc-title';
    50                 title.textContent = ayudawpPVCBlock.strings.title;
    51                 wrapper.appendChild( title );
    52 
    53                 // Description.
    54                 var desc = document.createElement( 'p' );
    55                 desc.className = 'ayudawp-pvc-description';
    56                 desc.textContent = ayudawpPVCBlock.strings.description;
    57                 wrapper.appendChild( desc );
    58 
    59                 // Radio options.
    60                 Object.keys( ayudawpPVCBlock.options ).forEach( function( value ) {
    61                         var label = document.createElement( 'label' );
    62 
    63                         var input = document.createElement( 'input' );
    64                         input.type = 'radio';
    65                         input.name = 'ayudawp_pvc_visibility';
    66                         input.value = value;
    67                         input.checked = ( value === currentVisibility );
    68 
    69                         input.addEventListener( 'change', function( e ) {
    70                                 if ( e.target.checked ) {
    71                                         updateVisibility( e.target.value );
    72                                 }
    73                         } );
    74 
    75                         var text = document.createElement( 'span' );
    76                         text.textContent = ayudawpPVCBlock.options[ value ];
    77 
    78                         label.appendChild( input );
    79                         label.appendChild( text );
    80                         wrapper.appendChild( label );
    81                 } );
    82 
    83                 return wrapper;
    84         }
    85 
    86         /**
    87          * Find the status popover and inject options.
    88          */
    89         function injectOptions() {
    90                 // Already injected?
    91                 if ( document.getElementById( COMPONENT_ID ) ) {
    92                         updateRadioSelection();
    93                         return;
    94                 }
    95 
    96                 // Find the popover by looking for status radio buttons.
    97                 // The popover contains radio buttons for Draft, Pending, Private, etc.
    98                 var statusPopover = null;
    99                 var popovers = document.querySelectorAll( '.components-popover__content' );
    100                
    101                 popovers.forEach( function( popover ) {
    102                         // Check if this popover contains the post status options.
    103                         var hasStatusRadios = popover.querySelector( 'input[type="radio"]' );
    104                         var hasPasswordCheckbox = popover.querySelector( 'input[type="checkbox"]' );
    105                        
    106                         // The status popover has both radio buttons and checkboxes.
    107                         if ( hasStatusRadios && hasPasswordCheckbox ) {
    108                                 // Verify it's the status popover by checking for specific elements.
    109                                 var textContent = popover.textContent || '';
    110                                 if ( textContent.indexOf( 'Protegido' ) !== -1 ||
    111                                      textContent.indexOf( 'Password' ) !== -1 ||
    112                                      textContent.indexOf( 'Fijo' ) !== -1 ||
    113                                      textContent.indexOf( 'Sticky' ) !== -1 ) {
    114                                         statusPopover = popover;
    115                                 }
    116                         }
    117                 } );
    118 
    119                 if ( ! statusPopover ) {
    120                         return;
    121                 }
    122 
    123                 // Find the password protection checkbox container.
    124                 var checkboxes = statusPopover.querySelectorAll( 'input[type="checkbox"]' );
    125                 var passwordCheckbox = null;
    126                
    127                 checkboxes.forEach( function( checkbox ) {
    128                         var container = checkbox.closest( '.components-base-control' ) || checkbox.closest( 'label' ) || checkbox.parentElement;
    129                         if ( container ) {
    130                                 var text = container.textContent || '';
    131                                 if ( text.indexOf( 'Protegido' ) !== -1 || text.indexOf( 'Password' ) !== -1 || text.indexOf( 'contraseña' ) !== -1 ) {
    132                                         passwordCheckbox = container;
    133                                 }
    134                         }
    135                 } );
    136 
    137                 // If we found the password checkbox, insert before it.
    138                 if ( passwordCheckbox ) {
    139                         var optionsElement = createOptionsElement();
    140                         passwordCheckbox.parentElement.insertBefore( optionsElement, passwordCheckbox );
    141                         return;
    142                 }
    143 
    144                 // Alternative: find the sticky checkbox and insert before it.
    145                 checkboxes.forEach( function( checkbox ) {
    146                         var container = checkbox.closest( '.components-base-control' ) || checkbox.closest( 'label' ) || checkbox.parentElement;
    147                         if ( container ) {
    148                                 var text = container.textContent || '';
    149                                 if ( text.indexOf( 'Fijo' ) !== -1 || text.indexOf( 'Sticky' ) !== -1 ) {
    150                                         var optionsElement = createOptionsElement();
    151                                         container.parentElement.insertBefore( optionsElement, container );
    152                                 }
    153                         }
    154                 } );
    155         }
    156 
    157         /**
    158          * Update radio selection if visibility changed externally.
    159          */
    160         function updateRadioSelection() {
    161                 var newVisibility = getCurrentVisibility();
    162                 if ( newVisibility !== currentVisibility ) {
    163                         var input = document.querySelector( '#' + COMPONENT_ID + ' input[value="' + newVisibility + '"]' );
    164                         if ( input && ! input.checked ) {
    165                                 input.checked = true;
    166                                 currentVisibility = newVisibility;
    167                         }
    168                 }
    169         }
    170 
    171         /**
    172          * Initialize.
    173          */
    174         function init() {
    175                 wp.domReady( function() {
    176                         // Observe DOM for popover opening.
    177                         var observer = new MutationObserver( function() {
    178                                 injectOptions();
    179                         } );
    180 
    181                         observer.observe( document.body, {
    182                                 childList: true,
    183                                 subtree: true
    184                         } );
    185 
    186                         // Subscribe to editor changes.
    187                         wp.data.subscribe( function() {
    188                                 if ( document.getElementById( COMPONENT_ID ) ) {
    189                                         updateRadioSelection();
    190                                 }
    191                         } );
    192                 } );
    193         }
    194 
    195         init();
    196 
    197 } )();
     83} )( window.wp, window.ayudawpPVCBlock );
  • post-visibility-control/trunk/includes/block-editor.php

    r3441562 r3648066  
    33 * Block Editor integration for Post Visibility Control.
    44 *
    5  * Adds visibility options to the Status & Visibility modal in Gutenberg.
     5 * Adds visibility options to the Summary panel of the document sidebar
     6 * through the PluginPostStatusInfo slot.
    67 *
    78 * @package PostVisibilityControl
    … …  
    5354                                'wp-components',
    5455                                'wp-data',
    55                                 'wp-dom-ready',
    56                                 'wp-i18n',
     56                                'wp-compose',
    5757                        ),
    5858                        AYUDAWP_PVC_VERSION,
    … …  
    7373                        )
    7474                );
    75 
    76                 // Set translations.
    77                 wp_set_script_translations( 'ayudawp-pvc-block-editor', 'post-visibility-control' );
    7875        }
    7976}
  • post-visibility-control/trunk/includes/frontend-filters.php

    r3458727 r3648066  
    2222
    2323        /**
     24         * Internal query var used to mark queries that need filtering.
     25         *
     26         * @since 1.1.0
     27         * @var string
     28         */
     29        const CONTEXT_QUERY_VAR = 'ayudawp_pvc_context';
     30
     31        /**
    2432         * Constructor.
    2533         *
    … …  
    3341
    3442                add_action( 'pre_get_posts', array( $this, 'filter_queries' ) );
     43                add_filter( 'posts_where', array( $this, 'exclude_posts_where' ), 10, 2 );
    3544        }
    3645
    3746        /**
    38          * Filter posts from search and archive queries.
     47         * Mark search and archive queries that need visibility filtering.
     48         *
     49         * The SQL is appended later by exclude_posts_where(), which only acts on
     50         * queries marked here. This keeps the exclusion scoped to the query that
     51         * triggered it instead of leaking into every later query of the request.
    3952         *
    4053         * @since 1.0.0
    … …  
    6881                }
    6982
    70                 // Build meta query to exclude hidden posts.
    71                 $visibility_to_exclude = array();
     83                if ( $query->is_search() ) {
     84                        $query->set( self::CONTEXT_QUERY_VAR, 'search' );
     85                } elseif ( $query->is_archive() || $query->is_home() || $query->is_front_page() ) {
     86                        $query->set( self::CONTEXT_QUERY_VAR, 'archive' );
     87                }
     88        }
    7289
    73                 if ( $query->is_search() ) {
    74                         // Exclude posts hidden from search.
    75                         $visibility_to_exclude = array( 'archive_only', 'hidden' );
    76                 } elseif ( $query->is_archive() || $query->is_home() || $query->is_front_page() ) {
    77                         // Exclude posts hidden from archives.
    78                         $visibility_to_exclude = array( 'search_only', 'hidden' );
     90        /**
     91         * Append the visibility exclusion to queries marked in filter_queries().
     92         *
     93         * Both variants are full SQL literals on purpose: Plugin Check 2.0 flags
     94         * any PHP variable interpolated inside a SQL string, so the two possible
     95         * exclusion lists are written out instead of being built dynamically.
     96         *
     97         * @since 1.1.0
     98         * @param string   $where The WHERE clause of the query.
     99         * @param WP_Query $query The WordPress query object.
     100         * @return string Filtered WHERE clause.
     101         */
     102        public function exclude_posts_where( $where, $query ) {
     103                $context = $query->get( self::CONTEXT_QUERY_VAR );
     104
     105                if ( 'search' !== $context && 'archive' !== $context ) {
     106                        return $where;
    79107                }
    80108
    81                 if ( empty( $visibility_to_exclude ) ) {
    82                         return;
     109                global $wpdb;
     110
     111                if ( 'search' === $context ) {
     112                        // Exclude posts hidden from search.
     113                        $where .= " AND {$wpdb->posts}.ID NOT IN (
     114                                SELECT post_id
     115                                FROM {$wpdb->postmeta}
     116                                WHERE meta_key = '_ayudawp_visibility'
     117                                AND meta_value IN ( 'archive_only', 'hidden' )
     118                        )";
     119                } else {
     120                        // Exclude posts hidden from archives.
     121                        $where .= " AND {$wpdb->posts}.ID NOT IN (
     122                                SELECT post_id
     123                                FROM {$wpdb->postmeta}
     124                                WHERE meta_key = '_ayudawp_visibility'
     125                                AND meta_value IN ( 'search_only', 'hidden' )
     126                        )";
    83127                }
    84128
    85                 // Use posts_where filter for better performance.
    86                 add_filter(
    87                         'posts_where',
    88                         function( $where ) use ( $visibility_to_exclude ) {
    89                                 global $wpdb;
    90 
    91                                 $excluded = "'" . implode( "','", array_map( 'esc_sql', $visibility_to_exclude ) ) . "'";
    92 
    93                                 $where .= " AND {$wpdb->posts}.ID NOT IN (
    94                                         SELECT post_id
    95                                         FROM {$wpdb->postmeta}
    96                                         WHERE meta_key = '_ayudawp_visibility'
    97                                         AND meta_value IN ({$excluded})
    98                                 )";
    99 
    100                                 return $where;
    101                         },
    102                         10,
    103                         1
    104                 );
     129                return $where;
    105130        }
    106131
  • post-visibility-control/trunk/includes/helpers.php

    r3441562 r3648066  
    7878 *
    7979 * @since 1.0.0
    80  * @param string $post_type The post type to check.
     80 * @param string|null $post_type The post type to check.
    8181 * @return bool True if supported, false otherwise.
    8282 */
    8383function ayudawp_pvc_is_supported_post_type( $post_type ) {
     84        if ( ! is_string( $post_type ) ) {
     85                return false;
     86        }
     87
    8488        $supported = ayudawp_pvc_get_supported_post_types();
    8589        return isset( $supported[ $post_type ] );
  • post-visibility-control/trunk/post-visibility-control.php

    r3492975 r3648066  
    44 * Plugin URI: https://servicios.ayudawp.com
    55 * Description: Control visibility of posts, pages, and custom post types in archives and search results.
    6  * Version: 1.0.2
     6 * Version: 1.1.0
    77 * Author: Fernando Tellado
    88 * Author URI: https://ayudawp.com
    … …  
    1212 * Requires at least: 5.0
    1313 * Requires PHP: 7.4
    14  * Tested up to: 7.0
     14 * Tested up to: 7.1
    1515 *
    1616 * @package PostVisibilityControl
    … …  
    2323
    2424// Plugin constants.
    25 define( 'AYUDAWP_PVC_VERSION', '1.0.2' );
     25define( 'AYUDAWP_PVC_VERSION', '1.1.0' );
    2626define( 'AYUDAWP_PVC_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
    2727define( 'AYUDAWP_PVC_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
  • post-visibility-control/trunk/readme.txt

    r3574876 r3648066  
    33Tags: visibility, exclude, search, archive, hide posts
    44Requires at least: 5.0
    5 Tested up to: 7.0
     5Tested up to: 7.1
    66Requires PHP: 7.4
    7 Stable tag: 1.0.2
     7Stable tag: 1.1.0
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    1515Post Visibility Control adds visibility options to posts, pages, and custom post types. Control exactly where your content appears without making it private.
    1616
    17 The plugin integrates seamlessly into the native WordPress interface. In the Block Editor (Gutenberg), you will find the options inside the Status & Visibility popup when you click on the post status. In the Classic Editor, the options appear within the Visibility section of the Publish metabox, right where you would expect them.
     17The plugin integrates seamlessly into the native WordPress interface. In the Block Editor (Gutenberg), you will find the options in the Summary panel of the settings sidebar, together with the rest of the status controls. In the Classic Editor, the options appear within the Visibility section of the Publish metabox, right where you would expect them.
    1818
    1919**Features:**
    … …  
    4646
    47471. Edit any post or page
    48 2. In the right sidebar, look for the "Status" section (usually shows "Draft" or "Publish")
    49 3. Click on the status to open the Status & Visibility popup
    50 4. You will see "Visibility Control" options with four radio buttons
    51 5. Select your preferred visibility option
    52 6. Save or publish your post
     482. Open the settings sidebar (gear icon) and select the document tab (Post or Page)
     493. In the Summary panel you will see "Visibility Control" with four radio buttons
     504. Select your preferred visibility option
     515. Save or publish your post
    5352
    5453**Classic Editor:**
    … …  
    65641. Upload the `post-visibility-control` folder to `/wp-content/plugins/`
    66652. Activate the plugin through the 'Plugins' menu in WordPress
    67 3. Edit any post or page and find the visibility control options in the Visibility section (Classic Editor) or Status & Visibility panel (Block Editor)
     663. Edit any post or page and find the visibility control options in the Visibility section (Classic Editor) or the Summary panel (Block Editor)
    6867
    6968== Frequently Asked Questions ==
    … …  
    7170= Where do I find the visibility options? =
    7271
    73 In the Block Editor (Gutenberg): Edit your post, then click on the "Status" in the right sidebar to open the Status & Visibility popup. The Visibility Control options appear there with four radio buttons.
     72In the Block Editor (Gutenberg): Edit your post and open the settings sidebar. The Visibility Control options appear in the Summary panel with four radio buttons.
    7473
    7574In the Classic Editor: Edit your post and look at the Publish metabox in the right sidebar. The Visibility Control options appear within the Visibility section, below the standard WordPress visibility settings.
    … …  
    102101== Changelog ==
    103102
    104 = 1.0.2 =
    105 Tested up to WordPress 7.0
     103= 1.1.0 =
     104* Improved: Block editor integration rebuilt on the official PluginPostStatusInfo API. The options now live in the Summary panel of the settings sidebar instead of being injected into the status popup.
     105* Improved: Tested up to WordPress 7.1.
     106* Fix: Visibility options did not appear in the block editor when the admin language was other than English or Spanish.
     107* Fix: After an archive or search query, the visibility exclusion leaked into every later query of the same request, so hidden posts could vanish from unrelated queries such as featured post blocks. The exclusion is now scoped to the query it targets.
     108* Fix: PHP 8.5 deprecation notice (null used as array offset) on admin screens without a post type context.
    106109
    107 = 1.0.1 =
    108 * Performance: Optimized database queries to prevent slow queries and server timeouts
    109 * Performance: Replaced inefficient meta_query with optimized posts_where filter
    110 * Fixed: Potential 504 errors on sites with large post counts
    111 
    112 = 1.0.0 =
    113 * Initial release
    114 * Support for posts, pages, and custom post types
    115 * Classic Editor and Block Editor support
    116 * Four visibility options
     110For older changelog entries, please check the [changelog.txt](https://plugins.svn.wordpress.org/post-visibility-control/trunk/changelog.txt) file
    117111
    118112== Upgrade Notice ==
    119113
    120 = 1.0.1 =
    121 Important performance update. Fixes slow database queries that could cause server timeouts on sites with many posts.
    122 
    123 = 1.0.0 =
    124 Initial release.
     114= 1.1.0 =
     115Visibility options now show in the block editor in any admin language, hidden posts no longer vanish from unrelated queries, and a PHP 8.5 deprecation notice is fixed.
    125116
    126117== Support ==
Note: See TracChangeset for help on using the changeset viewer.