PluginProbe ʕ •ᴥ•ʔ
Post Views Counter / 1.7.10
Post Views Counter v1.7.10
1.7.13 1.7.12 1.7.11 trunk 1.0.0 1.0.1 1.0.10 1.0.11 1.0.12 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.2.0 1.2.1 1.2.10 1.2.11 1.2.12 1.2.13 1.2.14 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3 1.3.1 1.3.10 1.3.11 1.3.12 1.3.13 1.3.2 1.3.2.1 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.7.0 1.7.1 1.7.10 1.7.2 1.7.3 1.7.4 1.7.5 1.7.6 1.7.7 1.7.8 1.7.9
post-views-counter / post-views-counter.php
post-views-counter Last commit date
assets 2 months ago blocks 2 months ago css 2 months ago includes 2 months ago js 2 months ago languages 2 months ago index.php 6 years ago post-views-counter.php 2 months ago readme.txt 2 months ago
post-views-counter.php
1114 lines
1 <?php
2 /*
3 Plugin Name: Post Views Counter
4 Description: Post Views Counter allows you to collect and display how many times a post, page, or other content has been viewed in a simple, fast and reliable way.
5 Version: 1.7.10
6 Author: dFactory
7 Author URI: https://dfactory.co/
8 Plugin URI: https://postviewscounter.com/
9 License: MIT License
10 License URI: https://opensource.org/licenses/MIT
11 Text Domain: post-views-counter
12 Domain Path: /languages
13
14 Post Views Counter
15 Copyright (C) 2014-2026, Digital Factory - info@digitalfactory.pl
16
17 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
18
19 The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
20
21 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 */
23
24 // exit if accessed directly
25 if ( ! defined( 'ABSPATH' ) )
26 exit;
27
28 if ( ! class_exists( 'Post_Views_Counter' ) ) {
29 /**
30 * Post Views Counter final class.
31 *
32 * @class Post_Views_Counter
33 * @version 1.7.10
34 */
35 final class Post_Views_Counter {
36
37 private static $instance;
38 private $notices;
39 public $options;
40 public $defaults = [
41 'general' => [
42 'post_types_count' => [ 'post' ],
43 'taxonomies_count' => false,
44 'users_count' => false,
45 'other_count' => false,
46 'technology_count' => false,
47 'data_storage' => 'cookies',
48 'amp_support' => false,
49 'counter_mode' => 'php',
50 'time_between_counts' => [
51 'number' => 24,
52 'type' => 'hours'
53 ],
54 'count_time' => 'gmt',
55 'reset_counts' => [
56 'number' => 0,
57 'type' => 'days'
58 ],
59 'caching_compatibility' => false,
60 'object_cache' => false,
61 'flush_interval' => [
62 'number' => 5,
63 'type' => 'minutes'
64 ],
65 'exclude' => [
66 'groups' => [],
67 'roles' => []
68 ],
69 'exclude_groups' => [],
70 'exclude_roles' => [],
71 'exclude_ips' => [],
72 'strict_counts' => false,
73 'cron_run' => true,
74 'cron_update' => true,
75 'update_version' => 1,
76 'update_notice' => true,
77 'update_delay_date' => 0
78 ],
79 'display' => [
80 'label' => 'Post Views:',
81 'display_period' => 'total',
82 'taxonomies' => false,
83 'taxonomies_display' => [],
84 'user_display' => false,
85 'post_types_display' => [ 'post' ],
86 'page_types_display' => [ 'singular' ],
87 'restrict_display' => [
88 'groups' => [],
89 'roles' => []
90 ],
91 'restrict_display_groups' => [],
92 'restrict_display_roles' => [],
93 'position' => 'after',
94 'post_views_column' => true,
95 'restrict_edit_views' => false,
96 'dynamic_loading' => false,
97 'use_format' => true,
98 'display_style' => [
99 'icon' => true,
100 'text' => true
101 ],
102 'icon_class' => 'dashicons-chart-bar',
103 'toolbar_statistics' => true,
104 'menu_position' => 'top'
105 ],
106 'other' => [
107 'import_meta_key' => 'views',
108 'deactivation_delete' => false,
109 'license' => ''
110 ],
111 'integrations' => [
112 'integrations' => []
113 ],
114 'version' => '1.7.10'
115 ];
116
117 // instances
118 public $counter;
119 public $crawler;
120 public $cron;
121 public $dashboard;
122 public $frontend;
123 public $functions;
124 public $settings;
125 public $settings_api;
126 public $import;
127
128 /**
129 * Disable object cloning.
130 *
131 * @return void
132 */
133 public function __clone() {}
134
135 /**
136 * Disable unserializing of the class.
137 *
138 * @return void
139 */
140 public function __wakeup() {}
141
142 /**
143 * Main plugin instance, insures that only one instance of the class exists in memory at one time.
144 *
145 * @return object
146 */
147 public static function instance() {
148 if ( ! isset( self::$instance ) && ! ( self::$instance instanceof Post_Views_Counter ) ) {
149 self::$instance = new Post_Views_Counter();
150
151 // short init?
152 if ( defined( 'SHORTINIT' ) && SHORTINIT ) {
153 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-counter.php' );
154 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-crawler-detect.php' );
155 include_once( POST_VIEWS_COUNTER_PATH . 'includes/functions.php' );
156
157 self::$instance->counter = new Post_Views_Counter_Counter();
158 self::$instance->crawler = new Post_Views_Counter_Crawler_Detect();
159
160 // we need to initialize crawler here since it is not called in SHORTINIT mode
161 self::$instance->crawler->init();
162 // regular setup
163 } else {
164 self::$instance->includes();
165
166 // create settings API
167 self::$instance->settings_api = new Post_Views_Counter_Settings_API(
168 [
169 'object' => self::$instance,
170 'prefix' => 'pvc',
171 'slug' => 'post-views-counter',
172 'domain' => 'post-views-counter',
173 'plugin' => 'Post Views Counter',
174 'plugin_url' => POST_VIEWS_COUNTER_URL
175 ]
176 );
177
178 // initialize other classes
179 self::$instance->functions = new Post_Views_Counter_Functions();
180
181 new Post_Views_Counter_Update();
182
183 self::$instance->settings = new Post_Views_Counter_Settings();
184 self::$instance->import = new Post_Views_Counter_Import();
185
186 new Post_Views_Counter_Admin();
187 new Post_Views_Counter_Query();
188
189 self::$instance->cron = new Post_Views_Counter_Cron();
190 self::$instance->counter = new Post_Views_Counter_Counter();
191
192 new Post_Views_Counter_Columns();
193 new Post_Views_Counter_Columns_Modal();
194 new Post_Views_Counter_Traffic_Signals();
195 new Post_Views_Counter_Toolbar();
196
197 self::$instance->crawler = new Post_Views_Counter_Crawler_Detect();
198 self::$instance->frontend = new Post_Views_Counter_Frontend();
199 self::$instance->dashboard = new Post_Views_Counter_Dashboard();
200
201 new Post_Views_Counter_Widgets();
202 }
203 }
204
205 return self::$instance;
206 }
207
208 /**
209 * Setup plugin constants.
210 *
211 * @return void
212 */
213 private function define_constants() {
214 // fix plugin_basename empty $wp_plugin_paths var
215 if ( ! ( defined( 'SHORTINIT' ) && SHORTINIT ) ) {
216 define( 'POST_VIEWS_COUNTER_URL', plugins_url( '', __FILE__ ) );
217 define( 'POST_VIEWS_COUNTER_BASENAME', plugin_basename( __FILE__ ) );
218 define( 'POST_VIEWS_COUNTER_REL_PATH', dirname( POST_VIEWS_COUNTER_BASENAME ) );
219 }
220
221 define( 'POST_VIEWS_COUNTER_PATH', plugin_dir_path( __FILE__ ) );
222 }
223
224 /**
225 * Include required files.
226 *
227 * @return void
228 */
229 private function includes() {
230 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-functions.php' );
231 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-update.php' );
232 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-settings-api.php' );
233 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-settings.php' );
234 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-settings-general.php' );
235 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-settings-display.php' );
236 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-settings-reports.php' );
237 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-integrations.php' );
238 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-settings-integrations.php' );
239 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-settings-other.php' );
240 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-import.php' );
241 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-admin.php' );
242 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-columns.php' );
243 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-columns-modal.php' );
244 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-toolbar.php' );
245 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-query.php' );
246 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-cron.php' );
247 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-counter.php' );
248 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-crawler-detect.php' );
249 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-frontend.php' );
250 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-dashboard.php' );
251 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-widgets.php' );
252 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-traffic-signals.php' );
253 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-integration-gutenberg.php' );
254 }
255
256 /**
257 * Class constructor.
258 *
259 * @return void
260 */
261 private function __construct() {
262 // define plugin constants
263 $this->define_constants();
264
265 // short init?
266 if ( defined( 'SHORTINIT' ) && SHORTINIT ) {
267 $this->options = [
268 'general' => array_merge( $this->defaults['general'], get_option( 'post_views_counter_settings_general', $this->defaults['general'] ) ),
269 'display' => array_merge( $this->defaults['display'], get_option( 'post_views_counter_settings_display', $this->defaults['display'] ) ),
270 'other' => array_merge( $this->defaults['other'], get_option( 'post_views_counter_settings_other', $this->defaults['other'] ) )
271 ];
272
273 $this->options['general']['time_between_counts'] = $this->normalize_time_between_counts( isset( $this->options['general']['time_between_counts'] ) ? $this->options['general']['time_between_counts'] : null );
274
275 // migrate exclude to new fields
276 if ( isset( $this->options['general']['exclude'] ) && is_array( $this->options['general']['exclude'] ) ) {
277 if ( ! isset( $this->options['general']['exclude_groups'] ) )
278 $this->options['general']['exclude_groups'] = $this->options['general']['exclude']['groups'] ?? [];
279
280 if ( ! isset( $this->options['general']['exclude_roles'] ) )
281 $this->options['general']['exclude_roles'] = $this->options['general']['exclude']['roles'] ?? [];
282 }
283
284 return;
285 }
286
287 // activation hooks
288 register_activation_hook( __FILE__, [ $this, 'activation' ] );
289 register_deactivation_hook( __FILE__, [ $this, 'deactivation' ] );
290
291 // settings
292 $this->options = [
293 'general' => array_merge( $this->defaults['general'], get_option( 'post_views_counter_settings_general', $this->defaults['general'] ) ),
294 'display' => array_merge( $this->defaults['display'], get_option( 'post_views_counter_settings_display', $this->defaults['display'] ) ),
295 'other' => array_merge( $this->defaults['other'], get_option( 'post_views_counter_settings_other', $this->defaults['other'] ) ),
296 'integrations' => array_merge( $this->defaults['integrations'], get_option( 'post_views_counter_settings_integrations', $this->defaults['integrations'] ) )
297 ];
298
299 $this->options['general']['time_between_counts'] = $this->normalize_time_between_counts( isset( $this->options['general']['time_between_counts'] ) ? $this->options['general']['time_between_counts'] : null );
300
301 // 1.5.3+
302 if ( ! isset( $this->options['general']['post_views_column'] ) )
303 $this->options['general']['post_views_column'] = $this->options['display']['post_views_column'];
304
305 if ( ! isset( $this->options['general']['restrict_edit_views'] ) )
306 $this->options['general']['restrict_edit_views'] = $this->options['display']['restrict_edit_views'];
307
308 // migrate exclude to new fields
309 if ( isset( $this->options['general']['exclude'] ) && is_array( $this->options['general']['exclude'] ) ) {
310 if ( ! isset( $this->options['general']['exclude_groups'] ) )
311 $this->options['general']['exclude_groups'] = $this->options['general']['exclude']['groups'] ?? [];
312
313 if ( ! isset( $this->options['general']['exclude_roles'] ) )
314 $this->options['general']['exclude_roles'] = $this->options['general']['exclude']['roles'] ?? [];
315 }
316
317 // actions
318 add_action( 'plugins_loaded', [ $this, 'extend_caching_plugins' ], -1 );
319 add_action( 'admin_enqueue_scripts', [ $this, 'admin_enqueue_scripts' ] );
320 add_action( 'admin_print_styles', [ $this, 'admin_print_styles' ] );
321 add_action( 'wp_loaded', [ $this, 'load_pluggable_functions' ] );
322 add_action( 'init', [ $this, 'load_textdomain' ] );
323 add_action( 'init', [ $this, 'register_blocks' ] );
324 add_action( 'admin_init', [ $this, 'update_notice' ] );
325 add_action( 'wp_initialize_site', [ $this, 'init_new_network_site' ] );
326 add_action( 'wp_ajax_pvc_dismiss_notice', [ $this, 'dismiss_notice' ] );
327
328 // filters
329 add_filter( 'plugin_action_links_' . POST_VIEWS_COUNTER_BASENAME, [ $this, 'plugin_settings_link' ] );
330 }
331
332 /**
333 * Extend list of caching plugins.
334 *
335 * @return void
336 */
337 public function extend_caching_plugins() {
338 // add new caching plugins
339 add_filter( 'pvc_active_caching_plugins', [ $this->settings, 'extend_active_caching_plugins' ] );
340 add_filter( 'pvc_is_plugin_active', [ $this->settings, 'extend_is_plugin_active' ], 10, 2 );
341 }
342
343 /**
344 * Register blocks.
345 *
346 * @return void
347 */
348 public function register_blocks() {
349 // actions
350 add_action( 'enqueue_block_editor_assets', [ $this, 'block_editor_enqueue_scripts' ] );
351
352 // filters
353 add_filter( 'block_categories_all', [ $this, 'add_block_category' ] );
354
355 add_filter( 'register_block_type_args', [ $this, 'update_block_args' ], 10, 2 );
356
357 register_block_type( __DIR__ . '/blocks/most-viewed-posts' );
358 register_block_type( __DIR__ . '/blocks/post-views' );
359
360 // register placeholder blocks when the related blocks are unavailable
361 if ( ! class_exists( 'Post_Views_Counter_Pro' ) ) {
362 $pro_placeholders = [
363 'most-viewed-terms',
364 'most-viewed-users',
365 'term-views',
366 'user-views',
367 'site-views'
368 ];
369
370 foreach ( $pro_placeholders as $block_slug ) {
371 register_block_type( __DIR__ . '/blocks/pro-placeholder/' . $block_slug );
372 }
373 }
374 }
375
376 /**
377 * Enqueue block scripts.
378 *
379 * @return void
380 */
381 public function block_editor_enqueue_scripts() {
382 // register inline-only script handle
383 wp_register_script( 'post-views-counter-block-editor-script', false, [], false, true );
384 wp_enqueue_script( 'post-views-counter-block-editor-script' );
385
386 // enqueue block editor styles
387 wp_enqueue_style( 'pvc-block-editor', POST_VIEWS_COUNTER_URL . '/css/block-editor.css', [], $this->defaults['version'] );
388
389 $block_image_sizes = [];
390
391 // image sizes
392 $image_sizes = array_merge( [ 'full' ], get_intermediate_image_sizes() );
393
394 // sort image sizes by name, ascending
395 sort( $image_sizes, SORT_STRING );
396
397 foreach ( $image_sizes as $image_size ) {
398 $block_image_sizes[] = [
399 'label' => $image_size,
400 'value' => $image_size
401 ];
402 }
403
404 $post_types = Post_Views_Counter()->functions->get_post_types();
405
406 // prepare script data
407 $script_data = [
408 'postTypesKeys' => array_combine( array_keys( $post_types ), array_fill( 0, count( $post_types ), false ) ),
409 'postTypes' => $post_types,
410 'imageSizes' => $block_image_sizes,
411 'isProActive' => class_exists( 'Post_Views_Counter_Pro' )
412 ];
413
414 // force post as enabled
415 $script_data['postTypesKeys']['post'] = true;
416
417 // prepare script data
418 $script_data['periods'] = [ [
419 'label' => __( 'Total Views', 'post-views-counter' ),
420 'value' => 'total'
421 ] ];
422
423 $script_data = apply_filters( 'pvc_block_editor_data', $script_data );
424
425 wp_add_inline_script( 'post-views-counter-block-editor-script', 'var pvcBlockEditorData = ' . wp_json_encode( $script_data ) . ";\n", 'before' );
426 }
427
428 /**
429 * Update block arguments.
430 *
431 * @param array $args
432 * @param string $block_type
433 *
434 * @return array
435 */
436 public function update_block_args( $args, $block_type ) {
437 // most viewed posts block
438 if ( $block_type === 'post-views-counter/most-viewed-posts' )
439 $args['render_callback'] = [ $this, 'most_viewed_posts_render_callback' ];
440 // post views block
441 elseif ( $block_type === 'post-views-counter/post-views' )
442 $args['render_callback'] = [ $this, 'post_views_render_callback' ];
443
444 return $args;
445 }
446
447 /**
448 * Server side block renderer for most viewed posts.
449 *
450 * @param array $attributes
451 * @param string $content
452 *
453 * @return array
454 */
455 public function most_viewed_posts_render_callback( $attributes, $content ) {
456 $post_types = [];
457
458 foreach ( $attributes['postTypes'] as $post_type => $enabled ) {
459 if ( $enabled === true || $enabled === 'true' )
460 $post_types[] = $post_type;
461 }
462
463 // map block attributes
464 $args = [
465 'number_of_posts' => max( 1, (int) $attributes['numberOfPosts'] ),
466 'post_type' => $post_types,
467 'period' => $attributes['period'],
468 'order' => $attributes['order'],
469 'thumbnail_size' => $attributes['thumbnailSize'],
470 'list_type' => $attributes['listType'],
471 'show_post_views' => (bool) $attributes['displayPostViews'],
472 'show_post_thumbnail' => (bool) $attributes['displayPostThumbnail'],
473 'show_post_author' => (bool) $attributes['displayPostAuthor'],
474 'show_post_excerpt' => (bool) $attributes['displayPostExcerpt'],
475 'no_posts_message' => $attributes['noPostsMessage']
476 ];
477
478 $title = trim( $attributes['title'] );
479
480 $html = '<div ' . get_block_wrapper_attributes() . '>';
481
482 if ( $title !== '' )
483 $html .= '<h2 class="block-title">' . esc_html( $title ) . '</h2>';
484
485 $html .= pvc_most_viewed_posts( $args, false );
486 $html .= '</div>';
487
488 return $html;
489 }
490
491 /**
492 * Server side block renderer for post views.
493 *
494 * @param array $attributes
495 * @param string $content
496 *
497 * @return array
498 */
499 public function post_views_render_callback( $attributes, $content ) {
500 $html = '<div ' . get_block_wrapper_attributes() . '>';
501 $html .= pvc_post_views( (int) $attributes['postID'], false, $attributes['period'] );
502 $html .= '</div>';
503
504 return $html;
505 }
506
507 /**
508 * Add new blocks category.
509 *
510 * @param array $categories
511 *
512 * @return array
513 */
514 public function add_block_category( $categories ) {
515 $categories[] = [
516 'slug' => 'post-views-counter',
517 'title' => 'Post Views Counter'
518 ];
519
520 return $categories;
521 }
522
523 /**
524 * Update notice.
525 *
526 * @return void
527 */
528 public function update_notice() {
529 if ( ! current_user_can( 'install_plugins' ) )
530 return;
531
532 $current_update = 2;
533
534 // get current time
535 $current_time = time();
536
537 if ( $this->options['general']['update_version'] < $current_update ) {
538 // check version, if update version is lower than plugin version, set update notice to true
539 $this->options['general'] = array_merge(
540 $this->options['general'],
541 [
542 'update_version' => $current_update,
543 'update_notice' => true
544 ]
545 );
546
547 update_option( 'post_views_counter_settings_general', $this->options['general'] );
548
549 // set activation date
550 $activation_date = get_option( 'post_views_counter_activation_date' );
551
552 if ( $activation_date === false )
553 update_option( 'post_views_counter_activation_date', $current_time );
554 }
555
556 // display current version notice
557 if ( $this->options['general']['update_notice'] === true ) {
558 // include notice js, only if needed
559 add_action( 'admin_print_scripts', [ $this, 'admin_inline_js' ], 999 );
560
561 // get activation date
562 $activation_date = get_option( 'post_views_counter_activation_date' );
563
564 if ( (int) $this->options['general']['update_delay_date'] === 0 ) {
565 if ( $activation_date + 2 * WEEK_IN_SECONDS > $current_time )
566 $this->options['general']['update_delay_date'] = $activation_date + 2 * WEEK_IN_SECONDS;
567 else
568 $this->options['general']['update_delay_date'] = $current_time;
569
570 update_option( 'post_views_counter_settings_general', $this->options['general'] );
571 }
572
573 if ( ( ! empty( $this->options['general']['update_delay_date'] ) ? (int) $this->options['general']['update_delay_date'] : $current_time ) <= $current_time )
574 $this->add_notice( sprintf( __( "Hey, you've been using <strong>Post Views Counter</strong> for more than %s.", 'post-views-counter' ), human_time_diff( $activation_date, $current_time ) ) . '<br />' . __( 'Could you please do me a BIG favor and give it a 5-star rating on WordPress to help us spread the word and boost our motivation.', 'post-views-counter' ) . '<br /><br />' . __( 'Your help is much appreciated. Thank you very much', 'post-views-counter' ) . ' ~ <strong>Bartosz Arendt</strong>, ' . __( 'founder of', 'post-views-counter' ) . ' <a href="https://postviewscounter.com/" target="_blank">Post Views Counter</a>.' . '<br /><br />' . '<a href="https://wordpress.org/support/plugin/post-views-counter/reviews/?filter=5#new-post" class="pvc-dismissible-notice" target="_blank" rel="noopener">' . __( 'Ok, you deserve it', 'post-views-counter' ) . '</a><br /><a href="#" class="pvc-dismissible-notice pvc-delay-notice" rel="noopener">' . __( 'Nope, maybe later', 'post-views-counter' ) . '</a><br /><a href="#" class="pvc-dismissible-notice" rel="noopener">' . __( 'I already did', 'post-views-counter' ) . '</a>', 'notice notice-info is-dismissible pvc-notice' );
575 }
576 }
577
578 /**
579 * Add admin notices.
580 *
581 * @param string $html Notice HTML
582 * @param string $status Notice status
583 * @param bool $paragraph Whether to use paragraph
584 * @param bool $network
585 *
586 * @return void
587 */
588 public function add_notice( $html = '', $status = 'error', $paragraph = true, $network = false ) {
589 $this->notices[] = [
590 'html' => $html,
591 'status' => $status,
592 'paragraph' => $paragraph
593 ];
594
595 add_action( 'admin_notices', [ $this, 'display_notice' ] );
596
597 if ( $network )
598 add_action( 'network_admin_notices', [ $this, 'display_notice' ] );
599 }
600
601 /**
602 * Print admin notices.
603 *
604 * @return void
605 */
606 public function display_notice() {
607 $allowed_html = array_merge(
608 wp_kses_allowed_html( 'post' ),
609 [
610 'input' => [
611 'type' => true,
612 'name' => true,
613 'class' => true,
614 'value' => true
615 ],
616 'form' => [
617 'action' => true,
618 'method' => true
619 ]
620 ]
621 );
622
623 foreach ( $this->notices as $notice ) {
624 echo '
625 <div class="' . esc_attr( $notice['status'] ) . '">
626 ' . ( $notice['paragraph'] ? '<p>' : '' ) . '
627 ' . wp_kses( $notice['html'], $allowed_html ) . '
628 ' . ( $notice['paragraph'] ? '</p>' : '' ) . '
629 </div>';
630 }
631 }
632
633 /**
634 * Print admin scripts.
635 *
636 * @return void
637 */
638 public function admin_inline_js() {
639 if ( ! current_user_can( 'install_plugins' ) )
640 return;
641
642 // register and enqueue styles
643 wp_register_script( 'pvc-notices', false );
644 wp_enqueue_script( 'pvc-notices' );
645
646 // add styles
647 wp_add_inline_script( 'pvc-notices', "
648 ( function( $ ) {
649 // ready event
650 $( function() {
651 // save dismiss state
652 $( '.pvc-notice.is-dismissible' ).on( 'click', '.notice-dismiss, .pvc-dismissible-notice', function( e ) {
653 if ( e.currentTarget.target !== '_blank' )
654 e.preventDefault();
655
656 var notice_action = 'hide';
657
658 if ( e.currentTarget.classList.contains( 'pvc-delay-notice' ) )
659 notice_action = 'delay';
660
661 $.post( ajaxurl, {
662 action: 'pvc_dismiss_notice',
663 notice_action: notice_action,
664 url: '" . esc_url_raw( admin_url( 'admin-ajax.php' ) ) . "',
665 nonce: '" . esc_attr( wp_create_nonce( 'pvc_dismiss_notice' ) ) . "'
666 } );
667
668 $( e.delegateTarget ).slideUp( 'fast' );
669 } );
670 } );
671 } )( jQuery );
672 ", 'after' );
673 }
674
675 /**
676 * Dismiss notice.
677 *
678 * @return void
679 */
680 public function dismiss_notice() {
681 if ( ! current_user_can( 'install_plugins' ) )
682 return;
683
684 if ( isset( $_REQUEST['nonce'] ) && wp_verify_nonce( $_REQUEST['nonce'], 'pvc_dismiss_notice' ) ) {
685 $notice_action = empty( $_REQUEST['notice_action'] ) || $_REQUEST['notice_action'] === 'hide' ? 'hide' : sanitize_text_field( $_REQUEST['notice_action'] );
686
687 switch ( $notice_action ) {
688 // delay notice
689 case 'delay':
690 // set delay period to 1 week from now
691 $this->options['general'] = array_merge(
692 $this->options['general'],
693 [
694 'update_delay_date' => time() + 2 * WEEK_IN_SECONDS
695 ]
696 );
697 update_option( 'post_views_counter_settings_general', $this->options['general'] );
698 break;
699
700 // hide notice
701 default:
702 $this->options['general'] = array_merge(
703 $this->options['general'],
704 [
705 'update_notice' => false
706 ]
707 );
708 $this->options['general'] = array_merge(
709 $this->options['general'],
710 [
711 'update_delay_date' => 0
712 ]
713 );
714
715 update_option( 'post_views_counter_settings_general', $this->options['general'] );
716 }
717 }
718
719 exit;
720 }
721
722 /**
723 * Plugin activation.
724 *
725 * @global object $wpdb
726 *
727 * @param bool $network
728 *
729 * @return void
730 */
731 public function activation( $network ) {
732 // network activation?
733 if ( is_multisite() && $network ) {
734 global $wpdb;
735
736 // get all available sites
737 $blogs_ids = $wpdb->get_col( 'SELECT blog_id FROM ' . $wpdb->blogs );
738
739 foreach ( $blogs_ids as $blog_id ) {
740 // change to another site
741 switch_to_blog( (int) $blog_id );
742
743 // run current site activation process
744 $this->activate_site();
745
746 restore_current_blog();
747 }
748 } else
749 $this->activate_site();
750 }
751
752 /**
753 * Single site activation.
754 *
755 * @global object $wpdb
756 * @global string $charset_collate
757 *
758 * @return void
759 */
760 public function activate_site() {
761 global $wpdb, $charset_collate;
762
763 // required for dbdelta
764 require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
765
766 // create post views table
767 dbDelta( '
768 CREATE TABLE IF NOT EXISTS ' . $wpdb->prefix . 'post_views (
769 `id` bigint unsigned NOT NULL,
770 `type` tinyint(1) unsigned NOT NULL,
771 `period` varchar(8) NOT NULL,
772 `count` bigint unsigned NOT NULL,
773 PRIMARY KEY (type, period, id),
774 UNIQUE INDEX id_type_period_count (id, type, period, count) USING BTREE,
775 INDEX type_period_count (type, period, count) USING BTREE
776 ) ' . $charset_collate . ';'
777 );
778
779 // add default options
780 add_option( 'post_views_counter_settings_general', $this->defaults['general'], null, false );
781 add_option( 'post_views_counter_settings_display', $this->defaults['display'], null, false );
782 add_option( 'post_views_counter_settings_other', $this->defaults['other'], null, false );
783 add_option( 'post_views_counter_version', $this->defaults['version'], null, false );
784 }
785
786 /**
787 * Plugin deactivation.
788 *
789 * @global object $wpdb
790 *
791 * @param bool $network
792 *
793 * @return void
794 */
795 public function deactivation( $network ) {
796 // network deactivation?
797 if ( is_multisite() && $network ) {
798 global $wpdb;
799
800 // get all available sites
801 $blogs_ids = $wpdb->get_col( 'SELECT blog_id FROM ' . $wpdb->blogs );
802
803 foreach ( $blogs_ids as $blog_id ) {
804 // change to another site
805 switch_to_blog( (int) $blog_id );
806
807 // run current site deactivation process
808 $this->deactivate_site( true );
809
810 restore_current_blog();
811 }
812 } else
813 $this->deactivate_site();
814 }
815
816 /**
817 * Single site deactivation.
818 *
819 * @global object $wpdb
820 *
821 * @param bool $multi
822 *
823 * @return void
824 */
825 public function deactivate_site( $multi = false ) {
826 if ( $multi === true ) {
827 $options = get_option( 'post_views_counter_settings_other' );
828 $check = $options['deactivation_delete'];
829 } else
830 $check = $this->options['other']['deactivation_delete'];
831
832 // delete options if needed
833 if ( $check ) {
834 // delete options
835 delete_option( 'post_views_counter_settings_general' );
836 delete_option( 'post_views_counter_settings_display' );
837 delete_option( 'post_views_counter_settings_other' );
838 delete_option( 'post_views_counter_activation_date' );
839 delete_option( 'post_views_counter_version' );
840
841 // delete transients
842 delete_transient( 'post_views_counter_ip_cache' );
843
844 global $wpdb;
845
846 // delete table from database
847 $wpdb->query( 'DROP TABLE IF EXISTS ' . $wpdb->prefix . 'post_views' );
848 }
849
850 // remove schedule
851 wp_clear_scheduled_hook( 'pvc_reset_counts' );
852
853 remove_action( 'pvc_reset_counts', [ $this->cron, 'reset_counts' ] );
854 }
855
856 /**
857 * Initialize new network site.
858 *
859 * @param object $site
860 *
861 * @return void
862 */
863 public function init_new_network_site( $site ) {
864 if ( is_multisite() ) {
865 // change to another site
866 switch_to_blog( $site->blog_id );
867
868 // run current site activation process
869 $this->activate_site();
870
871 restore_current_blog();
872 }
873 }
874
875 /**
876 * Load text domain.
877 *
878 * @return void
879 */
880 public function load_textdomain() {
881 load_plugin_textdomain( 'post-views-counter', false, POST_VIEWS_COUNTER_REL_PATH . '/languages/' );
882 }
883
884 /**
885 * Load pluggable template functions.
886 *
887 * @return void
888 */
889 public function load_pluggable_functions() {
890 include_once( POST_VIEWS_COUNTER_PATH . 'includes/functions.php' );
891 }
892
893 /**
894 * Enqueue admin scripts and styles.
895 *
896 * @global string $post_type
897 * @global string $wp_version
898 *
899 * @param string $page
900 *
901 * @return void
902 */
903 public function admin_enqueue_scripts( $page ) {
904 global $post_type;
905 global $wp_version;
906
907 // register styles
908 wp_register_style( 'pvc-admin', POST_VIEWS_COUNTER_URL . '/css/admin-settings.css', [], $this->defaults['version'] );
909 wp_register_style( 'pvc-admin-post-style', POST_VIEWS_COUNTER_URL . '/css/admin-post.css', [], $this->defaults['version'] );
910
911 // register scripts
912 wp_register_script( 'pvc-admin-settings', POST_VIEWS_COUNTER_URL . '/js/admin-settings.js', [ 'jquery' ], $this->defaults['version'] );
913 wp_register_script( 'pvc-admin-post', POST_VIEWS_COUNTER_URL . '/js/admin-post.js', [ 'jquery' ], $this->defaults['version'] );
914 wp_register_script( 'pvc-admin-quick-edit', POST_VIEWS_COUNTER_URL . '/js/admin-quick-edit.js', [ 'jquery' ], $this->defaults['version'] );
915
916 // load on pvc settings page
917 if ( in_array( $page, [ 'toplevel_page_post-views-counter', 'settings_page_post-views-counter' ], true ) ) {
918 wp_enqueue_script( 'pvc-admin-settings' );
919
920 // prepare script data
921 $script_data = [
922 'resetToDefaults' => esc_html__( 'Are you sure you want to reset these settings to defaults?', 'post-views-counter' ),
923 'resetViews' => esc_html__( 'Are you sure you want to delete all existing data?', 'post-views-counter' ),
924 'importViews' => esc_html__( 'Are you sure you want to import views now?', 'post-views-counter' )
925 ];
926
927 wp_add_inline_script( 'pvc-admin-settings', 'var pvcArgsSettings = ' . wp_json_encode( $script_data ) . ";\n", 'before' );
928
929 wp_enqueue_style( 'pvc-admin' );
930 // load on single post page
931 } elseif ( $page === 'post.php' || $page === 'post-new.php' ) {
932 $post_types = Post_Views_Counter()->options['general']['post_types_count'];
933
934 if ( ! in_array( $post_type, (array) $post_types ) )
935 return;
936
937 wp_enqueue_style( 'pvc-admin-post-style' );
938
939 // only enqueue post script in classic editor (not block editor)
940 // block editor doesn't have the #post-views metabox that this script targets
941 if ( ! function_exists( 'use_block_editor_for_post' ) ) {
942 // WP < 5.0, always use classic editor
943 wp_enqueue_script( 'pvc-admin-post' );
944 } else {
945 // get post ID from request
946 $post_id = isset( $_GET['post'] ) ? absint( $_GET['post'] ) : ( isset( $_POST['post_ID'] ) ? absint( $_POST['post_ID'] ) : 0 );
947
948 // check if classic editor is being used
949 if ( $post_id && ! use_block_editor_for_post( $post_id ) ) {
950 wp_enqueue_script( 'pvc-admin-post' );
951 } elseif ( ! $post_id && $page === 'post-new.php' && ! use_block_editor_for_post_type( $post_type ) ) {
952 // new post - check if block editor is default for this post type
953 wp_enqueue_script( 'pvc-admin-post' );
954 }
955 }
956 // edit post
957 } elseif ( $page === 'edit.php' ) {
958 $post_types = Post_Views_Counter()->options['general']['post_types_count'];
959
960 if ( ! in_array( $post_type, (array) $post_types ) )
961 return;
962
963 wp_enqueue_style( 'pvc-admin-post-style' );
964
965 // woocommerce
966 if ( get_post_type() !== 'product' ) {
967 wp_enqueue_script( 'pvc-admin-quick-edit' );
968
969 // prepare script data
970 $script_data = [
971 'nonce' => wp_create_nonce( 'pvc_save_bulk_post_views' ),
972 'wpVersion59' => version_compare( $wp_version, '5.9', '>=' )
973 ];
974
975 wp_add_inline_script( 'pvc-admin-quick-edit', 'var pvcArgsQuickEdit = ' . wp_json_encode( $script_data ) . ";\n", 'before' );
976 }
977 // widgets
978 } elseif ( $page === 'widgets.php' )
979 wp_enqueue_script( 'pvc-admin-widgets', POST_VIEWS_COUNTER_URL . '/js/admin-widgets.js', [ 'jquery' ], $this->defaults['version'] );
980 // media
981 elseif ( $page === 'upload.php' )
982 wp_enqueue_style( 'pvc-admin-post-style' );
983 }
984
985 /**
986 * Load admin style inline, for menu badge only.
987 *
988 * @return void
989 */
990 public function admin_print_styles() {
991 echo '
992 <style>
993 .toplevel_page_post-views-counter .pvc-admin-menu-new,
994 .settings_page_post-views-counter .pvc-admin-menu-new {
995 font-size: 10px;
996 vertical-align: super;
997 color: #ffc107;
998 }
999 </style>
1000 ';
1001 }
1002
1003 /**
1004 * Normalize the Count Interval option to canonical hours.
1005 *
1006 * @param mixed $time_between_counts
1007 * @param array $default_interval
1008 *
1009 * @return array
1010 */
1011 public function normalize_time_between_counts( $time_between_counts, $default_interval = [] ) {
1012 $default_number = isset( $default_interval['number'] ) ? (int) $default_interval['number'] : (int) $this->defaults['general']['time_between_counts']['number'];
1013 $default_interval = [
1014 'number' => $default_number,
1015 'type' => 'hours'
1016 ];
1017
1018 if ( ! is_array( $time_between_counts ) )
1019 return $default_interval;
1020
1021 if ( ! isset( $time_between_counts['number'], $time_between_counts['type'] ) || ! is_scalar( $time_between_counts['number'] ) || ! is_scalar( $time_between_counts['type'] ) )
1022 return $default_interval;
1023
1024 $number = (int) $time_between_counts['number'];
1025 $type = sanitize_key( (string) $time_between_counts['type'] );
1026
1027 switch ( $type ) {
1028 case 'minutes':
1029 $hours = (int) ( $number / 60 );
1030 break;
1031 case 'hours':
1032 $hours = $number;
1033 break;
1034 case 'days':
1035 $hours = $number * 24;
1036 break;
1037 case 'weeks':
1038 $hours = $number * 168;
1039 break;
1040 case 'months':
1041 $hours = $number * 720;
1042 break;
1043 case 'years':
1044 $hours = $number * 24 * 365;
1045 break;
1046 default:
1047 return $default_interval;
1048 }
1049
1050 return [
1051 'number' => max( 0, min( 720, (int) $hours ) ),
1052 'type' => 'hours'
1053 ];
1054 }
1055
1056 /**
1057 * Get the preferred admin menu position.
1058 *
1059 * @return string
1060 */
1061 public function get_menu_position() {
1062 $position = isset( $this->options['display']['menu_position'] ) ? $this->options['display']['menu_position'] : '';
1063
1064 if ( in_array( $position, [ 'top', 'sub' ], true ) )
1065 return $position;
1066
1067 if ( isset( $this->options['other']['menu_position'] ) && in_array( $this->options['other']['menu_position'], [ 'top', 'sub' ], true ) )
1068 return $this->options['other']['menu_position'];
1069
1070 return 'top';
1071 }
1072
1073 /**
1074 * Add link to Settings page.
1075 *
1076 * @param array $links
1077 *
1078 * @return array
1079 */
1080 public function plugin_settings_link( $links ) {
1081 if ( ! current_user_can( 'manage_options' ) )
1082 return $links;
1083
1084 // submenu?
1085 if ( $this->get_menu_position() === 'sub' )
1086 $url = admin_url( 'options-general.php?page=post-views-counter' );
1087 // topmenu?
1088 else
1089 $url = admin_url( 'admin.php?page=post-views-counter' );
1090
1091 array_unshift( $links, sprintf( '<a href="%s">%s</a>', esc_url_raw( $url ), esc_html__( 'Settings', 'post-views-counter' ) ) );
1092
1093 return $links;
1094 }
1095 }
1096 }
1097
1098 /**
1099 * Initialize Post Views Counter.
1100 *
1101 * @return object
1102 */
1103 function Post_Views_Counter() {
1104 static $instance;
1105
1106 // first call to instance() initializes the plugin
1107 if ( $instance === null || ! ( $instance instanceof Post_Views_Counter ) )
1108 $instance = Post_Views_Counter::instance();
1109
1110 return $instance;
1111 }
1112
1113 Post_Views_Counter();
1114