PluginProbe ʕ •ᴥ•ʔ
Post Views Counter / 1.7.15
Post Views Counter v1.7.15
1.7.15 1.7.14 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 3 weeks ago blocks 3 weeks ago css 3 weeks ago includes 3 weeks ago js 3 weeks ago languages 3 weeks ago index.php 6 years ago post-views-counter.php 3 weeks ago readme.txt 3 weeks ago
post-views-counter.php
1358 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.15
6 Author: dFactory
7 Author URI: https://dfactory.co/
8 Plugin URI: https://postviewscounter.com/
9 License: MIT License
10 License URI: https://opensource.org/licenses/MIT
11 Text Domain: post-views-counter
12 Domain Path: /languages
13
14 Post Views Counter
15 Copyright (C) 2014-2026, Digital Factory - info@digitalfactory.pl
16
17 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
18
19 The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
20
21 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 */
23
24 // exit if accessed directly
25 if ( ! defined( 'ABSPATH' ) )
26 exit;
27
28 if ( ! class_exists( 'Post_Views_Counter' ) ) {
29 /**
30 * Post Views Counter final class.
31 *
32 * @class Post_Views_Counter
33 * @version 1.7.15
34 */
35 final class Post_Views_Counter {
36
37 private static $instance;
38 private $notices;
39 public $options;
40 public $defaults = [
41 'general' => [
42 'post_types_count' => [ 'post' ],
43 'taxonomies_count' => false,
44 'users_count' => false,
45 'other_count' => false,
46 'technology_count' => false,
47 'data_storage' => 'cookies',
48 'amp_support' => false,
49 'counter_mode' => 'php',
50 'time_between_counts' => [
51 'number' => 24,
52 'type' => 'hours'
53 ],
54 'count_time' => 'gmt',
55 'reset_counts' => [
56 'number' => 0,
57 'type' => 'days'
58 ],
59 'caching_compatibility' => false,
60 'object_cache' => false,
61 'flush_interval' => [
62 'number' => 5,
63 'type' => 'minutes'
64 ],
65 'exclude' => [
66 'groups' => [],
67 'roles' => []
68 ],
69 'exclude_groups' => [],
70 'exclude_roles' => [],
71 'exclude_ips' => [],
72 'strict_counts' => false,
73 'cron_run' => true,
74 'cron_update' => true,
75 'update_version' => 1,
76 'update_notice' => true,
77 'update_delay_date' => 0
78 ],
79 'display' => [
80 'label' => 'Post Views:',
81 'display_period' => 'total',
82 'taxonomies' => false,
83 'taxonomies_display' => [],
84 'user_display' => false,
85 'post_types_display' => [ 'post' ],
86 'page_types_display' => [ 'singular' ],
87 'restrict_display' => [
88 'groups' => [],
89 'roles' => []
90 ],
91 'restrict_display_groups' => [],
92 'restrict_display_roles' => [],
93 'position' => 'after',
94 'post_views_column' => true,
95 'restrict_edit_views' => false,
96 'dynamic_loading' => false,
97 'use_format' => true,
98 'display_style' => [
99 'icon' => true,
100 'text' => true
101 ],
102 'icon_class' => 'dashicons-chart-bar',
103 'toolbar_statistics' => true,
104 'menu_position' => 'top'
105 ],
106 'other' => [
107 'import_meta_key' => 'views',
108 'deactivation_delete' => false,
109 'license' => ''
110 ],
111 'integrations' => [
112 'integrations' => []
113 ],
114 'emails' => [
115 'enabled' => true,
116 'recipient' => '',
117 'test_recipient' => '',
118 'min_views_threshold' => 25,
119 'include_post_types' => [],
120 'max_top_items' => 5,
121 'include_period_trend' => true,
122 'include_traffic_signals' => true,
123 'send_empty_reports' => false,
124 'include_top_gainers_decliners' => false,
125 'include_author_summary' => false,
126 'include_source_summary' => false,
127 'enabled_frequencies' => [ 'weekly' ],
128 'email_subject_template' => '',
129 'email_body_template' => '',
130 'last_sent_at' => null,
131 'last_period_start' => null,
132 'last_period_end' => null,
133 'last_error' => null,
134 'latest_status' => [
135 'last_attempt_at' => null,
136 'last_success_at' => null,
137 'last_error' => null,
138 'recipient' => '',
139 'source' => '',
140 'is_test' => false,
141 'period_start' => null,
142 'period_end' => null
143 ],
144 'schedule_version' => 1
145 ],
146 'version' => '1.7.15'
147 ];
148
149 // instances
150 public $counter;
151 public $crawler;
152 public $cron;
153 public $dashboard;
154 public $emails_scheduler;
155 public $frontend;
156 public $functions;
157 public $settings;
158 public $settings_api;
159 public $import;
160
161 /**
162 * Disable object cloning.
163 *
164 * @return void
165 */
166 public function __clone() {}
167
168 /**
169 * Disable unserializing of the class.
170 *
171 * @return void
172 */
173 public function __wakeup() {}
174
175 /**
176 * Main plugin instance, insures that only one instance of the class exists in memory at one time.
177 *
178 * @return object
179 */
180 public static function instance() {
181 if ( ! isset( self::$instance ) && ! ( self::$instance instanceof Post_Views_Counter ) ) {
182 self::$instance = new Post_Views_Counter();
183
184 // short init?
185 if ( defined( 'SHORTINIT' ) && SHORTINIT ) {
186 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-counter.php' );
187 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-crawler-detect.php' );
188 include_once( POST_VIEWS_COUNTER_PATH . 'includes/functions.php' );
189
190 self::$instance->counter = new Post_Views_Counter_Counter();
191 self::$instance->crawler = new Post_Views_Counter_Crawler_Detect();
192
193 // we need to initialize crawler here since it is not called in SHORTINIT mode
194 self::$instance->crawler->init();
195 // regular setup
196 } else {
197 self::$instance->includes();
198
199 // create settings API
200 self::$instance->settings_api = new Post_Views_Counter_Settings_API(
201 [
202 'object' => self::$instance,
203 'prefix' => 'pvc',
204 'slug' => 'post-views-counter',
205 'domain' => 'post-views-counter',
206 'plugin' => 'Post Views Counter',
207 'plugin_url' => POST_VIEWS_COUNTER_URL
208 ]
209 );
210
211 // initialize other classes
212 self::$instance->functions = new Post_Views_Counter_Functions();
213
214 new Post_Views_Counter_Update();
215
216 self::$instance->settings = new Post_Views_Counter_Settings();
217 self::$instance->emails_scheduler = new Post_Views_Counter_Emails_Scheduler();
218 self::$instance->import = new Post_Views_Counter_Import();
219
220 new Post_Views_Counter_Admin();
221 new Post_Views_Counter_Query();
222
223 self::$instance->cron = new Post_Views_Counter_Cron();
224 self::$instance->counter = new Post_Views_Counter_Counter();
225
226 new Post_Views_Counter_Columns();
227 new Post_Views_Counter_Columns_Modal();
228 new Post_Views_Counter_Traffic_Signals();
229 new Post_Views_Counter_Toolbar();
230
231 self::$instance->crawler = new Post_Views_Counter_Crawler_Detect();
232 self::$instance->frontend = new Post_Views_Counter_Frontend();
233 self::$instance->dashboard = new Post_Views_Counter_Dashboard();
234
235 new Post_Views_Counter_Widgets();
236 }
237 }
238
239 return self::$instance;
240 }
241
242 /**
243 * Setup plugin constants.
244 *
245 * @return void
246 */
247 private function define_constants() {
248 // fix plugin_basename empty $wp_plugin_paths var
249 if ( ! ( defined( 'SHORTINIT' ) && SHORTINIT ) ) {
250 define( 'POST_VIEWS_COUNTER_URL', plugins_url( '', __FILE__ ) );
251 define( 'POST_VIEWS_COUNTER_BASENAME', plugin_basename( __FILE__ ) );
252 define( 'POST_VIEWS_COUNTER_REL_PATH', dirname( POST_VIEWS_COUNTER_BASENAME ) );
253 }
254
255 define( 'POST_VIEWS_COUNTER_PATH', plugin_dir_path( __FILE__ ) );
256 }
257
258 /**
259 * Include required files.
260 *
261 * @return void
262 */
263 private function includes() {
264 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-functions.php' );
265 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-update.php' );
266 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-settings-api.php' );
267 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-settings.php' );
268 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-settings-general.php' );
269 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-settings-display.php' );
270 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-settings-reports.php' );
271 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-integrations.php' );
272 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-settings-integrations.php' );
273 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-emails-template.php' );
274 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-settings-emails.php' );
275 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-emails-period.php' );
276 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-emails-query.php' );
277 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-emails.php' );
278 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-emails-mailer.php' );
279 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-emails-scheduler.php' );
280 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-settings-other.php' );
281 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-import.php' );
282 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-admin.php' );
283 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-columns.php' );
284 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-columns-modal.php' );
285 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-toolbar.php' );
286 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-query.php' );
287 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-cron.php' );
288 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-counter.php' );
289 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-crawler-detect.php' );
290 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-frontend.php' );
291 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-dashboard.php' );
292 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-widgets.php' );
293 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-traffic-signals.php' );
294 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-integration-gutenberg.php' );
295 }
296
297 /**
298 * Initialize email template defaults from the template service.
299 *
300 * @return void
301 */
302 private function initialize_email_template_defaults() {
303 if ( ! class_exists( 'Post_Views_Counter_Emails_Template' ) )
304 include_once( POST_VIEWS_COUNTER_PATH . 'includes/class-emails-template.php' );
305
306 $this->defaults['emails']['email_subject_template'] = Post_Views_Counter_Emails_Template::get_default_subject_template();
307 $this->defaults['emails']['email_body_template'] = Post_Views_Counter_Emails_Template::get_default_body_template();
308 }
309
310 /**
311 * Normalize stored email settings with current defaults.
312 *
313 * @param mixed $stored_emails
314 * @param bool $persist
315 * @return array
316 */
317 private function normalize_email_settings( $stored_emails, $persist = false ) {
318 $defaults = $this->get_default_emails_settings();
319 $stored_emails = is_array( $stored_emails ) ? $stored_emails : [];
320 $normalized = array_merge( $defaults, $stored_emails );
321 $normalized['latest_status'] = array_merge(
322 isset( $defaults['latest_status'] ) && is_array( $defaults['latest_status'] ) ? $defaults['latest_status'] : [],
323 isset( $stored_emails['latest_status'] ) && is_array( $stored_emails['latest_status'] ) ? $stored_emails['latest_status'] : []
324 );
325 $did_update_templates = false;
326
327 foreach ( [ 'email_subject_template', 'email_body_template' ] as $template_key ) {
328 if ( ! isset( $stored_emails[$template_key] ) || $stored_emails[$template_key] === '' ) {
329 $normalized[$template_key] = $defaults[$template_key];
330 $did_update_templates = true;
331 }
332 }
333
334 if ( $persist && $did_update_templates )
335 update_option( 'post_views_counter_settings_emails', $normalized );
336
337 return $normalized;
338 }
339
340 /**
341 * Class constructor.
342 *
343 * @return void
344 */
345 private function __construct() {
346 // define plugin constants
347 $this->define_constants();
348 $this->initialize_email_template_defaults();
349
350 $stored_emails = get_option( 'post_views_counter_settings_emails', false );
351 $emails = $this->normalize_email_settings( $stored_emails, $stored_emails !== false );
352
353 // short init?
354 if ( defined( 'SHORTINIT' ) && SHORTINIT ) {
355 $this->options = [
356 'general' => array_merge( $this->defaults['general'], get_option( 'post_views_counter_settings_general', $this->defaults['general'] ) ),
357 'display' => array_merge( $this->defaults['display'], get_option( 'post_views_counter_settings_display', $this->defaults['display'] ) ),
358 'other' => array_merge( $this->defaults['other'], get_option( 'post_views_counter_settings_other', $this->defaults['other'] ) ),
359 'emails' => $emails
360 ];
361
362 $this->options['general']['time_between_counts'] = $this->normalize_time_between_counts( isset( $this->options['general']['time_between_counts'] ) ? $this->options['general']['time_between_counts'] : null );
363
364 // migrate exclude to new fields
365 if ( isset( $this->options['general']['exclude'] ) && is_array( $this->options['general']['exclude'] ) ) {
366 if ( ! isset( $this->options['general']['exclude_groups'] ) )
367 $this->options['general']['exclude_groups'] = $this->options['general']['exclude']['groups'] ?? [];
368
369 if ( ! isset( $this->options['general']['exclude_roles'] ) )
370 $this->options['general']['exclude_roles'] = $this->options['general']['exclude']['roles'] ?? [];
371 }
372
373 return;
374 }
375
376 // activation hooks
377 register_activation_hook( __FILE__, [ $this, 'activation' ] );
378 register_deactivation_hook( __FILE__, [ $this, 'deactivation' ] );
379
380 // settings
381 $this->options = [
382 'general' => array_merge( $this->defaults['general'], get_option( 'post_views_counter_settings_general', $this->defaults['general'] ) ),
383 'display' => array_merge( $this->defaults['display'], get_option( 'post_views_counter_settings_display', $this->defaults['display'] ) ),
384 'other' => array_merge( $this->defaults['other'], get_option( 'post_views_counter_settings_other', $this->defaults['other'] ) ),
385 'integrations' => array_merge( $this->defaults['integrations'], get_option( 'post_views_counter_settings_integrations', $this->defaults['integrations'] ) ),
386 'emails' => $emails
387 ];
388
389 $this->options['general']['time_between_counts'] = $this->normalize_time_between_counts( isset( $this->options['general']['time_between_counts'] ) ? $this->options['general']['time_between_counts'] : null );
390
391 // 1.5.3+
392 if ( ! isset( $this->options['general']['post_views_column'] ) )
393 $this->options['general']['post_views_column'] = $this->options['display']['post_views_column'];
394
395 if ( ! isset( $this->options['general']['restrict_edit_views'] ) )
396 $this->options['general']['restrict_edit_views'] = $this->options['display']['restrict_edit_views'];
397
398 // migrate exclude to new fields
399 if ( isset( $this->options['general']['exclude'] ) && is_array( $this->options['general']['exclude'] ) ) {
400 if ( ! isset( $this->options['general']['exclude_groups'] ) )
401 $this->options['general']['exclude_groups'] = $this->options['general']['exclude']['groups'] ?? [];
402
403 if ( ! isset( $this->options['general']['exclude_roles'] ) )
404 $this->options['general']['exclude_roles'] = $this->options['general']['exclude']['roles'] ?? [];
405 }
406
407 // actions
408 add_action( 'plugins_loaded', [ $this, 'extend_caching_plugins' ], -1 );
409 add_action( 'admin_enqueue_scripts', [ $this, 'admin_enqueue_scripts' ] );
410 add_action( 'admin_print_styles', [ $this, 'admin_print_styles' ] );
411 add_action( 'wp_loaded', [ $this, 'load_pluggable_functions' ] );
412 add_action( 'init', [ $this, 'load_textdomain' ] );
413 add_action( 'init', [ $this, 'register_blocks' ] );
414 add_action( 'admin_init', [ $this, 'update_notice' ] );
415 add_action( 'wp_initialize_site', [ $this, 'init_new_network_site' ] );
416 add_action( 'wp_ajax_pvc_dismiss_notice', [ $this, 'dismiss_notice' ] );
417
418 // filters
419 add_filter( 'plugin_action_links_' . POST_VIEWS_COUNTER_BASENAME, [ $this, 'plugin_settings_link' ] );
420 }
421
422 /**
423 * Extend list of caching plugins.
424 *
425 * @return void
426 */
427 public function extend_caching_plugins() {
428 // add new caching plugins
429 add_filter( 'pvc_active_caching_plugins', [ $this->settings, 'extend_active_caching_plugins' ] );
430 add_filter( 'pvc_is_plugin_active', [ $this->settings, 'extend_is_plugin_active' ], 10, 2 );
431 }
432
433 /**
434 * Register blocks.
435 *
436 * @return void
437 */
438 public function register_blocks() {
439 // actions
440 add_action( 'enqueue_block_editor_assets', [ $this, 'block_editor_enqueue_scripts' ] );
441
442 // filters
443 add_filter( 'block_categories_all', [ $this, 'add_block_category' ] );
444
445 add_filter( 'register_block_type_args', [ $this, 'update_block_args' ], 10, 2 );
446
447 register_block_type( __DIR__ . '/blocks/most-viewed-posts' );
448 register_block_type( __DIR__ . '/blocks/post-views' );
449
450 // register placeholder blocks when the related blocks are unavailable
451 if ( ! class_exists( 'Post_Views_Counter_Pro' ) ) {
452 /*
453 * Shared editor canvas stylesheet for every placeholder block.
454 *
455 * Registering a single handle here and referencing it as "editorStyle"
456 * from each placeholder block.json makes WordPress load the stylesheet
457 * into the iframed editor canvas exactly once, instead of relying on
458 * "enqueue_block_editor_assets" which only reaches the parent document.
459 */
460 wp_register_style( 'pvc-block-editor-canvas', POST_VIEWS_COUNTER_URL . '/css/block-editor-canvas.css', [], $this->defaults['version'] );
461
462 $pro_placeholders = [
463 'most-viewed-terms',
464 'most-viewed-users',
465 'term-views',
466 'user-views',
467 'site-views'
468 ];
469
470 foreach ( $pro_placeholders as $block_slug ) {
471 register_block_type( __DIR__ . '/blocks/pro-placeholder/' . $block_slug );
472 }
473 }
474 }
475
476 /**
477 * Enqueue block scripts.
478 *
479 * @return void
480 */
481 public function block_editor_enqueue_scripts() {
482 // register inline-only script handle
483 wp_register_script( 'post-views-counter-block-editor-script', false, [], false, true );
484 wp_enqueue_script( 'post-views-counter-block-editor-script' );
485
486 // enqueue block editor styles
487 wp_enqueue_style( 'pvc-block-editor', POST_VIEWS_COUNTER_URL . '/css/block-editor.css', [], $this->defaults['version'] );
488
489 $block_image_sizes = [];
490
491 // image sizes
492 $image_sizes = array_merge( [ 'full' ], get_intermediate_image_sizes() );
493
494 // sort image sizes by name, ascending
495 sort( $image_sizes, SORT_STRING );
496
497 foreach ( $image_sizes as $image_size ) {
498 $block_image_sizes[] = [
499 'label' => $image_size,
500 'value' => $image_size
501 ];
502 }
503
504 $post_types = Post_Views_Counter()->functions->get_post_types();
505
506 // prepare script data
507 $script_data = [
508 'postTypesKeys' => array_combine( array_keys( $post_types ), array_fill( 0, count( $post_types ), false ) ),
509 'postTypes' => $post_types,
510 'imageSizes' => $block_image_sizes,
511 'upgradeUrl' => $this->get_postviewscounter_url( '/upgrade/', 'link', 'upgrade-to-pro', 'block-editor-pro-placeholder-link', 'free' ),
512 'isProActive' => class_exists( 'Post_Views_Counter_Pro' )
513 ];
514
515 // force post as enabled
516 $script_data['postTypesKeys']['post'] = true;
517
518 // prepare script data
519 $script_data['periods'] = [ [
520 'label' => __( 'Total Views', 'post-views-counter' ),
521 'value' => 'total'
522 ] ];
523
524 $script_data = apply_filters( 'pvc_block_editor_data', $script_data );
525
526 wp_add_inline_script( 'post-views-counter-block-editor-script', 'var pvcBlockEditorData = ' . wp_json_encode( $script_data ) . ";\n", 'before' );
527 }
528
529 /**
530 * Add deterministic UTM parameters to postviewscounter.com URLs.
531 *
532 * @param string $url
533 * @param string $medium
534 * @param string $campaign
535 * @param string $content
536 * @param string $context
537 *
538 * @return string
539 */
540 public function add_postviewscounter_utm_args( $url, $medium = 'link', $campaign = 'general', $content = 'general', $context = 'free' ) {
541 $url = esc_url_raw( $url );
542
543 if ( $url === '' )
544 return '';
545
546 $host = wp_parse_url( $url, PHP_URL_HOST );
547
548 if ( ! is_string( $host ) )
549 return $url;
550
551 $host = strtolower( rtrim( $host, '.' ) );
552
553 if ( ! preg_match( '/(^|\.)postviewscounter\.com$/', $host ) )
554 return $url;
555
556 $allowed_mediums = [ 'button', 'link', 'email' ];
557 $allowed_contexts = [ 'free', 'pro-expired', 'pro-active' ];
558
559 $medium = sanitize_key( $medium );
560 $campaign = sanitize_key( $campaign );
561 $content = sanitize_key( $content );
562 $context = sanitize_key( $context );
563
564 if ( ! in_array( $medium, $allowed_mediums, true ) )
565 $medium = 'link';
566
567 if ( $campaign === '' )
568 $campaign = 'general';
569
570 if ( $content === '' )
571 $content = 'general';
572
573 if ( ! in_array( $context, $allowed_contexts, true ) )
574 $context = 'free';
575
576 if ( $context === 'free' ) {
577 $source = 'post-views-counter-lite';
578 $version = ! empty( $this->defaults['version'] ) ? (string) $this->defaults['version'] : (string) get_option( 'post_views_counter_version', '' );
579 } else {
580 $source = 'post-views-counter-pro';
581 $version = (string) get_option( 'post_views_counter_pro_version', '' );
582 }
583
584 $query_args = [
585 'utm_source' => $source,
586 'utm_medium' => $medium,
587 'utm_campaign' => $campaign,
588 'utm_content' => $content,
589 'utm_context' => $context
590 ];
591
592 if ( $version !== '' )
593 $query_args['utm_version'] = sanitize_text_field( $version );
594
595 return add_query_arg( $query_args, $url );
596 }
597
598 /**
599 * Get a postviewscounter.com URL with deterministic UTM parameters.
600 *
601 * @param string $path
602 * @param string $medium
603 * @param string $campaign
604 * @param string $content
605 * @param string $context
606 *
607 * @return string
608 */
609 public function get_postviewscounter_url( $path = '/', $medium = 'link', $campaign = 'general', $content = 'general', $context = 'free' ) {
610 if ( ! is_string( $path ) || $path === '' )
611 $path = '/';
612 elseif ( preg_match( '#^https?://#i', $path ) ) {
613 $parsed_path = wp_parse_url( $path, PHP_URL_PATH );
614 $parsed_query = wp_parse_url( $path, PHP_URL_QUERY );
615 $parsed_fragment = wp_parse_url( $path, PHP_URL_FRAGMENT );
616
617 $path = is_string( $parsed_path ) && $parsed_path !== '' ? $parsed_path : '/';
618
619 if ( is_string( $parsed_query ) && $parsed_query !== '' )
620 $path .= '?' . $parsed_query;
621
622 if ( is_string( $parsed_fragment ) && $parsed_fragment !== '' )
623 $path .= '#' . $parsed_fragment;
624 }
625
626 if ( $path === '/' )
627 $url = 'https://postviewscounter.com/';
628 else
629 $url = 'https://postviewscounter.com/' . ltrim( $path, '/' );
630
631 return $this->add_postviewscounter_utm_args( $url, $medium, $campaign, $content, $context );
632 }
633
634 /**
635 * Update block arguments.
636 *
637 * @param array $args
638 * @param string $block_type
639 *
640 * @return array
641 */
642 public function update_block_args( $args, $block_type ) {
643 // most viewed posts block
644 if ( $block_type === 'post-views-counter/most-viewed-posts' )
645 $args['render_callback'] = [ $this, 'most_viewed_posts_render_callback' ];
646 // post views block
647 elseif ( $block_type === 'post-views-counter/post-views' )
648 $args['render_callback'] = [ $this, 'post_views_render_callback' ];
649
650 return $args;
651 }
652
653 /**
654 * Server side block renderer for most viewed posts.
655 *
656 * @param array $attributes
657 * @param string $content
658 *
659 * @return array
660 */
661 public function most_viewed_posts_render_callback( $attributes, $content ) {
662 $post_types = [];
663
664 foreach ( $attributes['postTypes'] as $post_type => $enabled ) {
665 if ( $enabled === true || $enabled === 'true' )
666 $post_types[] = $post_type;
667 }
668
669 // map block attributes
670 $args = [
671 'number_of_posts' => max( 1, (int) $attributes['numberOfPosts'] ),
672 'post_type' => $post_types,
673 'period' => $attributes['period'],
674 'order' => $attributes['order'],
675 'thumbnail_size' => $attributes['thumbnailSize'],
676 'list_type' => $attributes['listType'],
677 'show_post_views' => (bool) $attributes['displayPostViews'],
678 'show_post_thumbnail' => (bool) $attributes['displayPostThumbnail'],
679 'show_post_author' => (bool) $attributes['displayPostAuthor'],
680 'show_post_excerpt' => (bool) $attributes['displayPostExcerpt'],
681 'no_posts_message' => $attributes['noPostsMessage']
682 ];
683
684 $title = trim( $attributes['title'] );
685
686 $html = '<div ' . get_block_wrapper_attributes() . '>';
687
688 if ( $title !== '' )
689 $html .= '<h2 class="block-title">' . esc_html( $title ) . '</h2>';
690
691 $html .= pvc_most_viewed_posts( $args, false );
692 $html .= '</div>';
693
694 return $html;
695 }
696
697 /**
698 * Server side block renderer for post views.
699 *
700 * @param array $attributes
701 * @param string $content
702 *
703 * @return array
704 */
705 public function post_views_render_callback( $attributes, $content ) {
706 $html = '<div ' . get_block_wrapper_attributes() . '>';
707 $html .= pvc_post_views( (int) $attributes['postID'], false, $attributes['period'] );
708 $html .= '</div>';
709
710 return $html;
711 }
712
713 /**
714 * Add new blocks category.
715 *
716 * @param array $categories
717 *
718 * @return array
719 */
720 public function add_block_category( $categories ) {
721 $categories[] = [
722 'slug' => 'post-views-counter',
723 'title' => 'Post Views Counter'
724 ];
725
726 return $categories;
727 }
728
729 /**
730 * Update notice.
731 *
732 * @return void
733 */
734 public function update_notice() {
735 if ( ! current_user_can( 'install_plugins' ) )
736 return;
737
738 $current_update = 2;
739
740 // get current time
741 $current_time = time();
742
743 if ( $this->options['general']['update_version'] < $current_update ) {
744 // check version, if update version is lower than plugin version, set update notice to true
745 $this->options['general'] = array_merge(
746 $this->options['general'],
747 [
748 'update_version' => $current_update,
749 'update_notice' => true
750 ]
751 );
752
753 update_option( 'post_views_counter_settings_general', $this->options['general'] );
754
755 // set activation date
756 $activation_date = get_option( 'post_views_counter_activation_date' );
757
758 if ( $activation_date === false )
759 update_option( 'post_views_counter_activation_date', $current_time );
760 }
761
762 // display current version notice
763 if ( $this->options['general']['update_notice'] === true ) {
764 // include notice js, only if needed
765 add_action( 'admin_print_scripts', [ $this, 'admin_inline_js' ], 999 );
766
767 // get activation date
768 $activation_date = get_option( 'post_views_counter_activation_date' );
769
770 if ( (int) $this->options['general']['update_delay_date'] === 0 ) {
771 if ( $activation_date + 2 * WEEK_IN_SECONDS > $current_time )
772 $this->options['general']['update_delay_date'] = $activation_date + 2 * WEEK_IN_SECONDS;
773 else
774 $this->options['general']['update_delay_date'] = $current_time;
775
776 update_option( 'post_views_counter_settings_general', $this->options['general'] );
777 }
778
779 if ( ( ! empty( $this->options['general']['update_delay_date'] ) ? (int) $this->options['general']['update_delay_date'] : $current_time ) <= $current_time ) {
780 $review_notice_brand_url = esc_url( $this->get_postviewscounter_url( '/', 'link', 'review-notice', 'review-notice-brand-link', 'free' ) );
781
782 $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="' . $review_notice_brand_url . '" 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' );
783 }
784 }
785 }
786
787 /**
788 * Add admin notices.
789 *
790 * @param string $html Notice HTML
791 * @param string $status Notice status
792 * @param bool $paragraph Whether to use paragraph
793 * @param bool $network
794 *
795 * @return void
796 */
797 public function add_notice( $html = '', $status = 'error', $paragraph = true, $network = false ) {
798 $this->notices[] = [
799 'html' => $html,
800 'status' => $status,
801 'paragraph' => $paragraph
802 ];
803
804 add_action( 'admin_notices', [ $this, 'display_notice' ] );
805
806 if ( $network )
807 add_action( 'network_admin_notices', [ $this, 'display_notice' ] );
808 }
809
810 /**
811 * Print admin notices.
812 *
813 * @return void
814 */
815 public function display_notice() {
816 $allowed_html = array_merge(
817 wp_kses_allowed_html( 'post' ),
818 [
819 'input' => [
820 'type' => true,
821 'name' => true,
822 'class' => true,
823 'value' => true
824 ],
825 'form' => [
826 'action' => true,
827 'method' => true
828 ]
829 ]
830 );
831
832 foreach ( $this->notices as $notice ) {
833 echo '
834 <div class="' . esc_attr( $notice['status'] ) . '">
835 ' . ( $notice['paragraph'] ? '<p>' : '' ) . '
836 ' . wp_kses( $notice['html'], $allowed_html ) . '
837 ' . ( $notice['paragraph'] ? '</p>' : '' ) . '
838 </div>';
839 }
840 }
841
842 /**
843 * Print admin scripts.
844 *
845 * @return void
846 */
847 public function admin_inline_js() {
848 if ( ! current_user_can( 'install_plugins' ) )
849 return;
850
851 // register and enqueue styles
852 wp_register_script( 'pvc-notices', false );
853 wp_enqueue_script( 'pvc-notices' );
854
855 // add styles
856 wp_add_inline_script( 'pvc-notices', "
857 ( function( $ ) {
858 // ready event
859 $( function() {
860 // save dismiss state
861 $( '.pvc-notice.is-dismissible' ).on( 'click', '.notice-dismiss, .pvc-dismissible-notice', function( e ) {
862 if ( e.currentTarget.target !== '_blank' )
863 e.preventDefault();
864
865 var notice_action = 'hide';
866
867 if ( e.currentTarget.classList.contains( 'pvc-delay-notice' ) )
868 notice_action = 'delay';
869
870 $.post( ajaxurl, {
871 action: 'pvc_dismiss_notice',
872 notice_action: notice_action,
873 url: '" . esc_url_raw( admin_url( 'admin-ajax.php' ) ) . "',
874 nonce: '" . esc_attr( wp_create_nonce( 'pvc_dismiss_notice' ) ) . "'
875 } );
876
877 $( e.delegateTarget ).slideUp( 'fast' );
878 } );
879 } );
880 } )( jQuery );
881 ", 'after' );
882 }
883
884 /**
885 * Dismiss notice.
886 *
887 * @return void
888 */
889 public function dismiss_notice() {
890 if ( ! current_user_can( 'install_plugins' ) )
891 return;
892
893 if ( isset( $_REQUEST['nonce'] ) && wp_verify_nonce( $_REQUEST['nonce'], 'pvc_dismiss_notice' ) ) {
894 $notice_action = empty( $_REQUEST['notice_action'] ) || $_REQUEST['notice_action'] === 'hide' ? 'hide' : sanitize_text_field( $_REQUEST['notice_action'] );
895
896 switch ( $notice_action ) {
897 // delay notice
898 case 'delay':
899 // set delay period to 1 week from now
900 $this->options['general'] = array_merge(
901 $this->options['general'],
902 [
903 'update_delay_date' => time() + 2 * WEEK_IN_SECONDS
904 ]
905 );
906 update_option( 'post_views_counter_settings_general', $this->options['general'] );
907 break;
908
909 // hide notice
910 default:
911 $this->options['general'] = array_merge(
912 $this->options['general'],
913 [
914 'update_notice' => false
915 ]
916 );
917 $this->options['general'] = array_merge(
918 $this->options['general'],
919 [
920 'update_delay_date' => 0
921 ]
922 );
923
924 update_option( 'post_views_counter_settings_general', $this->options['general'] );
925 }
926 }
927
928 exit;
929 }
930
931 /**
932 * Plugin activation.
933 *
934 * @global object $wpdb
935 *
936 * @param bool $network
937 *
938 * @return void
939 */
940 public function activation( $network ) {
941 // network activation?
942 if ( is_multisite() && $network ) {
943 global $wpdb;
944
945 // get all available sites
946 $blogs_ids = $wpdb->get_col( 'SELECT blog_id FROM ' . $wpdb->blogs );
947
948 foreach ( $blogs_ids as $blog_id ) {
949 // change to another site
950 switch_to_blog( (int) $blog_id );
951
952 // run current site activation process
953 $this->activate_site();
954
955 restore_current_blog();
956 }
957 } else
958 $this->activate_site();
959 }
960
961 /**
962 * Single site activation.
963 *
964 * @global object $wpdb
965 * @global string $charset_collate
966 *
967 * @return void
968 */
969 public function activate_site() {
970 global $wpdb, $charset_collate;
971
972 // required for dbdelta
973 require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
974
975 // create post views table
976 dbDelta( '
977 CREATE TABLE IF NOT EXISTS ' . $wpdb->prefix . 'post_views (
978 `id` bigint unsigned NOT NULL,
979 `type` tinyint(1) unsigned NOT NULL,
980 `period` varchar(8) NOT NULL,
981 `count` bigint unsigned NOT NULL,
982 PRIMARY KEY (type, period, id),
983 UNIQUE INDEX id_type_period_count (id, type, period, count) USING BTREE,
984 INDEX type_period_count (type, period, count) USING BTREE
985 ) ' . $charset_collate . ';'
986 );
987
988 // add default options
989 add_option( 'post_views_counter_settings_general', $this->defaults['general'], null, false );
990 add_option( 'post_views_counter_settings_display', $this->defaults['display'], null, false );
991 add_option( 'post_views_counter_settings_other', $this->defaults['other'], null, false );
992 add_option( 'post_views_counter_settings_emails', $this->get_default_emails_settings(), null, false );
993 add_option( 'post_views_counter_version', $this->defaults['version'], null, false );
994
995 if ( $this->emails_scheduler instanceof Post_Views_Counter_Emails_Scheduler )
996 $this->emails_scheduler->maybe_schedule( get_option( 'post_views_counter_settings_emails', $this->get_default_emails_settings() ) );
997 }
998
999 /**
1000 * Get emails defaults for new option creation.
1001 *
1002 * @return array
1003 */
1004 public function get_default_emails_settings() {
1005 $defaults = $this->defaults['emails'];
1006 $admin_email = get_option( 'admin_email' );
1007
1008 if ( is_email( $admin_email ) )
1009 $defaults['recipient'] = $admin_email;
1010
1011 return $defaults;
1012 }
1013
1014 /**
1015 * Plugin deactivation.
1016 *
1017 * @global object $wpdb
1018 *
1019 * @param bool $network
1020 *
1021 * @return void
1022 */
1023 public function deactivation( $network ) {
1024 // network deactivation?
1025 if ( is_multisite() && $network ) {
1026 global $wpdb;
1027
1028 // get all available sites
1029 $blogs_ids = $wpdb->get_col( 'SELECT blog_id FROM ' . $wpdb->blogs );
1030
1031 foreach ( $blogs_ids as $blog_id ) {
1032 // change to another site
1033 switch_to_blog( (int) $blog_id );
1034
1035 // run current site deactivation process
1036 $this->deactivate_site( true );
1037
1038 restore_current_blog();
1039 }
1040 } else
1041 $this->deactivate_site();
1042 }
1043
1044 /**
1045 * Single site deactivation.
1046 *
1047 * @global object $wpdb
1048 *
1049 * @param bool $multi
1050 *
1051 * @return void
1052 */
1053 public function deactivate_site( $multi = false ) {
1054 if ( $this->emails_scheduler instanceof Post_Views_Counter_Emails_Scheduler )
1055 $this->emails_scheduler->clear();
1056 else
1057 wp_clear_scheduled_hook( 'pvc_weekly_content_summary_send' );
1058
1059 if ( $multi === true ) {
1060 $options = get_option( 'post_views_counter_settings_other', [] );
1061 $check = is_array( $options ) && ! empty( $options['deactivation_delete'] );
1062 } else
1063 $check = $this->options['other']['deactivation_delete'];
1064
1065 // delete options if needed
1066 if ( $check ) {
1067 // Give a compatible Pro client one non-blocking opportunity to
1068 // release the remote license before Free removes shared settings.
1069 if ( function_exists( 'Post_Views_Counter_Pro' ) ) {
1070 $pvcp = Post_Views_Counter_Pro();
1071
1072 if ( isset( $pvcp->settings ) && method_exists( $pvcp->settings, 'delete_license_state_on_deactivation' ) )
1073 $pvcp->settings->delete_license_state_on_deactivation();
1074 }
1075
1076 // delete options
1077 delete_option( 'post_views_counter_settings_general' );
1078 delete_option( 'post_views_counter_settings_display' );
1079 delete_option( 'post_views_counter_settings_other' );
1080 delete_option( 'post_views_counter_settings_emails' );
1081 delete_option( 'post_views_counter_activation_date' );
1082 delete_option( 'post_views_counter_version' );
1083
1084 // delete transients
1085 delete_transient( 'post_views_counter_ip_cache' );
1086
1087 global $wpdb;
1088
1089 // delete table from database
1090 $wpdb->query( 'DROP TABLE IF EXISTS ' . $wpdb->prefix . 'post_views' );
1091 }
1092
1093 // remove schedule
1094 wp_clear_scheduled_hook( 'pvc_reset_counts' );
1095
1096 remove_action( 'pvc_reset_counts', [ $this->cron, 'reset_counts' ] );
1097 }
1098
1099 /**
1100 * Initialize new network site.
1101 *
1102 * @param object $site
1103 *
1104 * @return void
1105 */
1106 public function init_new_network_site( $site ) {
1107 if ( is_multisite() ) {
1108 // change to another site
1109 switch_to_blog( $site->blog_id );
1110
1111 // run current site activation process
1112 $this->activate_site();
1113
1114 restore_current_blog();
1115 }
1116 }
1117
1118 /**
1119 * Load text domain.
1120 *
1121 * @return void
1122 */
1123 public function load_textdomain() {
1124 load_plugin_textdomain( 'post-views-counter', false, POST_VIEWS_COUNTER_REL_PATH . '/languages/' );
1125 }
1126
1127 /**
1128 * Load pluggable template functions.
1129 *
1130 * @return void
1131 */
1132 public function load_pluggable_functions() {
1133 include_once( POST_VIEWS_COUNTER_PATH . 'includes/functions.php' );
1134 }
1135
1136 /**
1137 * Enqueue admin scripts and styles.
1138 *
1139 * @global string $post_type
1140 * @global string $wp_version
1141 *
1142 * @param string $page
1143 *
1144 * @return void
1145 */
1146 public function admin_enqueue_scripts( $page ) {
1147 global $post_type;
1148 global $wp_version;
1149
1150 // register styles
1151 wp_register_style( 'pvc-admin', POST_VIEWS_COUNTER_URL . '/css/admin-settings.css', [], $this->defaults['version'] );
1152 wp_register_style( 'pvc-admin-post-style', POST_VIEWS_COUNTER_URL . '/css/admin-post.css', [], $this->defaults['version'] );
1153
1154 // register scripts
1155 wp_register_script( 'pvc-admin-settings', POST_VIEWS_COUNTER_URL . '/js/admin-settings.js', [ 'jquery' ], $this->defaults['version'] );
1156 wp_register_script( 'pvc-admin-post', POST_VIEWS_COUNTER_URL . '/js/admin-post.js', [ 'jquery' ], $this->defaults['version'] );
1157 wp_register_script( 'pvc-admin-quick-edit', POST_VIEWS_COUNTER_URL . '/js/admin-quick-edit.js', [ 'jquery' ], $this->defaults['version'] );
1158
1159 // load on pvc settings page
1160 if ( in_array( $page, [ 'toplevel_page_post-views-counter', 'settings_page_post-views-counter' ], true ) ) {
1161 wp_enqueue_script( 'pvc-admin-settings' );
1162
1163 // prepare script data
1164 $script_data = [
1165 'resetToDefaults' => esc_html__( 'Are you sure you want to reset these settings to defaults?', 'post-views-counter' ),
1166 'resetViews' => esc_html__( 'Are you sure you want to delete all existing data?', 'post-views-counter' ),
1167 'importViews' => esc_html__( 'Are you sure you want to import views now?', 'post-views-counter' ),
1168 'testEmail' => [
1169 'invalidResponse' => esc_html__( 'The test email request did not return a valid response.', 'post-views-counter' ),
1170 'unexpectedError' => esc_html__( 'The test email request could not be completed. Please try again.', 'post-views-counter' )
1171 ]
1172 ];
1173
1174 wp_add_inline_script( 'pvc-admin-settings', 'var pvcArgsSettings = ' . wp_json_encode( $script_data ) . ";\n", 'before' );
1175
1176 wp_enqueue_style( 'pvc-admin' );
1177 // load on single post page
1178 } elseif ( $page === 'post.php' || $page === 'post-new.php' ) {
1179 $post_types = Post_Views_Counter()->options['general']['post_types_count'];
1180
1181 if ( ! in_array( $post_type, (array) $post_types ) )
1182 return;
1183
1184 wp_enqueue_style( 'pvc-admin-post-style' );
1185
1186 // only enqueue post script in classic editor (not block editor)
1187 // block editor doesn't have the #post-views metabox that this script targets
1188 if ( ! function_exists( 'use_block_editor_for_post' ) ) {
1189 // WP < 5.0, always use classic editor
1190 wp_enqueue_script( 'pvc-admin-post' );
1191 } else {
1192 // get post ID from request
1193 $post_id = isset( $_GET['post'] ) ? absint( $_GET['post'] ) : ( isset( $_POST['post_ID'] ) ? absint( $_POST['post_ID'] ) : 0 );
1194
1195 // check if classic editor is being used
1196 if ( $post_id && ! use_block_editor_for_post( $post_id ) ) {
1197 wp_enqueue_script( 'pvc-admin-post' );
1198 } elseif ( ! $post_id && $page === 'post-new.php' && ! use_block_editor_for_post_type( $post_type ) ) {
1199 // new post - check if block editor is default for this post type
1200 wp_enqueue_script( 'pvc-admin-post' );
1201 }
1202 }
1203 // edit post
1204 } elseif ( $page === 'edit.php' ) {
1205 $post_types = Post_Views_Counter()->options['general']['post_types_count'];
1206
1207 if ( ! in_array( $post_type, (array) $post_types ) )
1208 return;
1209
1210 wp_enqueue_style( 'pvc-admin-post-style' );
1211
1212 wp_enqueue_script( 'pvc-admin-quick-edit' );
1213
1214 // prepare script data
1215 $script_data = [
1216 'nonce' => wp_create_nonce( 'pvc_save_bulk_post_views' ),
1217 'wpVersion59' => version_compare( $wp_version, '5.9', '>=' )
1218 ];
1219
1220 wp_add_inline_script( 'pvc-admin-quick-edit', 'var pvcArgsQuickEdit = ' . wp_json_encode( $script_data ) . ";\n", 'before' );
1221 // widgets
1222 } elseif ( $page === 'widgets.php' )
1223 wp_enqueue_script( 'pvc-admin-widgets', POST_VIEWS_COUNTER_URL . '/js/admin-widgets.js', [ 'jquery' ], $this->defaults['version'] );
1224 // media
1225 elseif ( $page === 'upload.php' )
1226 wp_enqueue_style( 'pvc-admin-post-style' );
1227 }
1228
1229 /**
1230 * Load admin style inline, for menu badge only.
1231 *
1232 * @return void
1233 */
1234 public function admin_print_styles() {
1235 echo '
1236 <style>
1237 .toplevel_page_post-views-counter .pvc-admin-menu-new,
1238 .settings_page_post-views-counter .pvc-admin-menu-new {
1239 font-size: 10px;
1240 vertical-align: super;
1241 color: #ffc107;
1242 }
1243 </style>
1244 ';
1245 }
1246
1247 /**
1248 * Normalize the Count Interval option to canonical hours.
1249 *
1250 * @param mixed $time_between_counts
1251 * @param array $default_interval
1252 *
1253 * @return array
1254 */
1255 public function normalize_time_between_counts( $time_between_counts, $default_interval = [] ) {
1256 $default_number = isset( $default_interval['number'] ) ? (int) $default_interval['number'] : (int) $this->defaults['general']['time_between_counts']['number'];
1257 $default_interval = [
1258 'number' => $default_number,
1259 'type' => 'hours'
1260 ];
1261
1262 if ( ! is_array( $time_between_counts ) )
1263 return $default_interval;
1264
1265 if ( ! isset( $time_between_counts['number'], $time_between_counts['type'] ) || ! is_scalar( $time_between_counts['number'] ) || ! is_scalar( $time_between_counts['type'] ) )
1266 return $default_interval;
1267
1268 $number = (int) $time_between_counts['number'];
1269 $type = sanitize_key( (string) $time_between_counts['type'] );
1270
1271 switch ( $type ) {
1272 case 'minutes':
1273 $hours = (int) ( $number / 60 );
1274 break;
1275 case 'hours':
1276 $hours = $number;
1277 break;
1278 case 'days':
1279 $hours = $number * 24;
1280 break;
1281 case 'weeks':
1282 $hours = $number * 168;
1283 break;
1284 case 'months':
1285 $hours = $number * 720;
1286 break;
1287 case 'years':
1288 $hours = $number * 24 * 365;
1289 break;
1290 default:
1291 return $default_interval;
1292 }
1293
1294 return [
1295 'number' => max( 0, min( 720, (int) $hours ) ),
1296 'type' => 'hours'
1297 ];
1298 }
1299
1300 /**
1301 * Get the preferred admin menu position.
1302 *
1303 * @return string
1304 */
1305 public function get_menu_position() {
1306 $position = isset( $this->options['display']['menu_position'] ) ? $this->options['display']['menu_position'] : '';
1307
1308 if ( in_array( $position, [ 'top', 'sub' ], true ) )
1309 return $position;
1310
1311 if ( isset( $this->options['other']['menu_position'] ) && in_array( $this->options['other']['menu_position'], [ 'top', 'sub' ], true ) )
1312 return $this->options['other']['menu_position'];
1313
1314 return 'top';
1315 }
1316
1317 /**
1318 * Add link to Settings page.
1319 *
1320 * @param array $links
1321 *
1322 * @return array
1323 */
1324 public function plugin_settings_link( $links ) {
1325 if ( ! current_user_can( 'manage_options' ) )
1326 return $links;
1327
1328 // submenu?
1329 if ( $this->get_menu_position() === 'sub' )
1330 $url = admin_url( 'options-general.php?page=post-views-counter' );
1331 // topmenu?
1332 else
1333 $url = admin_url( 'admin.php?page=post-views-counter' );
1334
1335 array_unshift( $links, sprintf( '<a href="%s">%s</a>', esc_url_raw( $url ), esc_html__( 'Settings', 'post-views-counter' ) ) );
1336
1337 return $links;
1338 }
1339 }
1340 }
1341
1342 /**
1343 * Initialize Post Views Counter.
1344 *
1345 * @return object
1346 */
1347 function Post_Views_Counter() {
1348 static $instance;
1349
1350 // first call to instance() initializes the plugin
1351 if ( $instance === null || ! ( $instance instanceof Post_Views_Counter ) )
1352 $instance = Post_Views_Counter::instance();
1353
1354 return $instance;
1355 }
1356
1357 Post_Views_Counter();
1358