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