PluginProbe ʕ •ᴥ•ʔ
Post Views Counter / 1.7.3
Post Views Counter v1.7.3
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 5 months ago blocks 5 months ago css 5 months ago includes 5 months ago js 5 months ago languages 5 months ago index.php 6 years ago post-views-counter.php 5 months ago readme.txt 5 months ago
post-views-counter.php
1043 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.3
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-2025, 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.2
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 'position' => 'after',
92 'post_views_column' => true,
93 'restrict_edit_views' => false,
94 'dynamic_loading' => false,
95 'use_format' => true,
96 'display_style' => [
97 'icon' => true,
98 'text' => true
99 ],
100 'icon_class' => 'dashicons-chart-bar',
101 'toolbar_statistics' => true,
102 'menu_position' => 'top'
103 ],
104 'other' => [
105 'import_meta_key' => 'views',
106 'deactivation_delete' => false,
107 'license' => ''
108 ],
109 'integrations' => [
110 'integrations' => []
111 ],
112 'version' => '1.7.3'
113 ];
114
115 // instances
116 public $counter;
117 public $crawler;
118 public $cron;
119 public $dashboard;
120 public $frontend;
121 public $functions;
122 public $settings;
123 public $settings_api;
124 public $import;
125
126 /**
127 * Disable object cloning.
128 *
129 * @return void
130 */
131 public function __clone() {}
132
133 /**
134 * Disable unserializing of the class.
135 *
136 * @return void
137 */
138 public function __wakeup() {}
139
140 /**
141 * Main plugin instance, insures that only one instance of the class exists in memory at one time.
142 *
143 * @return object
144 */
145 public static function instance() {
146 if ( ! isset( self::$instance ) && ! ( self::$instance instanceof Post_Views_Counter ) ) {
147 self::$instance = new Post_Views_Counter();
148
149 // short init?
150 if ( defined( 'SHORTINIT' ) && SHORTINIT ) {
151 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-counter.php' );
152 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-crawler-detect.php' );
153 include_once( POST_VIEWS_COUNTER_PATH . 'includes/functions.php' );
154
155 self::$instance->counter = new Post_Views_Counter_Counter();
156 self::$instance->crawler = new Post_Views_Counter_Crawler_Detect();
157
158 // we need to initialize crawler here since it is not called in SHORTINIT mode
159 self::$instance->crawler->init();
160 // regular setup
161 } else {
162 self::$instance->includes();
163
164 // create settings API
165 self::$instance->settings_api = new Post_Views_Counter_Settings_API(
166 [
167 'object' => self::$instance,
168 'prefix' => 'pvc',
169 'slug' => 'post-views-counter',
170 'domain' => 'post-views-counter',
171 'plugin' => 'Post Views Counter',
172 'plugin_url' => POST_VIEWS_COUNTER_URL
173 ]
174 );
175
176 // initialize other classes
177 self::$instance->functions = new Post_Views_Counter_Functions();
178
179 new Post_Views_Counter_Update();
180
181 self::$instance->settings = new Post_Views_Counter_Settings();
182 self::$instance->import = new Post_Views_Counter_Import();
183
184 new Post_Views_Counter_Admin();
185 new Post_Views_Counter_Query();
186
187 self::$instance->cron = new Post_Views_Counter_Cron();
188 self::$instance->counter = new Post_Views_Counter_Counter();
189
190 new Post_Views_Counter_Columns();
191 new Post_Views_Counter_Toolbar();
192
193 self::$instance->crawler = new Post_Views_Counter_Crawler_Detect();
194 self::$instance->frontend = new Post_Views_Counter_Frontend();
195 self::$instance->dashboard = new Post_Views_Counter_Dashboard();
196
197 new Post_Views_Counter_Widgets();
198 }
199 }
200
201 return self::$instance;
202 }
203
204 /**
205 * Setup plugin constants.
206 *
207 * @return void
208 */
209 private function define_constants() {
210 // fix plugin_basename empty $wp_plugin_paths var
211 if ( ! ( defined( 'SHORTINIT' ) && SHORTINIT ) ) {
212 define( 'POST_VIEWS_COUNTER_URL', plugins_url( '', __FILE__ ) );
213 define( 'POST_VIEWS_COUNTER_BASENAME', plugin_basename( __FILE__ ) );
214 define( 'POST_VIEWS_COUNTER_REL_PATH', dirname( POST_VIEWS_COUNTER_BASENAME ) );
215 }
216
217 define( 'POST_VIEWS_COUNTER_PATH', plugin_dir_path( __FILE__ ) );
218 }
219
220 /**
221 * Include required files.
222 *
223 * @return void
224 */
225 private function includes() {
226 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-functions.php' );
227 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-update.php' );
228 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-settings-api.php' );
229 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-settings.php' );
230 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-settings-general.php' );
231 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-settings-display.php' );
232 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-settings-reports.php' );
233 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-integrations.php' );
234 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-settings-integrations.php' );
235 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-settings-other.php' );
236 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-import.php' );
237 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-admin.php' );
238 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-columns.php' );
239 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-toolbar.php' );
240 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-query.php' );
241 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-cron.php' );
242 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-counter.php' );
243 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-crawler-detect.php' );
244 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-frontend.php' );
245 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-dashboard.php' );
246 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-widgets.php' );
247 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-integration-gutenberg.php' );
248 }
249
250 /**
251 * Class constructor.
252 *
253 * @return void
254 */
255 private function __construct() {
256 // define plugin constants
257 $this->define_constants();
258
259 // load translations early to avoid just-in-time loading warnings in WP 6.7+
260 if ( ! ( defined( 'SHORTINIT' ) && SHORTINIT ) )
261 $this->load_textdomain();
262
263 // short init?
264 if ( defined( 'SHORTINIT' ) && SHORTINIT ) {
265 $this->options = [
266 'general' => array_merge( $this->defaults['general'], get_option( 'post_views_counter_settings_general', $this->defaults['general'] ) ),
267 'display' => array_merge( $this->defaults['display'], get_option( 'post_views_counter_settings_display', $this->defaults['display'] ) ),
268 'other' => array_merge( $this->defaults['other'], get_option( 'post_views_counter_settings_other', $this->defaults['other'] ) )
269 ];
270
271 // migrate exclude to new fields
272 if ( isset( $this->options['general']['exclude'] ) && is_array( $this->options['general']['exclude'] ) ) {
273 if ( ! isset( $this->options['general']['exclude_groups'] ) )
274 $this->options['general']['exclude_groups'] = $this->options['general']['exclude']['groups'] ?? [];
275
276 if ( ! isset( $this->options['general']['exclude_roles'] ) )
277 $this->options['general']['exclude_roles'] = $this->options['general']['exclude']['roles'] ?? [];
278 }
279
280 return;
281 }
282
283 // activation hooks
284 register_activation_hook( __FILE__, [ $this, 'activation' ] );
285 register_deactivation_hook( __FILE__, [ $this, 'deactivation' ] );
286
287 // settings
288 $this->options = [
289 'general' => array_merge( $this->defaults['general'], get_option( 'post_views_counter_settings_general', $this->defaults['general'] ) ),
290 'display' => array_merge( $this->defaults['display'], get_option( 'post_views_counter_settings_display', $this->defaults['display'] ) ),
291 'other' => array_merge( $this->defaults['other'], get_option( 'post_views_counter_settings_other', $this->defaults['other'] ) ),
292 'integrations' => array_merge( $this->defaults['integrations'], get_option( 'post_views_counter_settings_integrations', $this->defaults['integrations'] ) )
293 ];
294
295 // 1.5.3+
296 if ( ! isset( $this->options['general']['post_views_column'] ) )
297 $this->options['general']['post_views_column'] = $this->options['display']['post_views_column'];
298
299 if ( ! isset( $this->options['general']['restrict_edit_views'] ) )
300 $this->options['general']['restrict_edit_views'] = $this->options['display']['restrict_edit_views'];
301
302 // migrate exclude to new fields
303 if ( isset( $this->options['general']['exclude'] ) && is_array( $this->options['general']['exclude'] ) ) {
304 if ( ! isset( $this->options['general']['exclude_groups'] ) )
305 $this->options['general']['exclude_groups'] = $this->options['general']['exclude']['groups'] ?? [];
306
307 if ( ! isset( $this->options['general']['exclude_roles'] ) )
308 $this->options['general']['exclude_roles'] = $this->options['general']['exclude']['roles'] ?? [];
309 }
310
311 // actions
312 add_action( 'plugins_loaded', [ $this, 'extend_caching_plugins' ], -1 );
313 add_action( 'admin_enqueue_scripts', [ $this, 'admin_enqueue_scripts' ] );
314 add_action( 'admin_print_styles', [ $this, 'admin_print_styles' ] );
315 add_action( 'wp_loaded', [ $this, 'load_pluggable_functions' ] );
316 add_action( 'init', [ $this, 'register_blocks' ] );
317 add_action( 'admin_init', [ $this, 'update_notice' ] );
318 add_action( 'wp_initialize_site', [ $this, 'init_new_network_site' ] );
319 add_action( 'wp_ajax_pvc_dismiss_notice', [ $this, 'dismiss_notice' ] );
320
321 // filters
322 add_filter( 'plugin_action_links_' . POST_VIEWS_COUNTER_BASENAME, [ $this, 'plugin_settings_link' ] );
323 }
324
325 /**
326 * Extend list of caching plugins.
327 *
328 * @return void
329 */
330 public function extend_caching_plugins() {
331 // add new caching plugins
332 add_filter( 'pvc_active_caching_plugins', [ $this->settings, 'extend_active_caching_plugins' ] );
333 add_filter( 'pvc_is_plugin_active', [ $this->settings, 'extend_is_plugin_active' ], 10, 2 );
334 }
335
336 /**
337 * Register blocks.
338 *
339 * @global object $wp_version
340 *
341 * @return void
342 */
343 public function register_blocks() {
344 global $wp_version;
345
346 // actions
347 add_action( 'enqueue_block_editor_assets', [ $this, 'block_editor_enqueue_scripts' ] );
348
349 // filters
350 if ( version_compare( $wp_version, '5.8', '>=' ) )
351 add_filter( 'block_categories_all', [ $this, 'add_block_category' ] );
352 else
353 add_filter( 'block_categories', [ $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/build' );
358 register_block_type( __DIR__ . '/blocks/post-views/build' );
359 }
360
361 /**
362 * Enqueue block scripts.
363 *
364 * @global object $wp_version
365 *
366 * @return void
367 */
368 public function block_editor_enqueue_scripts() {
369 // enqueue script
370 wp_enqueue_script( 'post-views-counter-block-editor-script', POST_VIEWS_COUNTER_URL . '/js/dummy.js', [] );
371
372 $block_image_sizes = [];
373
374 // image sizes
375 $image_sizes = array_merge( [ 'full' ], get_intermediate_image_sizes() );
376
377 // sort image sizes by name, ascending
378 sort( $image_sizes, SORT_STRING );
379
380 foreach ( $image_sizes as $image_size ) {
381 $block_image_sizes[] = [
382 'label' => $image_size,
383 'value' => $image_size
384 ];
385 }
386
387 $post_types = Post_Views_Counter()->functions->get_post_types();
388
389 // prepare script data
390 $script_data = [
391 'postTypesKeys' => array_combine( array_keys( $post_types ), array_fill( 0, count( $post_types ), false ) ),
392 'postTypes' => $post_types,
393 'imageSizes' => $block_image_sizes
394 ];
395
396 // force post as enabled
397 $script_data['postTypesKeys']['post'] = true;
398
399 // prepare script data
400 $script_data['periods'] = [ [
401 'label' => __( 'Total Views', 'post-views-counter' ),
402 'value' => 'total'
403 ] ];
404
405 $script_data = apply_filters( 'pvc_block_editor_data', $script_data );
406
407 wp_add_inline_script( 'post-views-counter-block-editor-script', 'var pvcBlockEditorData = ' . wp_json_encode( $script_data ) . ";\n", 'before' );
408 }
409
410 /**
411 * Update block arguments.
412 *
413 * @param array $args
414 * @param string $block_type
415 *
416 * @return array
417 */
418 function update_block_args( $args, $block_type ) {
419 // most viewed posts block
420 if ( $block_type === 'post-views-counter/most-viewed-posts' )
421 $args['render_callback'] = [ $this, 'most_viewed_posts_render_callback' ];
422 // post views block
423 elseif ( $block_type === 'post-views-counter/post-views' )
424 $args['render_callback'] = [ $this, 'post_views_render_callback' ];
425
426 return $args;
427 }
428
429 /**
430 * Server side block renderer for most viewed posts.
431 *
432 * @param array $attributes
433 * @param string $content
434 *
435 * @return array
436 */
437 function most_viewed_posts_render_callback( $attributes, $content ) {
438 $post_types = [];
439
440 foreach ( $attributes['postTypes'] as $post_type => $enabled ) {
441 if ( $enabled === true || $enabled === 'true' )
442 $post_types[] = $post_type;
443 }
444
445 // map block attributes
446 $args = [
447 'number_of_posts' => max( 1, (int) $attributes['numberOfPosts'] ),
448 'post_type' => $post_types,
449 'period' => $attributes['period'],
450 'order' => $attributes['order'],
451 'thumbnail_size' => $attributes['thumbnailSize'],
452 'list_type' => $attributes['listType'],
453 'show_post_views' => (bool) $attributes['displayPostViews'],
454 'show_post_thumbnail' => (bool) $attributes['displayPostThumbnail'],
455 'show_post_author' => (bool) $attributes['displayPostAuthor'],
456 'show_post_excerpt' => (bool) $attributes['displayPostExcerpt'],
457 'no_posts_message' => $attributes['noPostsMessage']
458 ];
459
460 $title = trim( $attributes['title'] );
461
462 $html = '<div ' . get_block_wrapper_attributes() . '>';
463
464 if ( $title !== '' )
465 $html .= '<h2 class="block-title">' . esc_html( $title ) . '</h2>';
466
467 $html .= pvc_most_viewed_posts( $args, false );
468 $html .= '</div>';
469
470 return $html;
471 }
472
473 /**
474 * Server side block renderer for post views.
475 *
476 * @param array $attributes
477 * @param string $content
478 *
479 * @return array
480 */
481 function post_views_render_callback( $attributes, $content ) {
482 $html = '<div ' . get_block_wrapper_attributes() . '>';
483 $html .= pvc_post_views( (int) $attributes['postID'], false, $attributes['period'] );
484 $html .= '</div>';
485
486 return $html;
487 }
488
489 /**
490 * Add new blocks category.
491 *
492 * @param array $categories
493 *
494 * @return array
495 */
496 function add_block_category( $categories ) {
497 $categories[] = [
498 'slug' => 'post-views-counter',
499 'title' => 'Post Views Counter'
500 ];
501
502 return $categories;
503 }
504
505 /**
506 * Update notice.
507 *
508 * @return void
509 */
510 public function update_notice() {
511 if ( ! current_user_can( 'install_plugins' ) )
512 return;
513
514 $current_update = 2;
515
516 // get current time
517 $current_time = time();
518
519 if ( $this->options['general']['update_version'] < $current_update ) {
520 // check version, if update version is lower than plugin version, set update notice to true
521 $this->options['general'] = array_merge(
522 $this->options['general'],
523 [
524 'update_version' => $current_update,
525 'update_notice' => true
526 ]
527 );
528
529 update_option( 'post_views_counter_settings_general', $this->options['general'] );
530
531 // set activation date
532 $activation_date = get_option( 'post_views_counter_activation_date' );
533
534 if ( $activation_date === false )
535 update_option( 'post_views_counter_activation_date', $current_time );
536 }
537
538 // display current version notice
539 if ( $this->options['general']['update_notice'] === true ) {
540 // include notice js, only if needed
541 add_action( 'admin_print_scripts', [ $this, 'admin_inline_js' ], 999 );
542
543 // get activation date
544 $activation_date = get_option( 'post_views_counter_activation_date' );
545
546 if ( (int) $this->options['general']['update_delay_date'] === 0 ) {
547 if ( $activation_date + 2 * WEEK_IN_SECONDS > $current_time )
548 $this->options['general']['update_delay_date'] = $activation_date + 2 * WEEK_IN_SECONDS;
549 else
550 $this->options['general']['update_delay_date'] = $current_time;
551
552 update_option( 'post_views_counter_settings_general', $this->options['general'] );
553 }
554
555 if ( ( ! empty( $this->options['general']['update_delay_date'] ) ? (int) $this->options['general']['update_delay_date'] : $current_time ) <= $current_time )
556 $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' );
557 }
558 }
559
560 /**
561 * Add admin notices.
562 *
563 * @param string $html Notice HTML
564 * @param string $status Notice status
565 * @param bool $paragraph Whether to use paragraph
566 * @param bool $network
567 *
568 * @return void
569 */
570 public function add_notice( $html = '', $status = 'error', $paragraph = true, $network = false ) {
571 $this->notices[] = [
572 'html' => $html,
573 'status' => $status,
574 'paragraph' => $paragraph
575 ];
576
577 add_action( 'admin_notices', [ $this, 'display_notice' ] );
578
579 if ( $network )
580 add_action( 'network_admin_notices', [ $this, 'display_notice' ] );
581 }
582
583 /**
584 * Print admin notices.
585 *
586 * @return void
587 */
588 public function display_notice() {
589 $allowed_html = array_merge(
590 wp_kses_allowed_html( 'post' ),
591 [
592 'input' => [
593 'type' => true,
594 'name' => true,
595 'class' => true,
596 'value' => true
597 ],
598 'form' => [
599 'action' => true,
600 'method' => true
601 ]
602 ]
603 );
604
605 foreach ( $this->notices as $notice ) {
606 echo '
607 <div class="' . esc_attr( $notice['status'] ) . '">
608 ' . ( $notice['paragraph'] ? '<p>' : '' ) . '
609 ' . wp_kses( $notice['html'], $allowed_html ) . '
610 ' . ( $notice['paragraph'] ? '</p>' : '' ) . '
611 </div>';
612 }
613 }
614
615 /**
616 * Print admin scripts.
617 *
618 * @return void
619 */
620 public function admin_inline_js() {
621 if ( ! current_user_can( 'install_plugins' ) )
622 return;
623
624 // register and enqueue styles
625 wp_register_script( 'pvc-notices', false );
626 wp_enqueue_script( 'pvc-notices' );
627
628 // add styles
629 wp_add_inline_script( 'pvc-notices', "
630 ( function( $ ) {
631 // ready event
632 $( function() {
633 // save dismiss state
634 $( '.pvc-notice.is-dismissible' ).on( 'click', '.notice-dismiss, .pvc-dismissible-notice', function( e ) {
635 if ( e.currentTarget.target !== '_blank' )
636 e.preventDefault();
637
638 var notice_action = 'hide';
639
640 if ( e.currentTarget.classList.contains( 'pvc-delay-notice' ) )
641 notice_action = 'delay';
642
643 $.post( ajaxurl, {
644 action: 'pvc_dismiss_notice',
645 notice_action: notice_action,
646 url: '" . esc_url_raw( admin_url( 'admin-ajax.php' ) ) . "',
647 nonce: '" . esc_attr( wp_create_nonce( 'pvc_dismiss_notice' ) ) . "'
648 } );
649
650 $( e.delegateTarget ).slideUp( 'fast' );
651 } );
652 } );
653 } )( jQuery );
654 ", 'after' );
655 }
656
657 /**
658 * Dismiss notice.
659 *
660 * @return void
661 */
662 public function dismiss_notice() {
663 if ( ! current_user_can( 'install_plugins' ) )
664 return;
665
666 if ( isset( $_REQUEST['nonce'] ) && wp_verify_nonce( $_REQUEST['nonce'], 'pvc_dismiss_notice' ) ) {
667 $notice_action = empty( $_REQUEST['notice_action'] ) || $_REQUEST['notice_action'] === 'hide' ? 'hide' : sanitize_text_field( $_REQUEST['notice_action'] );
668
669 switch ( $notice_action ) {
670 // delay notice
671 case 'delay':
672 // set delay period to 1 week from now
673 $this->options['general'] = array_merge(
674 $this->options['general'],
675 [
676 'update_delay_date' => time() + 2 * WEEK_IN_SECONDS
677 ]
678 );
679 update_option( 'post_views_counter_settings_general', $this->options['general'] );
680 break;
681
682 // hide notice
683 default:
684 $this->options['general'] = array_merge(
685 $this->options['general'],
686 [
687 'update_notice' => false
688 ]
689 );
690 $this->options['general'] = array_merge(
691 $this->options['general'],
692 [
693 'update_delay_date' => 0
694 ]
695 );
696
697 update_option( 'post_views_counter_settings_general', $this->options['general'] );
698 }
699 }
700
701 exit;
702 }
703
704 /**
705 * Plugin activation.
706 *
707 * @global object $wpdb
708 *
709 * @param bool $network
710 *
711 * @return void
712 */
713 public function activation( $network ) {
714 // network activation?
715 if ( is_multisite() && $network ) {
716 global $wpdb;
717
718 // get all available sites
719 $blogs_ids = $wpdb->get_col( 'SELECT blog_id FROM ' . $wpdb->blogs );
720
721 foreach ( $blogs_ids as $blog_id ) {
722 // change to another site
723 switch_to_blog( (int) $blog_id );
724
725 // run current site activation process
726 $this->activate_site();
727
728 restore_current_blog();
729 }
730 } else
731 $this->activate_site();
732 }
733
734 /**
735 * Single site activation.
736 *
737 * @global object $wpdb
738 * @global string $charset_collate
739 *
740 * @return void
741 */
742 public function activate_site() {
743 global $wpdb, $charset_collate;
744
745 // required for dbdelta
746 require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
747
748 // create post views table
749 dbDelta( '
750 CREATE TABLE IF NOT EXISTS ' . $wpdb->prefix . 'post_views (
751 `id` bigint unsigned NOT NULL,
752 `type` tinyint(1) unsigned NOT NULL,
753 `period` varchar(8) NOT NULL,
754 `count` bigint unsigned NOT NULL,
755 PRIMARY KEY (type, period, id),
756 UNIQUE INDEX id_type_period_count (id, type, period, count) USING BTREE,
757 INDEX type_period_count (type, period, count) USING BTREE
758 ) ' . $charset_collate . ';'
759 );
760
761 // add default options
762 add_option( 'post_views_counter_settings_general', $this->defaults['general'], null, false );
763 add_option( 'post_views_counter_settings_display', $this->defaults['display'], null, false );
764 add_option( 'post_views_counter_settings_other', $this->defaults['other'], null, false );
765 add_option( 'post_views_counter_version', $this->defaults['version'], null, false );
766 }
767
768 /**
769 * Plugin deactivation.
770 *
771 * @global object $wpdb
772 *
773 * @param bool $network
774 *
775 * @return void
776 */
777 public function deactivation( $network ) {
778 // network deactivation?
779 if ( is_multisite() && $network ) {
780 global $wpdb;
781
782 // get all available sites
783 $blogs_ids = $wpdb->get_col( 'SELECT blog_id FROM ' . $wpdb->blogs );
784
785 foreach ( $blogs_ids as $blog_id ) {
786 // change to another site
787 switch_to_blog( (int) $blog_id );
788
789 // run current site deactivation process
790 $this->deactivate_site( true );
791
792 restore_current_blog();
793 }
794 } else
795 $this->deactivate_site();
796 }
797
798 /**
799 * Single site deactivation.
800 *
801 * @global object $wpdb
802 *
803 * @param bool $multi
804 *
805 * @return void
806 */
807 public function deactivate_site( $multi = false ) {
808 if ( $multi === true ) {
809 $options = get_option( 'post_views_counter_settings_other' );
810 $check = $options['deactivation_delete'];
811 } else
812 $check = $this->options['other']['deactivation_delete'];
813
814 // delete options if needed
815 if ( $check ) {
816 // delete options
817 delete_option( 'post_views_counter_settings_general' );
818 delete_option( 'post_views_counter_settings_display' );
819 delete_option( 'post_views_counter_settings_other' );
820 delete_option( 'post_views_counter_activation_date' );
821 delete_option( 'post_views_counter_version' );
822
823 // delete transients
824 delete_transient( 'post_views_counter_ip_cache' );
825
826 global $wpdb;
827
828 // delete table from database
829 $wpdb->query( 'DROP TABLE IF EXISTS ' . $wpdb->prefix . 'post_views' );
830 }
831
832 // remove schedule
833 wp_clear_scheduled_hook( 'pvc_reset_counts' );
834
835 remove_action( 'pvc_reset_counts', [ $this->cron, 'reset_counts' ] );
836 }
837
838 /**
839 * Initialize new network site.
840 *
841 * @param object $site
842 *
843 * @return void
844 */
845 public function init_new_network_site( $site ) {
846 if ( is_multisite() ) {
847 // change to another site
848 switch_to_blog( $site->blog_id );
849
850 // run current site activation process
851 $this->activate_site();
852
853 restore_current_blog();
854 }
855 }
856
857 /**
858 * Load text domain.
859 *
860 * @return void
861 */
862 public function load_textdomain() {
863 load_plugin_textdomain( 'post-views-counter', false, POST_VIEWS_COUNTER_REL_PATH . '/languages/' );
864 }
865
866 /**
867 * Load pluggable template functions.
868 *
869 * @return void
870 */
871 public function load_pluggable_functions() {
872 include_once( POST_VIEWS_COUNTER_PATH . 'includes/functions.php' );
873 }
874
875 /**
876 * Enqueue admin scripts and styles.
877 *
878 * @global string $post_type
879 * @global string $wp_version
880 *
881 * @param string $page
882 *
883 * @return void
884 */
885 public function admin_enqueue_scripts( $page ) {
886 global $post_type;
887 global $wp_version;
888
889 // register styles
890 wp_register_style( 'pvc-admin', POST_VIEWS_COUNTER_URL . '/css/admin-settings.css', [], $this->defaults['version'] );
891 wp_register_style( 'pvc-admin-post-style', POST_VIEWS_COUNTER_URL . '/css/admin-post.css', [], $this->defaults['version'] );
892
893 // register scripts
894 wp_register_script( 'pvc-admin-settings', POST_VIEWS_COUNTER_URL . '/js/admin-settings.js', [ 'jquery' ], $this->defaults['version'] );
895 wp_register_script( 'pvc-admin-post', POST_VIEWS_COUNTER_URL . '/js/admin-post.js', [ 'jquery' ], $this->defaults['version'] );
896 wp_register_script( 'pvc-admin-quick-edit', POST_VIEWS_COUNTER_URL . '/js/admin-quick-edit.js', [ 'jquery' ], $this->defaults['version'] );
897
898 // load on pvc settings page
899 if ( in_array( $page, [ 'toplevel_page_post-views-counter', 'settings_page_post-views-counter' ], true ) ) {
900 wp_enqueue_script( 'pvc-admin-settings' );
901
902 // prepare script data
903 $script_data = [
904 'resetToDefaults' => esc_html__( 'Are you sure you want to reset these settings to defaults?', 'post-views-counter' ),
905 'resetViews' => esc_html__( 'Are you sure you want to delete all existing data?', 'post-views-counter' ),
906 'importViews' => esc_html__( 'Are you sure you want to import views now?', 'post-views-counter' )
907 ];
908
909 wp_add_inline_script( 'pvc-admin-settings', 'var pvcArgsSettings = ' . wp_json_encode( $script_data ) . ";\n", 'before' );
910
911 wp_enqueue_style( 'pvc-admin' );
912 // load on single post page
913 } elseif ( $page === 'post.php' || $page === 'post-new.php' ) {
914 $post_types = Post_Views_Counter()->options['general']['post_types_count'];
915
916 if ( ! in_array( $post_type, (array) $post_types ) )
917 return;
918
919 wp_enqueue_style( 'pvc-admin-post-style' );
920
921 // only enqueue post script in classic editor (not block editor)
922 // block editor doesn't have the #post-views metabox that this script targets
923 if ( ! function_exists( 'use_block_editor_for_post' ) ) {
924 // WP < 5.0, always use classic editor
925 wp_enqueue_script( 'pvc-admin-post' );
926 } else {
927 // get post ID from request
928 $post_id = isset( $_GET['post'] ) ? absint( $_GET['post'] ) : ( isset( $_POST['post_ID'] ) ? absint( $_POST['post_ID'] ) : 0 );
929
930 // check if classic editor is being used
931 if ( $post_id && ! use_block_editor_for_post( $post_id ) ) {
932 wp_enqueue_script( 'pvc-admin-post' );
933 } elseif ( ! $post_id && $page === 'post-new.php' && ! use_block_editor_for_post_type( $post_type ) ) {
934 // new post - check if block editor is default for this post type
935 wp_enqueue_script( 'pvc-admin-post' );
936 }
937 }
938 // edit post
939 } elseif ( $page === 'edit.php' ) {
940 $post_types = Post_Views_Counter()->options['general']['post_types_count'];
941
942 if ( ! in_array( $post_type, (array) $post_types ) )
943 return;
944
945 wp_enqueue_style( 'pvc-admin-post-style' );
946
947 // woocommerce
948 if ( get_post_type() !== 'product' ) {
949 wp_enqueue_script( 'pvc-admin-quick-edit' );
950
951 // prepare script data
952 $script_data = [
953 'nonce' => wp_create_nonce( 'pvc_save_bulk_post_views' ),
954 'wpVersion59' => version_compare( $wp_version, '5.9', '>=' )
955 ];
956
957 wp_add_inline_script( 'pvc-admin-quick-edit', 'var pvcArgsQuickEdit = ' . wp_json_encode( $script_data ) . ";\n", 'before' );
958 }
959 // widgets
960 } elseif ( $page === 'widgets.php' )
961 wp_enqueue_script( 'pvc-admin-widgets', POST_VIEWS_COUNTER_URL . '/js/admin-widgets.js', [ 'jquery' ], $this->defaults['version'] );
962 // media
963 elseif ( $page === 'upload.php' )
964 wp_enqueue_style( 'pvc-admin-post-style' );
965 }
966
967 /**
968 * Load admin style inline, for menu badge only.
969 *
970 * @return void
971 */
972 public function admin_print_styles() {
973 echo '
974 <style>
975 .toplevel_page_post-views-counter .pvc-admin-menu-new,
976 .settings_page_post-views-counter .pvc-admin-menu-new {
977 font-size: 10px;
978 vertical-align: super;
979 color: #ffc107;
980 }
981 </style>
982 ';
983 }
984
985 /**
986 * Get the preferred admin menu position.
987 *
988 * @return string
989 */
990 public function get_menu_position() {
991 $position = isset( $this->options['display']['menu_position'] ) ? $this->options['display']['menu_position'] : '';
992
993 if ( in_array( $position, [ 'top', 'sub' ], true ) )
994 return $position;
995
996 if ( isset( $this->options['other']['menu_position'] ) && in_array( $this->options['other']['menu_position'], [ 'top', 'sub' ], true ) )
997 return $this->options['other']['menu_position'];
998
999 return 'top';
1000 }
1001
1002 /**
1003 * Add link to Settings page.
1004 *
1005 * @param array $links
1006 *
1007 * @return array
1008 */
1009 public function plugin_settings_link( $links ) {
1010 if ( ! current_user_can( 'manage_options' ) )
1011 return $links;
1012
1013 // submenu?
1014 if ( $this->get_menu_position() === 'sub' )
1015 $url = admin_url( 'options-general.php?page=post-views-counter' );
1016 // topmenu?
1017 else
1018 $url = admin_url( 'admin.php?page=post-views-counter' );
1019
1020 array_unshift( $links, sprintf( '<a href="%s">%s</a>', esc_url_raw( $url ), esc_html__( 'Settings', 'post-views-counter' ) ) );
1021
1022 return $links;
1023 }
1024 }
1025 }
1026
1027 /**
1028 * Initialize Post Views Counter.
1029 *
1030 * @return object
1031 */
1032 function Post_Views_Counter() {
1033 static $instance;
1034
1035 // first call to instance() initializes the plugin
1036 if ( $instance === null || ! ( $instance instanceof Post_Views_Counter ) )
1037 $instance = Post_Views_Counter::instance();
1038
1039 return $instance;
1040 }
1041
1042 Post_Views_Counter();
1043