<?php
/*
Plugin Name: Post Views Counter
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.
Version: 1.7.9
Author: dFactory
Author URI: https://dfactory.co/
Plugin URI: https://postviewscounter.com/
License: MIT License
License URI: https://opensource.org/licenses/MIT
Text Domain: post-views-counter
Domain Path: /languages

Post Views Counter
Copyright (C) 2014-2026, Digital Factory - info@digitalfactory.pl

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:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

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.
*/

// exit if accessed directly
if ( ! defined( 'ABSPATH' ) )
	exit;

if ( ! class_exists( 'Post_Views_Counter' ) ) {
	/**
	 * Post Views Counter final class.
	 *
	 * @class Post_Views_Counter
	 * @version	1.7.9
	 */
	final class Post_Views_Counter {

		private static $instance;
		private $notices;
		public $options;
		public $defaults = [
			'general'	=> [
				'post_types_count'		=> [ 'post' ],
				'taxonomies_count'		=> false,
				'users_count'			=> false,
				'other_count'			=> false,
				'technology_count'		=> false,
				'data_storage'			=> 'cookies',
				'amp_support'			=> false,
				'counter_mode'			=> 'php',
				'time_between_counts'	=> [
					'number'	=> 24,
					'type'		=> 'hours'
				],
				'count_time'			=> 'gmt',
				'reset_counts'			=> [
					'number'	=> 0,
					'type'		=> 'days'
				],
				'caching_compatibility'	=> false,
				'object_cache'			=> false,
				'flush_interval'		=> [
					'number'	=> 5,
					'type'		=> 'minutes'
				],
				'exclude'				=> [
					'groups' => [],
					'roles'	 => []
				],
				'exclude_groups'		=> [],
				'exclude_roles'			=> [],
				'exclude_ips'			=> [],
				'strict_counts'			=> false,
				'cron_run'				=> true,
				'cron_update'			=> true,
				'update_version'		=> 1,
				'update_notice'			=> true,
				'update_delay_date'		=> 0
			],
			'display'	=> [
				'label'					=> 'Post Views:',
				'display_period'		=> 'total',
				'taxonomies'			=> false,
				'taxonomies_display'	=> [],
				'user_display'			=> false,
				'post_types_display'	=> [ 'post' ],
				'page_types_display'	=> [ 'singular' ],
				'restrict_display'		=> [
					'groups' => [],
					'roles'	 => []
				],
				'restrict_display_groups'	=> [],
				'restrict_display_roles'	=> [],
				'position'				=> 'after',
				'post_views_column'		=> true,
				'restrict_edit_views'	=> false,
				'dynamic_loading'		=> false,
				'use_format'			=> true,
				'display_style'			=> [
					'icon'	 => true,
					'text'	 => true
				],
				'icon_class'			=> 'dashicons-chart-bar',
				'toolbar_statistics'	=> true,
				'menu_position'			=> 'top'
			],
			'other'		=> [
				'import_meta_key'		=> 'views',
				'deactivation_delete'	=> false,
				'license'				=> ''
			],
			'integrations' => [
				'integrations'			=> []
			],
			'version'	=> '1.7.9'
		];

		// instances
		public $counter;
		public $crawler;
		public $cron;
		public $dashboard;
		public $frontend;
		public $functions;
		public $settings;
		public $settings_api;
		public $import;

		/**
		 * Disable object cloning.
		 *
		 * @return void
		 */
		public function __clone() {}

		/**
		 * Disable unserializing of the class.
		 *
		 * @return void
		 */
		public function __wakeup() {}

		/**
		 * Main plugin instance, insures that only one instance of the class exists in memory at one time.
		 *
		 * @return object
		 */
		public static function instance() {
			if ( ! isset( self::$instance ) && ! ( self::$instance instanceof Post_Views_Counter ) ) {
				self::$instance = new Post_Views_Counter();

				// short init?
				if ( defined( 'SHORTINIT' ) && SHORTINIT ) {
					include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-counter.php' );
					include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-crawler-detect.php' );
					include_once( POST_VIEWS_COUNTER_PATH . 'includes/functions.php' );

					self::$instance->counter = new Post_Views_Counter_Counter();
					self::$instance->crawler = new Post_Views_Counter_Crawler_Detect();

					// we need to initialize crawler here since it is not called in SHORTINIT mode
					self::$instance->crawler->init();
				// regular setup
				} else {
					self::$instance->includes();

					// create settings API
					self::$instance->settings_api = new Post_Views_Counter_Settings_API(
						[
							'object'		=> self::$instance,
							'prefix'		=> 'pvc',
							'slug'			=> 'post-views-counter',
							'domain'		=> 'post-views-counter',
							'plugin'		=> 'Post Views Counter',
							'plugin_url'	=> POST_VIEWS_COUNTER_URL
						]
					);

					// initialize other classes
					self::$instance->functions = new Post_Views_Counter_Functions();

					new Post_Views_Counter_Update();

					self::$instance->settings = new Post_Views_Counter_Settings();
					self::$instance->import = new Post_Views_Counter_Import();

					new Post_Views_Counter_Admin();
					new Post_Views_Counter_Query();

					self::$instance->cron = new Post_Views_Counter_Cron();
					self::$instance->counter = new Post_Views_Counter_Counter();

					new Post_Views_Counter_Columns();
					new Post_Views_Counter_Columns_Modal();
					new Post_Views_Counter_Traffic_Signals();
					new Post_Views_Counter_Toolbar();

					self::$instance->crawler = new Post_Views_Counter_Crawler_Detect();
					self::$instance->frontend = new Post_Views_Counter_Frontend();
					self::$instance->dashboard = new Post_Views_Counter_Dashboard();

					new Post_Views_Counter_Widgets();
				}
			}

			return self::$instance;
		}

		/**
		 * Setup plugin constants.
		 *
		 * @return void
		 */
		private function define_constants() {
			// fix plugin_basename empty $wp_plugin_paths var
			if ( ! ( defined( 'SHORTINIT' ) && SHORTINIT ) ) {
				define( 'POST_VIEWS_COUNTER_URL', plugins_url( '', __FILE__ ) );
				define( 'POST_VIEWS_COUNTER_BASENAME', plugin_basename( __FILE__ ) );
				define( 'POST_VIEWS_COUNTER_REL_PATH', dirname( POST_VIEWS_COUNTER_BASENAME ) );
			}

			define( 'POST_VIEWS_COUNTER_PATH', plugin_dir_path( __FILE__ ) );
		}

		/**
		 * Include required files.
		 *
		 * @return void
		 */
		private function includes() {
			include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-functions.php' );
			include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-update.php' );
			include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-settings-api.php' );
			include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-settings.php' );
			include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-settings-general.php' );
			include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-settings-display.php' );
			include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-settings-reports.php' );
			include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-integrations.php' );
			include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-settings-integrations.php' );
			include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-settings-other.php' );
			include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-import.php' );
			include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-admin.php' );
			include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-columns.php' );
			include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-columns-modal.php' );
			include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-toolbar.php' );
			include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-query.php' );
			include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-cron.php' );
			include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-counter.php' );
			include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-crawler-detect.php' );
			include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-frontend.php' );
			include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-dashboard.php' );
			include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-widgets.php' );
			include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-traffic-signals.php' );
			include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-integration-gutenberg.php' );
		}

		/**
		 * Class constructor.
		 *
		 * @return void
		 */
		private function __construct() {
			// define plugin constants
			$this->define_constants();

			// short init?
			if ( defined( 'SHORTINIT' ) && SHORTINIT ) {
				$this->options = [
					'general'	 => array_merge( $this->defaults['general'], get_option( 'post_views_counter_settings_general', $this->defaults['general'] ) ),
					'display'	 => array_merge( $this->defaults['display'], get_option( 'post_views_counter_settings_display', $this->defaults['display'] ) ),
					'other'		=> array_merge( $this->defaults['other'], get_option( 'post_views_counter_settings_other', $this->defaults['other'] ) )
				];

				// migrate exclude to new fields
				if ( isset( $this->options['general']['exclude'] ) && is_array( $this->options['general']['exclude'] ) ) {
					if ( ! isset( $this->options['general']['exclude_groups'] ) )
						$this->options['general']['exclude_groups'] = $this->options['general']['exclude']['groups'] ?? [];

					if ( ! isset( $this->options['general']['exclude_roles'] ) )
						$this->options['general']['exclude_roles'] = $this->options['general']['exclude']['roles'] ?? [];
				}

				return;
			}

			// activation hooks
			register_activation_hook( __FILE__, [ $this, 'activation' ] );
			register_deactivation_hook( __FILE__, [ $this, 'deactivation' ] );

			// settings
			$this->options = [
				'general'		=> array_merge( $this->defaults['general'], get_option( 'post_views_counter_settings_general', $this->defaults['general'] ) ),
				'display'		=> array_merge( $this->defaults['display'], get_option( 'post_views_counter_settings_display', $this->defaults['display'] ) ),
				'other'			=> array_merge( $this->defaults['other'], get_option( 'post_views_counter_settings_other', $this->defaults['other'] ) ),
				'integrations'	=> array_merge( $this->defaults['integrations'], get_option( 'post_views_counter_settings_integrations', $this->defaults['integrations'] ) )
			];

			// 1.5.3+
			if ( ! isset( $this->options['general']['post_views_column'] ) )
				$this->options['general']['post_views_column'] = $this->options['display']['post_views_column'];

			if ( ! isset( $this->options['general']['restrict_edit_views'] ) )
				$this->options['general']['restrict_edit_views'] = $this->options['display']['restrict_edit_views'];

			// migrate exclude to new fields
			if ( isset( $this->options['general']['exclude'] ) && is_array( $this->options['general']['exclude'] ) ) {
				if ( ! isset( $this->options['general']['exclude_groups'] ) )
					$this->options['general']['exclude_groups'] = $this->options['general']['exclude']['groups'] ?? [];

				if ( ! isset( $this->options['general']['exclude_roles'] ) )
					$this->options['general']['exclude_roles'] = $this->options['general']['exclude']['roles'] ?? [];
			}

			// actions
			add_action( 'plugins_loaded', [ $this, 'extend_caching_plugins' ], -1 );
			add_action( 'admin_enqueue_scripts', [ $this, 'admin_enqueue_scripts' ] );
			add_action( 'admin_print_styles', [ $this, 'admin_print_styles' ] );
			add_action( 'wp_loaded', [ $this, 'load_pluggable_functions' ] );
			add_action( 'init', [ $this, 'load_textdomain' ] );
			add_action( 'init', [ $this, 'register_blocks' ] );
			add_action( 'admin_init', [ $this, 'update_notice' ] );
			add_action( 'wp_initialize_site', [ $this, 'init_new_network_site' ] );
			add_action( 'wp_ajax_pvc_dismiss_notice', [ $this, 'dismiss_notice' ] );

			// filters
			add_filter( 'plugin_action_links_' . POST_VIEWS_COUNTER_BASENAME, [ $this, 'plugin_settings_link' ] );
		}

		/**
		 * Extend list of caching plugins.
		 *
		 * @return void
		 */
		public function extend_caching_plugins() {
			// add new caching plugins
			add_filter( 'pvc_active_caching_plugins', [ $this->settings, 'extend_active_caching_plugins' ] );
			add_filter( 'pvc_is_plugin_active', [ $this->settings, 'extend_is_plugin_active' ], 10, 2 );
		}

		/**
		 * Register blocks.
		 *
		 * @return void
		 */
		public function register_blocks() {
			// actions
			add_action( 'enqueue_block_editor_assets', [ $this, 'block_editor_enqueue_scripts' ] );

			// filters
			add_filter( 'block_categories_all', [ $this, 'add_block_category' ] );

			add_filter( 'register_block_type_args', [ $this, 'update_block_args' ], 10, 2 );

			register_block_type( __DIR__ . '/blocks/most-viewed-posts' );
			register_block_type( __DIR__ . '/blocks/post-views' );

			// register Pro placeholder blocks when Pro is not active
			if ( ! class_exists( 'Post_Views_Counter_Pro' ) ) {
				$pro_placeholders = [
					'most-viewed-terms',
					'most-viewed-users',
					'term-views',
					'user-views',
					'site-views'
				];

				foreach ( $pro_placeholders as $block_slug ) {
					register_block_type( __DIR__ . '/blocks/pro-placeholder/' . $block_slug );
				}
			}
		}

		/**
		 * Enqueue block scripts.
		 *
		 * @return void
		 */
		public function block_editor_enqueue_scripts() {
			// register inline-only script handle
			wp_register_script( 'post-views-counter-block-editor-script', false, [], false, true );
			wp_enqueue_script( 'post-views-counter-block-editor-script' );

			// enqueue block editor styles
			wp_enqueue_style( 'pvc-block-editor', POST_VIEWS_COUNTER_URL . '/css/block-editor.css', [], $this->defaults['version'] );

			$block_image_sizes = [];

			// image sizes
			$image_sizes = array_merge( [ 'full' ], get_intermediate_image_sizes() );

			// sort image sizes by name, ascending
			sort( $image_sizes, SORT_STRING );

			foreach ( $image_sizes as $image_size ) {
				$block_image_sizes[] = [
					'label'	=> $image_size,
					'value'	=> $image_size
				];
			}

			$post_types = Post_Views_Counter()->functions->get_post_types();

			// prepare script data
			$script_data = [
				'postTypesKeys'	=> array_combine( array_keys( $post_types ), array_fill( 0, count( $post_types ), false ) ),
				'postTypes'		=> $post_types,
				'imageSizes'	=> $block_image_sizes,
				'isProActive'	=> class_exists( 'Post_Views_Counter_Pro' )
			];

			// force post as enabled
			$script_data['postTypesKeys']['post'] = true;

			// prepare script data
			$script_data['periods'] = [	[
				'label'		=> __( 'Total Views', 'post-views-counter' ),
				'value'		=> 'total'
			] ];

			$script_data = apply_filters( 'pvc_block_editor_data', $script_data );

			wp_add_inline_script( 'post-views-counter-block-editor-script', 'var pvcBlockEditorData = ' . wp_json_encode( $script_data ) . ";\n", 'before' );
		}

		/**
		 * Update block arguments.
		 *
		 * @param array $args
		 * @param string $block_type
		 *
		 * @return array
		 */
		function update_block_args( $args, $block_type ) {
			// most viewed posts block
			if ( $block_type === 'post-views-counter/most-viewed-posts' )
				$args['render_callback'] = [ $this, 'most_viewed_posts_render_callback' ];
			// post views block
			elseif ( $block_type === 'post-views-counter/post-views' )
				$args['render_callback'] = [ $this, 'post_views_render_callback' ];

			return $args;
		}

		/**
		 * Server side block renderer for most viewed posts.
		 *
		 * @param array $attributes
		 * @param string $content
		 *
		 * @return array
		 */
		function most_viewed_posts_render_callback( $attributes, $content ) {
			$post_types = [];

			foreach ( $attributes['postTypes'] as $post_type => $enabled ) {
				if ( $enabled === true || $enabled === 'true' )
					$post_types[] = $post_type;
			}

			// map block attributes
			$args = [
				'number_of_posts'		=> max( 1, (int) $attributes['numberOfPosts'] ),
				'post_type'				=> $post_types,
				'period'				=> $attributes['period'],
				'order'					=> $attributes['order'],
				'thumbnail_size'		=> $attributes['thumbnailSize'],
				'list_type'				=> $attributes['listType'],
				'show_post_views'		=> (bool) $attributes['displayPostViews'],
				'show_post_thumbnail'	=> (bool) $attributes['displayPostThumbnail'],
				'show_post_author'		=> (bool) $attributes['displayPostAuthor'],
				'show_post_excerpt'		=> (bool) $attributes['displayPostExcerpt'],
				'no_posts_message'		=> $attributes['noPostsMessage']
			];

			$title = trim( $attributes['title'] );

			$html = '<div ' . get_block_wrapper_attributes() . '>';

			if ( $title !== '' )
				$html .= '<h2 class="block-title">' . esc_html( $title ) . '</h2>';

			$html .= pvc_most_viewed_posts( $args, false );
			$html .= '</div>';

			return $html;
		}

		/**
		 * Server side block renderer for post views.
		 *
		 * @param array $attributes
		 * @param string $content
		 *
		 * @return array
		 */
		function post_views_render_callback( $attributes, $content ) {
			$html = '<div ' . get_block_wrapper_attributes() . '>';
			$html .= pvc_post_views( (int) $attributes['postID'], false, $attributes['period'] );
			$html .= '</div>';

			return $html;
		}

		/**
		 * Add new blocks category.
		 *
		 * @param array $categories
		 *
		 * @return array
		 */
		function add_block_category( $categories ) {
			$categories[] = [
				'slug'	=> 'post-views-counter',
				'title'	=> 'Post Views Counter'
			];

			return $categories;
		}

		/**
		 * Update notice.
		 *
		 * @return void
		 */
		public function update_notice() {
			if ( ! current_user_can( 'install_plugins' ) )
				return;

			$current_update = 2;

			// get current time
			$current_time = time();

			if ( $this->options['general']['update_version'] < $current_update ) {
				// check version, if update version is lower than plugin version, set update notice to true
				$this->options['general'] = array_merge(
					$this->options['general'],
					[
						'update_version'	=> $current_update,
						'update_notice'		=> true
					]
				);

				update_option( 'post_views_counter_settings_general', $this->options['general'] );

				// set activation date
				$activation_date = get_option( 'post_views_counter_activation_date' );

				if ( $activation_date === false )
					update_option( 'post_views_counter_activation_date', $current_time );
			}

			// display current version notice
			if ( $this->options['general']['update_notice'] === true ) {
				// include notice js, only if needed
				add_action( 'admin_print_scripts', [ $this, 'admin_inline_js' ], 999 );

				// get activation date
				$activation_date = get_option( 'post_views_counter_activation_date' );

				if ( (int) $this->options['general']['update_delay_date'] === 0 ) {
					if ( $activation_date + 2 * WEEK_IN_SECONDS > $current_time )
						$this->options['general']['update_delay_date'] = $activation_date + 2 * WEEK_IN_SECONDS;
					else
						$this->options['general']['update_delay_date'] = $current_time;

					update_option( 'post_views_counter_settings_general', $this->options['general'] );
				}

				if ( ( ! empty( $this->options['general']['update_delay_date'] ) ? (int) $this->options['general']['update_delay_date'] : $current_time ) <= $current_time )
					$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' );
			}
		}

		/**
		 * Add admin notices.
		 *
		 * @param string $html Notice HTML
		 * @param string $status Notice status
		 * @param bool $paragraph Whether to use paragraph
		 * @param bool $network
		 *
		 * @return void
		 */
		public function add_notice( $html = '', $status = 'error', $paragraph = true, $network = false ) {
			$this->notices[] = [
				'html' 		=> $html,
				'status'	=> $status,
				'paragraph'	=> $paragraph
			];

			add_action( 'admin_notices', [ $this, 'display_notice' ] );

			if ( $network )
				add_action( 'network_admin_notices', [ $this, 'display_notice' ] );
		}

		/**
		 * Print admin notices.
		 *
		 * @return void
		 */
		public function display_notice() {
			$allowed_html = array_merge(
				wp_kses_allowed_html( 'post' ),
				[
					'input'	=> [
						'type'	=> true,
						'name'	=> true,
						'class'	=> true,
						'value'	=> true
					],
					'form'	=> [
						'action'	=> true,
						'method'	=> true
					]
				]
			);

			foreach ( $this->notices as $notice ) {
				echo '
				<div class="' . esc_attr( $notice['status'] ) . '">
					' . ( $notice['paragraph'] ? '<p>' : '' ) . '
					' . wp_kses( $notice['html'], $allowed_html ) . '
					' . ( $notice['paragraph'] ? '</p>' : '' ) . '
				</div>';
			}
		}

		/**
		 * Print admin scripts.
		 *
		 * @return void
		 */
		public function admin_inline_js() {
			if ( ! current_user_can( 'install_plugins' ) )
				return;

			// register and enqueue styles
			wp_register_script( 'pvc-notices', false );
			wp_enqueue_script( 'pvc-notices' );

			// add styles
			wp_add_inline_script( 'pvc-notices', "
				( function( $ ) {
					// ready event
					$( function() {
						// save dismiss state
						$( '.pvc-notice.is-dismissible' ).on( 'click', '.notice-dismiss, .pvc-dismissible-notice', function( e ) {
							if ( e.currentTarget.target !== '_blank' )
								e.preventDefault();

							var notice_action = 'hide';

							if ( e.currentTarget.classList.contains( 'pvc-delay-notice' ) )
								notice_action = 'delay';

							$.post( ajaxurl, {
								action: 'pvc_dismiss_notice',
								notice_action: notice_action,
								url: '" . esc_url_raw( admin_url( 'admin-ajax.php' ) ) . "',
								nonce: '" . esc_attr( wp_create_nonce( 'pvc_dismiss_notice' ) ) . "'
							} );

							$( e.delegateTarget ).slideUp( 'fast' );
						} );
					} );
				} )( jQuery );
			", 'after' );
		}

		/**
		 * Dismiss notice.
		 *
		 * @return void
		 */
		public function dismiss_notice() {
			if ( ! current_user_can( 'install_plugins' ) )
				return;

			if ( isset( $_REQUEST['nonce'] ) && wp_verify_nonce( $_REQUEST['nonce'], 'pvc_dismiss_notice' ) ) {
				$notice_action = empty( $_REQUEST['notice_action'] ) || $_REQUEST['notice_action'] === 'hide' ? 'hide' : sanitize_text_field( $_REQUEST['notice_action'] );

				switch ( $notice_action ) {
					// delay notice
					case 'delay':
						// set delay period to 1 week from now
						$this->options['general'] = array_merge(
							$this->options['general'],
							[
								'update_delay_date'	=> time() + 2 * WEEK_IN_SECONDS
							]
						);
						update_option( 'post_views_counter_settings_general', $this->options['general'] );
						break;

					// hide notice
					default:
						$this->options['general'] = array_merge(
							$this->options['general'],
							[
								'update_notice' => false
							]
						);
						$this->options['general'] = array_merge(
							$this->options['general'],
							[
								'update_delay_date' => 0
							]
						);

						update_option( 'post_views_counter_settings_general', $this->options['general'] );
				}
			}

			exit;
		}

		/**
		 * Plugin activation.
		 *
		 * @global object $wpdb
		 *
		 * @param bool $network
		 *
		 * @return void
		 */
		public function activation( $network ) {
			// network activation?
			if ( is_multisite() && $network ) {
				global $wpdb;

				// get all available sites
				$blogs_ids = $wpdb->get_col( 'SELECT blog_id FROM ' . $wpdb->blogs );

				foreach ( $blogs_ids as $blog_id ) {
					// change to another site
					switch_to_blog( (int) $blog_id );

					// run current site activation process
					$this->activate_site();

					restore_current_blog();
				}
			} else
				$this->activate_site();
		}

		/**
		 * Single site activation.
		 *
		 * @global object $wpdb
		 * @global string $charset_collate
		 *
		 * @return void
		 */
		public function activate_site() {
			global $wpdb, $charset_collate;

			// required for dbdelta
			require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );

			// create post views table
			dbDelta( '
				CREATE TABLE IF NOT EXISTS ' . $wpdb->prefix . 'post_views (
					`id` bigint unsigned NOT NULL,
					`type` tinyint(1) unsigned NOT NULL,
					`period` varchar(8) NOT NULL,
					`count` bigint unsigned NOT NULL,
					PRIMARY KEY  (type, period, id),
					UNIQUE INDEX id_type_period_count (id, type, period, count) USING BTREE,
					INDEX type_period_count (type, period, count) USING BTREE
				) ' . $charset_collate . ';'
			);

			// add default options
			add_option( 'post_views_counter_settings_general', $this->defaults['general'], null, false );
			add_option( 'post_views_counter_settings_display', $this->defaults['display'], null, false );
			add_option( 'post_views_counter_settings_other', $this->defaults['other'], null, false );
			add_option( 'post_views_counter_version', $this->defaults['version'], null, false );
		}

		/**
		 * Plugin deactivation.
		 *
		 * @global object $wpdb
		 *
		 * @param bool $network
		 *
		 * @return void
		 */
		public function deactivation( $network ) {
			// network deactivation?
			if ( is_multisite() && $network ) {
				global $wpdb;

				// get all available sites
				$blogs_ids = $wpdb->get_col( 'SELECT blog_id FROM ' . $wpdb->blogs );

				foreach ( $blogs_ids as $blog_id ) {
					// change to another site
					switch_to_blog( (int) $blog_id );

					// run current site deactivation process
					$this->deactivate_site( true );

					restore_current_blog();
				}
			} else
				$this->deactivate_site();
		}

		/**
		 * Single site deactivation.
		 *
		 * @global object $wpdb
		 *
		 * @param bool $multi
		 *
		 * @return void
		 */
		public function deactivate_site( $multi = false ) {
			if ( $multi === true ) {
				$options = get_option( 'post_views_counter_settings_other' );
				$check = $options['deactivation_delete'];
			} else
				$check = $this->options['other']['deactivation_delete'];

			// delete options if needed
			if ( $check ) {
				// delete options
				delete_option( 'post_views_counter_settings_general' );
				delete_option( 'post_views_counter_settings_display' );
				delete_option( 'post_views_counter_settings_other' );
				delete_option( 'post_views_counter_activation_date' );
				delete_option( 'post_views_counter_version' );

				// delete transients
				delete_transient( 'post_views_counter_ip_cache' );

				global $wpdb;

				// delete table from database
				$wpdb->query( 'DROP TABLE IF EXISTS ' . $wpdb->prefix . 'post_views' );
			}

			// remove schedule
			wp_clear_scheduled_hook( 'pvc_reset_counts' );

			remove_action( 'pvc_reset_counts', [ $this->cron, 'reset_counts' ] );
		}

		/**
		 * Initialize new network site.
		 *
		 * @param object $site
		 *
		 * @return void
		 */
		public function init_new_network_site( $site ) {
			if ( is_multisite() ) {
				// change to another site
				switch_to_blog( $site->blog_id );

				// run current site activation process
				$this->activate_site();

				restore_current_blog();
			}
		}

		/**
		 * Load text domain.
		 *
		 * @return void
		 */
		public function load_textdomain() {
			load_plugin_textdomain( 'post-views-counter', false, POST_VIEWS_COUNTER_REL_PATH . '/languages/' );
		}

		/**
		 * Load pluggable template functions.
		 *
		 * @return void
		 */
		public function load_pluggable_functions() {
			include_once( POST_VIEWS_COUNTER_PATH . 'includes/functions.php' );
		}

		/**
		 * Enqueue admin scripts and styles.
		 *
		 * @global string $post_type
		 * @global string $wp_version
		 *
		 * @param string $page
		 *
		 * @return void
		 */
		public function admin_enqueue_scripts( $page ) {
			global $post_type;
			global $wp_version;

			// register styles
			wp_register_style( 'pvc-admin', POST_VIEWS_COUNTER_URL . '/css/admin-settings.css', [], $this->defaults['version'] );
			wp_register_style( 'pvc-admin-post-style', POST_VIEWS_COUNTER_URL . '/css/admin-post.css', [], $this->defaults['version'] );

			// register scripts
			wp_register_script( 'pvc-admin-settings', POST_VIEWS_COUNTER_URL . '/js/admin-settings.js', [ 'jquery' ], $this->defaults['version'] );
			wp_register_script( 'pvc-admin-post', POST_VIEWS_COUNTER_URL . '/js/admin-post.js', [ 'jquery' ], $this->defaults['version'] );
			wp_register_script( 'pvc-admin-quick-edit', POST_VIEWS_COUNTER_URL . '/js/admin-quick-edit.js', [ 'jquery' ], $this->defaults['version'] );

			// load on pvc settings page
			if ( in_array( $page, [ 'toplevel_page_post-views-counter', 'settings_page_post-views-counter' ], true ) ) {
				wp_enqueue_script( 'pvc-admin-settings' );

				// prepare script data
				$script_data = [
					'resetToDefaults'	=> esc_html__( 'Are you sure you want to reset these settings to defaults?', 'post-views-counter' ),
					'resetViews'		=> esc_html__( 'Are you sure you want to delete all existing data?', 'post-views-counter' ),
					'importViews'		=> esc_html__( 'Are you sure you want to import views now?', 'post-views-counter' )
				];

				wp_add_inline_script( 'pvc-admin-settings', 'var pvcArgsSettings = ' . wp_json_encode( $script_data ) . ";\n", 'before' );

				wp_enqueue_style( 'pvc-admin' );
			// load on single post page
			} elseif ( $page === 'post.php' || $page === 'post-new.php' ) {
				$post_types = Post_Views_Counter()->options['general']['post_types_count'];

				if ( ! in_array( $post_type, (array) $post_types ) )
					return;

				wp_enqueue_style( 'pvc-admin-post-style' );

			// only enqueue post script in classic editor (not block editor)
			// block editor doesn't have the #post-views metabox that this script targets
			if ( ! function_exists( 'use_block_editor_for_post' ) ) {
				// WP < 5.0, always use classic editor
				wp_enqueue_script( 'pvc-admin-post' );
			} else {
				// get post ID from request
				$post_id = isset( $_GET['post'] ) ? absint( $_GET['post'] ) : ( isset( $_POST['post_ID'] ) ? absint( $_POST['post_ID'] ) : 0 );
				
				// check if classic editor is being used
				if ( $post_id && ! use_block_editor_for_post( $post_id ) ) {
					wp_enqueue_script( 'pvc-admin-post' );
				} elseif ( ! $post_id && $page === 'post-new.php' && ! use_block_editor_for_post_type( $post_type ) ) {
					// new post - check if block editor is default for this post type
					wp_enqueue_script( 'pvc-admin-post' );
				}
			}
			// edit post
			} elseif ( $page === 'edit.php' ) {
				$post_types = Post_Views_Counter()->options['general']['post_types_count'];

				if ( ! in_array( $post_type, (array) $post_types ) )
					return;

				wp_enqueue_style( 'pvc-admin-post-style' );

				// woocommerce
				if ( get_post_type() !== 'product' ) {
					wp_enqueue_script( 'pvc-admin-quick-edit' );

					// prepare script data
					$script_data = [
						'nonce'			=> wp_create_nonce( 'pvc_save_bulk_post_views' ),
						'wpVersion59'	=> version_compare( $wp_version, '5.9', '>=' )
					];

					wp_add_inline_script( 'pvc-admin-quick-edit', 'var pvcArgsQuickEdit = ' . wp_json_encode( $script_data ) . ";\n", 'before' );
				}
			// widgets
			} elseif ( $page === 'widgets.php' )
				wp_enqueue_script( 'pvc-admin-widgets', POST_VIEWS_COUNTER_URL . '/js/admin-widgets.js', [ 'jquery' ], $this->defaults['version'] );
			// media
			elseif ( $page === 'upload.php' )
				wp_enqueue_style( 'pvc-admin-post-style' );
		}

		/**
		 * Load admin style inline, for menu badge only.
		 *
		 * @return void
		 */
		public function admin_print_styles() {
			echo '
			<style>
				.toplevel_page_post-views-counter .pvc-admin-menu-new,
				.settings_page_post-views-counter .pvc-admin-menu-new {
					font-size: 10px;
					vertical-align: super;
					color: #ffc107;
				}
			</style>
			';
		}

		/**
		 * Get the preferred admin menu position.
		 *
		 * @return string
		 */
		public function get_menu_position() {
			$position = isset( $this->options['display']['menu_position'] ) ? $this->options['display']['menu_position'] : '';

			if ( in_array( $position, [ 'top', 'sub' ], true ) )
				return $position;

			if ( isset( $this->options['other']['menu_position'] ) && in_array( $this->options['other']['menu_position'], [ 'top', 'sub' ], true ) )
				return $this->options['other']['menu_position'];

			return 'top';
		}

		/**
		 * Add link to Settings page.
		 *
		 * @param array $links
		 *
		 * @return array
		 */
		public function plugin_settings_link( $links ) {
			if ( ! current_user_can( 'manage_options' ) )
				return $links;

			// submenu?
			if ( $this->get_menu_position() === 'sub' )
				$url = admin_url( 'options-general.php?page=post-views-counter' );
			// topmenu?
			else
				$url = admin_url( 'admin.php?page=post-views-counter' );

			array_unshift( $links, sprintf( '<a href="%s">%s</a>', esc_url_raw( $url ), esc_html__( 'Settings', 'post-views-counter' ) ) );

			return $links;
		}
	}
}

/**
 * Initialize Post Views Counter.
 *
 * @return object
 */
function Post_Views_Counter() {
	static $instance;

	// first call to instance() initializes the plugin
	if ( $instance === null || ! ( $instance instanceof Post_Views_Counter ) )
		$instance = Post_Views_Counter::instance();

	return $instance;
}

Post_Views_Counter();
