Plugin Directory

Changeset 3311724


Ignore:
Timestamp:
06/15/2025 12:16:17 AM (10 months ago)
Author:
etruel
Message:

Major update: Adds Gutenberg block, per-post view metabox with reset, role-based view filtering, and tools to reset counters.

Location:
wpecounter
Files:
52 added
10 edited

Legend:

Unmodified
Added
Removed
  • wpecounter/trunk/README.md

    r2781621 r3311724  
    1 # wpecounter
    2 
    31=== WP Views Counter ===
    42
    5 Contributors: etruel, khaztiel
     3Contributors: etruel, khaztiel, gerarjos14
    64
    75Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=7267TH4PT3GSW
    86
    9 Tags: views, visit counter, visits counter, post views, visits, counter, post visits, ajax counter, page count, count visits, popular posts.
     7Tags: post views, views counter, popular posts, ajax counter, analytics
    108
    119Requires at least: 3.1
    1210
    13 Tested up to: 6.0.2
     11Tested up to: 6.8.2
    1412
    15 Stable tag: trunk
     13Requires PHP: 5.6
     14
     15Stable tag: 2.1
    1616
    1717License: GPLv2
    1818
    1919
    20 Visits Post(types) counter. Shown the number of visits on lists of posts, pages and/or custom post types and shortcode.
    21 
     20Track your content’s popularity with a fast, reliable post views counter. Smarter and lighter than other bloated plugins.
    2221
    2322== Description ==
    2423
    25 Knows how much views has every post, page or custom post type, just on wordpress list.
    26 You can select on plugin settings, which types must count.  Also you can print using shortcode [WPeCounter] in widgets, post or pages.
     24**WP Views Counter** is a lightweight yet powerful plugin to track how many times each post, page, or custom post type is viewed — from inside the WordPress admin or via shortcode or block.
    2725
    28 Works with Easy Digital Downloads Products to see how many views has every Download, also with other plugins that work with public custom post types.
     26Designed for performance and precision, it's ideal for blogs, WooCommerce stores, or Easy Digital Downloads shops where knowing what content attracts the most attention is critical.
    2927
    30 Is extremely lightweight because it works with ajax. 
     28Unlike bloated analytics solutions, WP Views Counter focuses on just one thing — accurate view tracking — without slowing down your site.
    3129
    32 Developer and bugtracker on github repository: https://github.com/Etruel-Developments/wpecounter/issues
     30= Why choose WP Views Counter over other counters? =
    3331
    34 Feel free to fork it and propose new enhancements or Pull Requests.
     32✅ **Accurate post view counts** shown in the admin list, shortcodes, or blocks
     33✅ **Metabox in each post** with manual reset option
     34✅ **Exclude views by user role** or logged-in users
     35✅ **Works with all post types** and EDD
     36✅ **Lightweight AJAX-based tracking** — no page reloads
     37✅ **Gutenberg block included** to display most viewed content
     38✅ **Import views from other plugins**
     39✅ **Multilingual and developer-friendly**
     40
     41Track content performance at a glance and optimize your content strategy with a tool that just works — no tracking scripts, no third-party APIs, and no clutter.
     42
     43Developer-friendly: [GitHub Repository](https://github.com/Etruel-Developments/wpecounter/issues)
     44Contributions, forks and feedback welcome.
  • wpecounter/trunk/assets/js/settings.js

    r2789038 r3311724  
     1jQuery(document).ready(function ($) {
     2    // If any role is checked, uncheck "All roles"
     3    $('#views_counter_rol_list').on('change', '.views_counter_rol_role', function () {
     4        if ($(this).is(':checked')) {
     5            $('#views_counter_rol_all').prop('checked', false);
     6        }
     7    });
     8    // If "All roles" is checked, uncheck all others
     9    $('#views_counter_rol_list').on('change', '#views_counter_rol_all', function () {
     10        if ($(this).is(':checked')) {
     11            $('.views_counter_rol_role').prop('checked', false);
     12        }
     13    });
     14});
  • wpecounter/trunk/includes/class-views.php

    r3279485 r3311724  
    5353            /* Admin filters    */
    5454            add_action('admin_init', array($this, 'admin_init'));
    55         }
    56 
    57         /**
    58          * Adds support for 'wpecounter' to the 'post', 'page', and 'attachment' post types (default WordPress
     55            add_action('add_meta_boxes', array(__CLASS__, 'add_views_metabox'));
     56        }
     57
     58
     59        public static function add_views_metabox(){
     60            $options     = get_option('WPeCounter_Options'); // $WPeCounterSettings->options_key);
     61            $cpostypes   = $options['cpostypes'];
     62            $screens = array();
     63            $args        = array('public' => true);
     64            $output      = 'names'; // names or objects
     65            $post_types  = get_post_types($args, $output);
     66
     67            foreach ($post_types as $post_type) {
     68                if (@$cpostypes[$post_type]) {
     69                    $screens[] = $post_type;
     70                }
     71            }
     72            add_meta_box(
     73                'views_metabox',
     74                __('Views Counter', 'wpecounter'),
     75                array(__CLASS__, 'render_views_metabox'),
     76                $screens,
     77                'side',
     78                'default'
     79            );
     80        }
     81
     82        /**
     83         * Renders the views metabox.
     84         *
     85         * @param WP_Post $post The current post object.
     86         */
     87        public static function render_views_metabox($post) {
     88
     89            // Print styles once
     90            self::print_reset_views_button_styles();
     91
     92            // Prepare nonce and button
     93            $nonce = wp_create_nonce('wpecounter_reset_views_' . $post->ID);
     94            $reset_button = sprintf(
     95                '<a href="#" class="wpecounter-reset-views-btn editor-post-publish-button editor-post-publish-button__button button-primary is-compact" data-postid="%d" data-nonce="%s" title="%s">%s</a>',
     96                esc_attr($post->ID),
     97                esc_attr($nonce),
     98                esc_attr__('Reset Views', 'wpecounter'),
     99                esc_attr__('Reset', 'wpecounter')
     100            );
     101
     102            // Get views count
     103            $views = self::get_post_views_count($post->ID);
     104
     105            // Output metabox content
     106            printf(
     107                '<div class="wpecounter-views-box"><label id="wpecounter-views-count" class="wpecounter-views-label">%s </label>%s</div>',
     108                sprintf('%d', $views),
     109                $reset_button
     110            );
     111        }
     112
     113        /**
     114         * Adds support for 'wpecounter' to the 'post', 'page', and 'attachment' post types (default WordPress
    59115         * post types).  For all other post types, the theme should explicitly register support for this feature.
    60116         *
     
    155211         */
    156212        function update_single_views($post_id) {
    157             /* If we're on a singular view of a post, calculate the number of views. */
    158             if (!empty($post_id)) {
    159 
    160                 /* Allow devs to override the meta key used. By default, this is 'Views'. */
     213           
     214            // Get plugin options from the database
     215            $options = get_option('WPeCounter_Options');
     216           
     217            // Get the list of roles for which views should be counted
     218            $not_allowed_roles = isset($options['views_counter_rol']) ? (array)$options['views_counter_rol'] : array();
     219
     220            // Flag to determine if the view should be counted
     221            $should_count = false;
     222
     223            // If role-based counting is enabled and not set to 'all_roles'
     224            // Always count views for guests (not logged in)
     225            if (!is_user_logged_in()) {
     226                $should_count = true;
     227            } else {
     228                // Only count for logged-in users not in the not allowed roles
     229                if (!empty($not_allowed_roles) && is_array($not_allowed_roles) && !in_array('all_roles', $not_allowed_roles)) {
     230                    $current_user = wp_get_current_user();
     231                    // If the current user's roles intersect with the not allowed roles, do not count the view
     232                    if (!empty($current_user->roles) && array_intersect($current_user->roles, $not_allowed_roles)) {
     233                        $should_count = false;
     234                    } else {
     235                        $should_count = true;
     236                    }
     237                } else {
     238                    // If no roles are restricted, count the view for all logged-in users
     239                    $should_count = false;
     240                }
     241            }
     242
     243            // If the view should be counted and the post ID is valid
     244            if ($should_count && !empty($post_id)) {
     245                // Get the meta key for storing views
    161246                $meta_key = $this->wpecounter_views_meta_key();
    162 
    163                 /* Get the number of views the post currently has. */
     247                // Get the current number of views
    164248                $old_views = get_post_meta($post_id, $meta_key, true);
    165 
    166                 /* Add +1 to the number of current views. */
     249                // Increment the view count by 1
    167250                $new_views = absint($old_views) + 1;
    168 
    169                 /* Update the view count with the new view count. */
     251                // Update the post meta with the new view count
    170252                update_post_meta($post_id, $meta_key, $new_views, $old_views);
    171253            }
     
    237319        }
    238320
    239         /**
    240          * Init filters to display column and order by views on post types lists.
    241          *
    242          * @access public
    243          * @return object
    244          */
     321//      /**
     322//       * Init filters to display column and order by views on post types lists.
     323//       *
     324//       * @access public
     325//       * @return object
     326//       */
     327//      public function admin_init() {
     328//          global $options;
     329//          $options     = get_option('WPeCounter_Options'); // $WPeCounterSettings->options_key);
     330//          $cpostypes   = $options['cpostypes'];
     331
     332//          $args        = array('public' => true);
     333//          $output      = 'names'; // names or objects
     334//          $post_types  = get_post_types($args, $output);
     335//          foreach ($post_types as $post_type) {
     336//              if (@$cpostypes[$post_type]) {
     337//                  //  add_filter('manage_'.$post_type.'_posts_columns', array( $this, 'posts_columns_id'), 5);
     338//                  add_filter('manage_edit-' . $post_type . '_columns', array($this, 'posts_columns_id'), 10);
     339//                  add_action('manage_' . $post_type . '_posts_custom_column', array($this, 'posts_custom_id_columns'), 10, 2);
     340//                  //Order
     341//                  add_filter('manage_edit-' . $post_type . '_sortable_columns', array($this, 'views_column_register_sortable'));
     342//              }
     343//          }
     344//          add_action('pre_get_posts', array($this, 'views_column_orderby'));
     345// //           add_action('parse_query', array($this, 'views_column_orderby'));
     346//          add_action('admin_head', array($this, 'post_views_column_width'));
     347// //           
     348// //           
     349// //           add_filter('manage_edit-' . $post_type . '_columns', array(__CLASS__, 'set_edit_wpematico_columns'));
     350// //           add_action('manage_' . $post_type . '_posts_custom_column', array(__CLASS__, 'custom_wpematico_column'), 10, 2);
     351// //           add_filter("manage_edit-' . $post_type . '_sortable_columns", array(__CLASS__, "sortable_columns"));
     352// //           add_action('pre_get_posts', array(__CLASS__, 'column_orderby'));
     353// //           
     354//      }
     355//      public function posts_columns_id($columns) {
     356//          global $options;
     357//          $cfg = $options;
     358           
     359//          $column_post_views   = array('post_views' => '' . __('Views', 'wpecounter') . '');
     360//          $column_pos = (isset($cfg['wpecounter_column_pos']) and $cfg['wpecounter_column_pos'] > 0 ) ? $cfg['wpecounter_column_pos'] : 5;
     361//          $columns = array_slice($columns, 0, $column_pos, true) + $column_post_views + array_slice($columns, $column_pos, NULL, true);
     362//          $columns             = array_merge($columns, $column_post_views);
     363//          return $columns;
     364//      }
     365
     366        public function posts_custom_id_columns($column_name, $id) {
     367            if ($column_name === 'post_views') {
     368                echo '' . wpecounter_post_views(array('post_id' => $id)) . '';
     369            }
     370        }
     371        public static function print_reset_views_button_styles() {
     372            echo '<style>
     373                .wpecounter-views-box{
     374                    display: flex;
     375                    align-items: flex-end;
     376                    justify-content: center;
     377                    text-align: center;
     378                }
     379                .wpecounter-views-box .button-primary.wpecounter-reset-views-btn {
     380                    vertical-align: -webkit-baseline-middle;
     381                    font-size: 10px;
     382                    min-height: 20px;
     383                    line-height: 2;
     384                    margin-left: 15px;
     385                }
     386                .wpecounter-views-label{
     387                    display: inline-flex;
     388                    font-size: 60px;
     389                    font-weight: bold;
     390                    color: grey;
     391                    line-height: 45px;
     392                }
     393            </style>';
     394        }
     395
     396        // Add the new column to the posts table
     397        public function posts_columns_id($columns) {
     398            global $options;
     399            $cfg = $options;
     400
     401            $column_post_views = array('post_views' => '' . __('Views', 'wpecounter') . '');
     402            $column_pos = (isset($cfg['wpecounter_column_pos']) and $cfg['wpecounter_column_pos'] > 0 ) ? $cfg['wpecounter_column_pos'] : 5;
     403            $columns = array_slice($columns, 0, $column_pos, true) + $column_post_views + array_slice($columns, $column_pos, NULL, true);
     404            $columns = array_merge($columns, $column_post_views);
     405            return $columns;
     406        }
     407
     408        // Enqueue admin JS for AJAX reset
    245409        public function admin_init() {
     410            global $options;
    246411            $options     = get_option('WPeCounter_Options'); // $WPeCounterSettings->options_key);
    247412            $cpostypes   = $options['cpostypes'];
     
    252417            foreach ($post_types as $post_type) {
    253418                if (@$cpostypes[$post_type]) {
    254                     //  add_filter('manage_'.$post_type.'_posts_columns', array( $this, 'posts_columns_id'), 5);
    255419                    add_filter('manage_edit-' . $post_type . '_columns', array($this, 'posts_columns_id'), 10);
    256420                    add_action('manage_' . $post_type . '_posts_custom_column', array($this, 'posts_custom_id_columns'), 10, 2);
    257                     //Order
    258421                    add_filter('manage_edit-' . $post_type . '_sortable_columns', array($this, 'views_column_register_sortable'));
    259422                }
    260423            }
    261424            add_action('pre_get_posts', array($this, 'views_column_orderby'));
    262 //          add_action('parse_query', array($this, 'views_column_orderby'));
    263425            add_action('admin_head', array($this, 'post_views_column_width'));
    264 //         
    265 //         
    266 //          add_filter('manage_edit-' . $post_type . '_columns', array(__CLASS__, 'set_edit_wpematico_columns'));
    267 //          add_action('manage_' . $post_type . '_posts_custom_column', array(__CLASS__, 'custom_wpematico_column'), 10, 2);
    268 //          add_filter("manage_edit-' . $post_type . '_sortable_columns", array(__CLASS__, "sortable_columns"));
    269 //          add_action('pre_get_posts', array(__CLASS__, 'column_orderby'));
    270 //         
    271         }
    272 
    273         public function posts_columns_id($columns) {
    274 
    275             $column_post_views   = array('post_views' => '' . __('Views', 'wpecounter') . '');
    276             // 5to lugar
    277             //$columns = array_slice( $columns, 0, 5, true ) + $column_post_views + array_slice( $columns, 5, NULL, true );
    278             $columns             = array_merge($columns, $column_post_views);
    279             return $columns;
    280         }
    281 
    282         public function posts_custom_id_columns($column_name, $id) {
    283             if ($column_name === 'post_views') {
    284                 echo '' . wpecounter_post_views(array('post_id' => $id)) . '';
    285             }
     426
     427            // Enqueue JS only on post list screens
     428            add_action('admin_enqueue_scripts', function ($hook) {
     429                wp_enqueue_script('wpecounter-reset-views', plugins_url('../assets/js/reset-views.js', __FILE__), array('jquery'), null, true);
     430                wp_localize_script('wpecounter-reset-views', 'wpecounterResetViews', array(
     431                    'ajax_url' => admin_url('admin-ajax.php'),
     432                    'confirm'  => __('Are you sure you want to reset the views for this post?', 'wpecounter'),
     433                ));
     434            });
     435            add_action('wp_ajax_wpecounter_reset_views', array($this, 'ajax_reset_views'));
     436        }
     437
     438        // AJAX handler to reset views
     439        public function ajax_reset_views() {
     440            if (
     441                !isset($_POST['post_id'], $_POST['nonce']) ||
     442                !wp_verify_nonce($_POST['nonce'], 'wpecounter_reset_views_' . absint($_POST['post_id']))
     443            ) {
     444                wp_send_json_error(array('message' => __('Invalid request.', 'wpecounter')));
     445            }
     446            $post_id = absint($_POST['post_id']);
     447            if ($post_id) {
     448                delete_post_meta($post_id, $this->wpecounter_views_meta_key());
     449                wp_send_json_success(array('message' => __('Views reset.', 'wpecounter')));
     450            }
     451            wp_send_json_error(array('message' => __('Invalid post ID.', 'wpecounter')));
    286452        }
    287453
  • wpecounter/trunk/includes/plugin-utils.php

    r3279485 r3311724  
    77}
    88
    9 /* function boilerplate_license_menu() {
    10   add_submenu_page(
    11   'edit.php?post_type=wpematico',
    12   'Boilerplate Settings',
    13   'BoilerPlate <span class="dashicons-before dashicons-admin-plugins"></span>',
    14   'manage_options',
    15   'boilerplate_license',
    16   'boilerplate_license_page'
    17   );
    18   //add_plugins_page( 'Plugin License', 'Plugin License', 'manage_options', 'boilerplate_license', 'boilerplate_license_page' );
    19   }
    20   add_action('admin_menu', 'boilerplate_license_menu');
    21  */
    22 
    239
    2410
     
    2713    class WPeCounterPluginUtils {
    2814
     15        // Constructor: Hooks into WordPress actions and filters
    2916        function __construct() {
    30 //          add_filter('admin_init', array(__CLASS__, 'init'), 10, 2);
    31 //      }
    32 //
    33 //      public static function init() {
    34             //Additional links on the plugin page
     17            // Register the block during init
     18            add_action('init', array(__CLASS__, 'mvpb_register_block'));
     19
     20            // Enqueue editor assets for the block
     21            add_action('enqueue_block_editor_assets', array(__CLASS__, 'wpecounter_enqueue_block_editor_assets'));
     22
     23            // Add a custom block category to the block editor
     24            add_filter('block_categories_all', function ($categories, $post) {
     25                $custom_category = [
     26                    [
     27                        'slug'  => 'wpecounter',
     28                        'title' => __('WP Views Counter', 'wpecounter'),
     29                        'icon'  => 'visibility', // Optional icon
     30                    ],
     31                ];
     32                // Add the custom category at the beginning of the list
     33                return array_merge($custom_category, $categories);
     34            }, 10, 2);
     35
     36            // Add custom links in the plugin list
    3537            add_filter('plugin_row_meta', array(__CLASS__, 'init_row_meta'), 10, 2);
    3638            add_filter('plugin_action_links_' . plugin_basename(WPECOUNTER_PLUGIN_FILE), array(__CLASS__, 'init_action_links'));
    3739        }
    3840
     41        // Register the dynamic block and link the render callback
     42        public static function mvpb_register_block() {
     43            register_block_type(WPECOUNTER_PLUGIN_DIR, array(
     44                'render_callback' => array(__CLASS__, 'mvpb_render_most_viewed'),
     45            ));
     46        }
     47
     48        // Render callback for the dynamic block (frontend output)
     49        public static function mvpb_render_most_viewed($attributes){
     50            // Set default values for attributes
     51            if (! isset($attributes['postType'])) {
     52                $attributes['postType'] = 'post';
     53            }
     54            if (! isset($attributes['limit'])) {
     55                $attributes['limit'] = 5;
     56            }
     57            if (! isset($attributes['order'])) {
     58                $attributes['order'] = 'DESC';
     59            }
     60            if (! isset($attributes['title'])) {
     61                $attributes['title'] = __('Most Popular', 'text-domain');
     62            }
     63
     64            // Instantiate the views counter object if not already set
     65            if (!isset($WPeCounterViews)) {
     66                $WPeCounterViews = new WPeCounterViews();
     67            }
     68
     69            // Query arguments to retrieve the most viewed posts
     70            $args = array(
     71                'post_type'           => sanitize_text_field($attributes['postType']),
     72                'posts_per_page'      => intval($attributes['limit']),
     73                'post_status'         => 'publish',
     74                'meta_key'            => $WPeCounterViews->wpecounter_views_meta_key(),
     75                'orderby'             => 'meta_value_num',
     76                'order'               => in_array(strtoupper($attributes['order']), ['ASC', 'DESC']) ? strtoupper($attributes['order']) : 'DESC',
     77                'no_found_rows'       => true,
     78                'ignore_sticky_posts' => true,
     79            );
     80
     81            $posts = get_posts($args);
     82
     83            // If no posts found, return fallback message
     84            if (empty($posts)) {
     85                return '<p>' . esc_html__('No popular posts found.', 'text-domain') . '</p>';
     86            }
     87
     88            // Wrapper with dynamic block attributes (e.g., className, etc.)
     89            $wrapper = get_block_wrapper_attributes();
     90
     91            // Start building HTML output
     92            $output  = "<div {$wrapper}>";
     93            $output .= '<div class="mvpb-block">';
     94            $output .= '<h3 class="mvpb-block-title">' . esc_html($attributes['title']) . '</h3>';
     95            $output .= '<ul class="mvpb-post-list">';
     96
     97            // Loop through the posts and create a list item for each
     98            foreach ($posts as $post) {
     99                $title = esc_html(get_the_title($post->ID));
     100                $url   = esc_url(get_permalink($post->ID));
     101                $views = $WPeCounterViews->get_post_views_count($post->ID);
     102                $output .= "<li class='mvpb-post-item'><a href='{$url}'>{$title}</a> ({$views})</li>";
     103            }
     104
     105            $output .= '</ul>';
     106            $output .= '</div>';
     107            $output .= '</div>';
     108
     109            return $output;
     110        }
     111
     112        // Enqueue JavaScript and pass data to the block editor
     113        public static function wpecounter_enqueue_block_editor_assets(){
     114            wp_enqueue_script(
     115                'wpecounter-block-editor', WPECOUNTER_PLUGIN_DIR . 'build/index.js',
     116            );
     117
     118            // Get plugin options
     119            $options = get_option('WPeCounter_Options');
     120            $cpostypes = isset($options['cpostypes']) ? (array) $options['cpostypes'] : [];
     121           
     122            // Get all public post types
     123            $args = array('public' => true);
     124            $post_types = get_post_types($args, 'names');
     125
     126            // List of post types to exclude
     127            $exclude = array('attachment', 'revision', 'nav_menu_item');
     128
     129            // Filter post types based on user settings and exclusion list
     130            $post_types_filtered = array_filter($post_types, function ($pt) use ($exclude, $cpostypes) {
     131                return !in_array($pt, $exclude, true) && isset($cpostypes[$pt]) && $cpostypes[$pt] === '1';
     132            });
     133
     134            // Format the post types for Select dropdown in block editor
     135            $select_options = [];
     136            foreach ($post_types_filtered as $pt) {
     137                $select_options[] = [
     138                    'label' => ucfirst($pt),
     139                    'value' => $pt,
     140                ];
     141            }
     142
     143            // Pass the data to the block editor script
     144            wp_localize_script(
     145                'wpecounter-block-editor',
     146                'wpecounterData',
     147                array(
     148                    'postTypes' => $select_options,
     149                )
     150            );
     151        }
    39152        /**
    40153         * Actions-Links del Plugin
  • wpecounter/trunk/includes/settings.php

    r2789038 r3311724  
    2222         * Some settings to use by default
    2323         */
    24         protected $SettingsPage      = 'WPeCounter';
    25         protected $section           = 'WPeCounter-settings';
    26         protected $options_key       = 'WPeCounter_Options';
    27         protected $heading           = 'WP Views Counter Options';
    28         protected $description       = 'These options are applied to the selected post-types counters.';
    29         protected $default_options   = array(
    30             'cpostypes' => array(
    31                 'post'   => 1,
    32                 'page'   => 1
    33             )
     24        protected $SettingsPage    = 'WPeCounter';
     25        protected $section         = 'WPeCounter-settings';
     26        protected $options_key     = 'WPeCounter_Options';
     27        protected $heading         = 'WP Views Counter Options';
     28        protected $description     = 'These options are applied to the selected post-types counters.';
     29        protected $default_options = array(
     30            'cpostypes'         => array(
     31                'post' => 1,
     32                'page' => 1
     33            ),
     34            'views_counter_rol' => array('all_roles'),
    3435        );
    3536
     
    7778        public function register_settings() {
    7879
    79 //          delete_option($this->options_key);
    80             //$section       = $this->section;
    81             //$option_group  = $this->options_key;
    8280            // no options - create them.
    8381            if (false == get_option($this->options_key)) {
     
    8684
    8785            $options = get_option($this->options_key);
    88 
    8986            /**
    9087             * Check is exist each option and assign by default values
     
    9289            if (false == isset($options['cpostypes'])) {
    9390                $options['cpostypes'] = $this->default_options['cpostypes'];
     91            }
     92            if (false == isset($options['views_counter_rol'])) {
     93                $options['views_counter_rol'] = $this->default_options['views_counter_rol'];
    9494            }
    9595
     
    107107                    $this->SettingsPage,
    108108                    $this->section,
    109                     array('id'   => 'cpostypes',
     109                    array(
     110                        'id'     => 'cpostypes',
    110111                        'name'   => 'WPeCounter_Options[cpostypes]',
    111112                        'values' => $options['cpostypes']
     
    113114            );
    114115
    115 //          add_settings_field(
    116 //                  'impofield',
    117 //                  __('CAMPO DE TEXTo:', 'wpecounter'),
    118 //                  array($this, 'textinput'),
    119 //                  $this->SettingsPage,
    120 //                  $this->section,
    121 //                  array('id'   => 'impofield',
    122 //                      'name'   => 'WPeCounter_Options[cpostypes]',
    123 //                      'value'  => $options['cpostypes']
    124 //                  )
    125 //          );
    126             // finally
     116            // Add setting for campaign_in_postslist and column_campaign_pos
     117            add_settings_field(
     118                    'campaign_in_postslist',
     119                    __('Column position on lists:', 'wpecounter'),
     120                    function ($args) use ($options) {
     121                        $wpecounter_column_pos = isset($options['wpecounter_column_pos']) ? intval($options['wpecounter_column_pos']) : 0;
     122                        ?>
     123                        <p>
     124                            <span id="wpecounter_column_pos_field" class="insidesec" >
     125                                <label>
     126                                    <input name="WPeCounter_Options[wpecounter_column_pos]" id="wpecounter_column_pos" class="small-text" min="0" type="number" value="<?php echo esc_attr($wpecounter_column_pos); ?>" />
     127                                </label>
     128                            </span>
     129                            <br>
     130                            <span class="description"><?php esc_html_e('Position of the Views column in the posts(-type) lists.', 'wpecounter'); ?></span>
     131                        </p>
     132                       
     133                        <?php
     134                    },
     135                    $this->SettingsPage,
     136                    $this->section
     137            );
     138
     139            // Add multi-select for views_counter_rol
     140            add_settings_field(
     141                    'views_counter_rol',
     142                    __('Do not count views for:', 'wpecounter'),
     143                    function ($args) use ($options) {
     144                        global $wp_roles;
     145                        if (!isset($wp_roles)) {
     146                            $wp_roles = new WP_Roles();
     147                        }
     148                        $roles             = $wp_roles->get_names();
     149                        $current           = isset($options['views_counter_rol']) ? (array) $options['views_counter_rol'] : array();
     150                        $all_roles_checked = in_array('all_roles', $current) && count($current) === 1;
     151                        echo '<div id="views_counter_rol_list">';
     152                        echo '<label><input type="checkbox" id="views_counter_rol_all" name="WPeCounter_Options[views_counter_rol][]" value="all_roles"' . ($all_roles_checked ? ' checked' : '') . '> ' . esc_html__('All logged in users', 'wpecounter') . '</label><br>';
     153                        foreach ($roles as $role_key => $role_name) {
     154                            printf(
     155                                    '<label><input type="checkbox" class="views_counter_rol_role" name="WPeCounter_Options[views_counter_rol][]" value="%s"%s> %s</label><br>',
     156                                    esc_attr($role_key),
     157                                    in_array($role_key, $current) ? ' checked' : '',
     158                                    esc_html($role_name)
     159                            );
     160                        }
     161                        echo '</div>';
     162                    },
     163                    $this->SettingsPage,
     164                    $this->section
     165            );
     166/*          add_settings_field(
     167                'reset_counters',
     168                __('Reset Counters', 'wpecounter'),
     169                function($args) use ($options) {
     170                    // Get all public post types
     171                    $post_types = get_post_types(['public' => true], 'objects');
     172                   
     173            ?>
     174            <div>
     175                <label>
     176                    <input type="radio" name="WPeCounter_Options[reset_scope]" value="all" checked>
     177                    <?php _e('Reset all counters (all post types)', 'wpecounter'); ?>
     178                </label>
     179                <br>
     180                <label>
     181                    <input type="radio" name="WPeCounter_Options[reset_scope]" value="by_type">
     182                    <?php _e('Reset by post type:', 'wpecounter'); ?>
     183                </label>
     184                <select name="WPeCounter_Options[reset_post_type]" style="margin-left:10px;">
     185                    <?php
     186                    foreach ($post_types as $post_type) :
     187                                if ($post_type->name == 'attachment')
     188                                    continue;
     189                               
     190                        ?>
     191                        <option value="<?php echo esc_attr($post_type->name); ?>"><?php echo esc_html($post_type->labels->singular_name); ?></option>
     192                        <?php endforeach; ?>
     193                </select>
     194                <br><br>
     195                <button type="submit" name="reset_counters_btn" class="button button-secondary" onclick="return confirm('//<?php echo esc_js(__('Are you sure you want to reset the counters? This cannot be undone.', 'wpecounter')); ?>');">
     196                    <?php _e('Reset Counters', 'wpecounter'); ?>
     197                </button>
     198                <p class="description">//<?php _e('This will set all selected counters to zero. This action cannot be undone.', 'wpecounter'); ?></p>
     199            </div>
     200            <?php
     201                },
     202                $this->SettingsPage,
     203                $this->section
     204            );
     205*/
     206            // Handle reset counters action
     207            if (isset($_POST['reset_counters_btn'])) {
     208                global $wpdb;
     209                if (!isset($WPeCounterViews)) {
     210                    $WPeCounterViews = new WPeCounterViews();
     211                }
     212                $meta_key    = $WPeCounterViews->wpecounter_views_meta_key();
     213                $scope       = isset($_POST['WPeCounter_Options']['reset_scope']) ? $_POST['WPeCounter_Options']['reset_scope'] : 'all';
     214                $reset_count = 0;
     215                if ($scope === 'all') {
     216                    // Reset all counters for all public post types
     217                    $reset_count = $wpdb->query(
     218                            $wpdb->prepare(
     219                                    "UPDATE $wpdb->postmeta pm
     220                            INNER JOIN $wpdb->posts p ON pm.post_id = p.ID
     221                            SET pm.meta_value = '0'
     222                            WHERE pm.meta_key = %s AND p.post_type != %s",
     223                                    $meta_key,
     224                                    'attachment'
     225                            )
     226                    );
     227                } elseif ($scope === 'by_type' && !empty($_POST['WPeCounter_Options']['reset_post_type'])) {
     228                    $post_type   = sanitize_text_field($_POST['WPeCounter_Options']['reset_post_type']);
     229                    $reset_count = $wpdb->query(
     230                            $wpdb->prepare(
     231                                    "UPDATE $wpdb->postmeta pm
     232                            INNER JOIN $wpdb->posts p ON pm.post_id = p.ID
     233                            SET pm.meta_value = '0'
     234                            WHERE pm.meta_key = %s AND p.post_type = %s",
     235                                    $meta_key,
     236                                    $post_type
     237                            )
     238                    );
     239                }
     240                if ($reset_count !== false) {
     241                    add_settings_error($this->SettingsPage, '', sprintf(__('Reset %d counters to zero.', 'wpecounter'), $reset_count), 'success');
     242                } else {
     243                    add_settings_error($this->SettingsPage, '', __('Failed to reset counters.', 'wpecounter'), 'error');
     244                }
     245            }
     246
    127247            register_setting(
    128248                    $this->options_key,
     
    152272                <br />
    153273                <div>
    154                     <?php // echo $desc;   ?>
     274                    <?php // echo $desc;    ?>
    155275                </div>
    156276
    157                 <?php //echo settings_errors($this->SettingsPage); ?>
     277                <?php //echo settings_errors($this->SettingsPage);  ?>
    158278
    159279                <div id="tab_container">
     
    162282                            <div class="postbox inside">                       
    163283                                <div class="inside">
     284                                    <h3><span class="dashicons dashicons-welcome-view-site"></span><?php _e('General Options', 'wpecounter'); ?></h3>
     285                                    <hr />
    164286                                    <table class="form-table">
    165287                                        <?php
     
    177299                                <div class="inside">
    178300                                    <h3><span class="dashicons dashicons-sos"></span><?php _e('Danger Area', 'wpecounter'); ?></h3>
     301                                    <hr />
     302                                    <?php
     303                                    // Get all public post types
     304                                    $post_types = get_post_types(['public' => true], 'objects');
     305                                    ?>
     306                                    <div><p>
     307                                        <label>
     308                                            <input type="radio" name="WPeCounter_Options[reset_scope]" value="by_type" checked>
     309                                            <?php _e('Reset by post type:', 'wpecounter'); ?>
     310                                        </label>
     311                                        <select name="WPeCounter_Options[reset_post_type]" style="margin-left:10px;">
     312                                            <?php
     313                                            foreach ($post_types as $post_type) :
     314                                                if ($post_type->name == 'attachment')
     315                                                    continue;
     316                                                ?>
     317                                                <option value="<?php echo esc_attr($post_type->name); ?>"><?php echo esc_html($post_type->labels->singular_name); ?></option>
     318                                            <?php endforeach; ?>
     319                                        </select>
     320                                        <br>
     321                                        <label>
     322                                            <input type="radio" name="WPeCounter_Options[reset_scope]" value="all">
     323                                            <?php _e('Reset all counters (all post types)', 'wpecounter'); ?>
     324                                        </label>
     325                                        </p><p>
     326                                        <button type="submit" name="reset_counters_btn" class="button button-secondary" onclick="return confirm('<?php echo esc_js(__('Are you sure you want to reset the counters? This cannot be undone.', 'wpecounter')); ?>');">
     327                                            <?php _e('Reset Counters', 'wpecounter'); ?>
     328                                        </button>
     329                                        </p>
     330                                        <p class="description"><?php _e('This will set all selected counters to zero. This action cannot be undone.', 'wpecounter'); ?></p>
     331                                       
     332                                    </div>
     333                                    <hr />
     334                                    <p>
    179335                                    <strong><?php _e('Be careful with these options. There are not undo. Be sure to make backup of postmeta table.', 'wpecounter'); ?></strong>
     336                                    </p>
    180337                                    <p><label>
    181                                             <input type="checkbox" class="checkbox" name="showimpo" value="1" onclick="jQuery('#metaimpo').toggle();" />
    182                                             <?php _e('Show Options.', 'wpecounter'); ?></label>
     338                                    <input type = "checkbox" class = "checkbox" name = "showimpo" value = "1" onclick = "jQuery('#metaimpo').toggle();" />
     339                                    <?php _e('Show more options below:', 'wpecounter');
     340                                    ?></label>
    183341                                    </p>
    184342                                    <div id="metaimpo" style="display:none;">
     
    250408        public static function checkboxes_callback($data, $echo = true) {
    251409            // Currently selected post types
    252             $cpostypes   = $data['values'];
     410            $cpostypes  = $data['values'];
    253411            unset($cpostypes['attachment']);  // Do not allow Views counter for attachments
    254412            // only publics as privates doesn't have visits
    255             $args        = array('public' => true);
    256             $post_types  = get_post_types($args, 'names'); // names or objects
     413            $args       = array('public' => true);
     414            $post_types = get_post_types($args, 'names'); // names or objects
    257415
    258416            foreach ($post_types as $post_type) {
     
    299457
    300458            foreach ($args['options'] as $option => $name) {
    301                 $selected    = selected($option, $value, false);
    302                 $html       .= '<option value="' . $option . '" ' . $selected . '>' . $name . '</option>';
    303             }
    304 
    305             $html    .= '</select>';
    306             $html    .= '<label for="wpedpc_settings[' . $args['id'] . ']"> ' . $args['desc'] . '</label>';
     459                $selected = selected($option, $value, false);
     460                $html     .= '<option value="' . $option . '" ' . $selected . '>' . $name . '</option>';
     461            }
     462
     463            $html .= '</select>';
     464            $html .= '<label for="wpedpc_settings[' . $args['id'] . ']"> ' . $args['desc'] . '</label>';
    307465
    308466            if ($echo) {
     
    331489            // Loop through the input and sanitize each of the values
    332490            foreach ($input as $key => $val) {
    333                 $new_input[$key] = (!is_array($val) ) ? sanitize_text_field($val) : $val;
     491                $new_input[$key] = (!is_array($val)) ? sanitize_text_field($val) : $val;
    334492            }
    335493
     
    368526                                    $WPeCounterViews->wpecounter_views_meta_key(),
    369527                                    $impoviews
    370                     ));
     528                            ));
    371529                    // Prepare updated messages
    372530                    if ($introws > 0) {
     
    432590
    433591                    // Currently selected post types
    434                     $cpostypes   = $new_input['cpostypes'];
    435                     $sPostTypes  = "'" . implode("', '", array_keys($cpostypes)) . "'";
     592                    $cpostypes  = $new_input['cpostypes'];
     593                    $sPostTypes = "'" . implode("', '", array_keys($cpostypes)) . "'";
    436594
    437595                    $query = "SELECT
     
    454612                    $results = $wpdb->get_results($query);
    455613
    456                     $insertQuery = "INSERT INTO $wpdb->postmeta (post_id,meta_key,meta_value) VALUES ";
    457                     $insertQueryValues  = array();
     614                    $insertQuery      = "INSERT INTO $wpdb->postmeta (post_id,meta_key,meta_value) VALUES ";
     615                    $insertQueryValues = array();
    458616                    foreach ($results as $value) {
    459617                        $array = (array) $value;
     
    462620                    }
    463621                    $insertValues = implode(",", $insertQueryValues);
    464                     $insertQuery .= $insertValues;
    465                    
     622                    $insertQuery  .= $insertValues;
     623
    466624                    $metafixed = $wpdb->query($insertQuery);
    467625//                  var_dump($wpdb->last_query);
    468 
    469626//                  $metafixed = $wpdb->query("
    470627//                      INSERT INTO `$wpdb->postmeta` (`post_id`,`meta_key`,`meta_value`)
     
    521678            return self::$instance;
    522679        }
    523 
    524680    }
    525681
  • wpecounter/trunk/readme.txt

    r3306765 r3311724  
    11=== WP Views Counter ===
    2 Contributors: etruel, khaztiel, gerarjos14 
    3 Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=7267TH4PT3GSW 
    4 Tags: views, count visits, post views, post visits, ajax counter 
    5 Requires at least: 3.1 
    6 Tested up to: 6.8 
    7 Requires PHP: 5.6 
    8 Stable tag: 2.0.4 
    9 License: GPLv2 
     2Contributors: etruel, khaztiel, gerarjos14
     3Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=7267TH4PT3GSW
     4Tags: post views, views counter, popular posts, ajax counter, analytics
     5Requires at least: 3.1
     6Tested up to: 6.8.2
     7Requires PHP: 5.6
     8Stable tag: 2.1
     9License: GPLv2
    1010
    11 A lightweight and powerful post views counter. Displays visits in post lists and via shortcode or widget. Ideal for tracking popularity across all post types.
     11Fast, lightweight post views counter. Display views in admin, blocks or shortcodes — no tracking scripts required.
    1212
    1313== Description ==
    1414
    15 **WP Views Counter** lets you see how many views each post, page or custom post type entry has directly in the WordPress admin list table or with a shortcode.
     15**WP Views Counter** is a lightweight, high-performance plugin that accurately tracks and displays post, page, and custom post type views — directly in the WordPress admin, via shortcode, or with a Gutenberg block.
    1616
    17 Perfect for bloggers, marketers and eCommerce site owners, this plugin is designed for speed and simplicity. Whether you're running a WooCommerce shop, an Easy Digital Downloads store, or a content-heavy blog — you'll benefit from knowing what content your users engage with the most.
     17Built for bloggers, marketers, store owners, and developers, it works seamlessly across all post types — including WooCommerce and Easy Digital Downloads — with minimal impact on your site’s speed. No external scripts. No unnecessary bloat.
    1818
    19 The counter is AJAX-based, making it efficient and non-intrusive, even on high-traffic sites.
     19This plugin does one job and does it exceptionally well: it tells you which content is getting the most attention.
    2020
    21 = Why choose WP Views Counter? =
     21= Key Benefits =
    2222
    23 ✅ **Fast and lightweight** — built for performance 
    24 ✅ **Seamless integration** with all custom post types 
    25 ✅ **Easy Digital Downloads compatible** 
    26 ✅ **Popular posts widget included** 
    27 ✅ **Counts views via shortcode or in admin columns** 
    28 ✅ **Multilingual ready**
     23✅ **Accurate view counts** in admin columns, shortcode, or block 
     24✅ **Metabox per post** with real-time views and reset button 
     25✅ **Exclude views from logged-in users or specific roles** 
     26✅ **Fully AJAX-powered** — no page reloads or slowdowns 
     27✅ **Works with all post types**, including EDD and WooCommerce 
     28✅ **Block to display popular posts** — no legacy widgets required 
     29✅ **Developer-friendly and fully translatable**
     30✅ **Import views from other plugins**
    2931
    30 Unlike bloated analytics plugins, WP Views Counter focuses only on what matters: showing you the view count where you need it.
     32Whether you're optimizing your content strategy or simply want to know what's working, **WP Views Counter** is the simple and effective alternative to bloated analytics plugins.
    3133
    32 Developer-friendly on GitHub: https://github.com/Etruel-Developments/wpecounter/issues 
    33 We welcome forks, feedback and pull requests.
     34📦 Start tracking your most popular content today — with clarity, speed and control.
    3435
    35 == Features ==
    36 
    37 * Display post views in the admin post list columns.
    38 * Count views on any custom post type.
    39 * Shortcode `[WPeCounter]` to display views anywhere.
    40 * AJAX-based counting — no page reloads needed.
    41 * Order admin lists by view count.
    42 * Easy Digital Downloads (EDD) integration: track views for Downloads.
    43 * Legacy widget to show most visited posts or products.
    44 * Import views from other counters.
    45 * Fully translatable — multilingual support out of the box.
    46 
    47 == Coming Soon ==
    48 
    49 * Option to ignore visits from logged-in users or specific roles.
    50 * Choose display position of Views column per post type.
     36💡 Developer-friendly: [Contribute on GitHub](https://github.com/Etruel-Developments/wpecounter/issues) — forks and pull requests welcome.
    5137
    5238== Frequently Asked Questions ==
    5339
    54 = Can I upgrade from an older version without losing data? = 
     40= Can I upgrade from an older version without losing data? =
    5541Yes. Version 2.0+ automatically imports your previous data and settings. You can also manually import custom view fields. Always make a backup first.
    5642
     
    6955
    7056== Changelog ==
     57
     58= 2.1 – Jun 13, 2025 =
     59* Added a post metabox showing view count with a "Reset" button per post.(Deprecated Legacy Widget will be deleted on future release.)
     60* Introduced a Gutenberg block to replace the legacy popular posts widget.
     61* New option to choose the Views column position in each post type list.
     62* Added feature to exclude logged-in users (or by role) from the view count.
     63* New tools to reset all view counters or by post type.
    7164
    7265= 2.0.4 – Jun 4, 2025 =
     
    121114
    122115== Upgrade Notice ==
    123 = 2.0.4 =
    124 Recommended security and stability update. Compatible with WordPress 6.8.
     116= 2.1 =
     117Major update: Adds Gutenberg block, per-post view metabox with reset, role-based view filtering, and tools to reset counters. Fully compatible with WP 6.8.2.
  • wpecounter/trunk/wpecounter.php

    r3306765 r3311724  
    44 * Plugin URI:   https://etruel.com/downloads/wpecounter
    55 * Description:  Counts visits on post lists, pages and/or custom post types. It also displays them in posts, pages or text widget content, shortcode [WPeCounter].
    6  * Version:      2.0.4
     6 * Version:      2.1
    77 * Author:       Etruel Developments LLC
    88 * Author URI:   https://etruel.com
     
    1515// Plugin version
    1616if (!defined('WPECOUNTER_VERSION'))
    17     define('WPECOUNTER_VERSION', '2.0.4');
     17    define('WPECOUNTER_VERSION', '2.1');
    1818
    1919if (!class_exists('WPeCounter')) :
Note: See TracChangeset for help on using the changeset viewer.