PluginProbe ʕ •ᴥ•ʔ
Post Views Counter / 1.7.6
Post Views Counter v1.7.6
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 / includes / class-settings.php
post-views-counter / includes Last commit date
class-admin.php 4 months ago class-columns-modal.php 4 months ago class-columns.php 4 months ago class-counter.php 4 months ago class-crawler-detect.php 4 months ago class-cron.php 4 months ago class-dashboard.php 4 months ago class-frontend.php 4 months ago class-functions.php 4 months ago class-import.php 4 months ago class-integration-gutenberg.php 4 months ago class-integrations.php 4 months ago class-query.php 4 months ago class-settings-api.php 4 months ago class-settings-display.php 4 months ago class-settings-general.php 4 months ago class-settings-integrations.php 4 months ago class-settings-other.php 4 months ago class-settings-reports.php 4 months ago class-settings.php 4 months ago class-toolbar.php 4 months ago class-traffic-signals.php 4 months ago class-update.php 4 months ago class-widgets.php 4 months ago functions.php 4 months ago
class-settings.php
1005 lines
1 <?php
2 // exit if accessed directly
3 if ( ! defined( 'ABSPATH' ) )
4 exit;
5
6 /**
7 * Post_Views_Counter_Settings class.
8 *
9 * @class Post_Views_Counter_Settings
10 */
11 class Post_Views_Counter_Settings {
12
13 /**
14 * @var Post_Views_Counter_Settings_General
15 */
16 public $general;
17
18 /**
19 * @var Post_Views_Counter_Settings_Display
20 */
21 public $display;
22
23 /**
24 * @var Post_Views_Counter_Settings_Reports
25 */
26 public $reports;
27
28 /**
29 * @var Post_Views_Counter_Settings_Other
30 */
31 public $other;
32
33 /**
34 * @var Post_Views_Counter_Settings_Integrations
35 */
36 public $integrations;
37
38 /**
39 * Class constructor.
40 *
41 * @return void
42 */
43 public function __construct() {
44 // actions
45 add_action( 'pvc_settings_sidebar', [ $this, 'settings_sidebar' ], 12, 4 );
46 add_action( 'pvc_settings_form', [ $this, 'settings_form' ], 10, 4 );
47
48 // filters
49 add_filter( 'pvc_settings_data', [ $this, 'settings_data' ], 1 );
50 add_filter( 'pvc_settings_data', [ $this, 'settings_fields_compat' ], 2 );
51 add_filter( 'pvc_settings_data', [ $this, 'normalize_pro_settings' ], 10 );
52 add_filter( 'pvc_settings_data', [ $this, 'settings_sections_compat' ], 99 );
53 add_filter( 'pvc_settings_pages', [ $this, 'settings_page' ] );
54 add_filter( 'pvc_settings_page_class', [ $this, 'settings_page_class' ] );
55 add_filter( 'pvc_plugin_status_tables', [ $this, 'register_core_tables' ] );
56
57 // instantiate page classes
58 $this->general = new Post_Views_Counter_Settings_General();
59 $this->display = new Post_Views_Counter_Settings_Display();
60 $this->reports = new Post_Views_Counter_Settings_Reports();
61 $this->integrations = new Post_Views_Counter_Settings_Integrations();
62 $this->other = new Post_Views_Counter_Settings_Other( $this );
63 }
64
65 /**
66 * Magic method to proxy method calls to page classes for backward compatibility.
67 *
68 * @param string $method
69 * @param array $args
70 *
71 * @return mixed
72 */
73 public function __call( $method, $args ) {
74 // Check if method exists in general page class
75 if ( method_exists( $this->general, $method ) ) {
76 return call_user_func_array( [ $this->general, $method ], $args );
77 }
78
79 // Check if method exists in display page class
80 if ( method_exists( $this->display, $method ) ) {
81 return call_user_func_array( [ $this->display, $method ], $args );
82 }
83
84 // Check if method exists in reports page class
85 if ( method_exists( $this->reports, $method ) ) {
86 return call_user_func_array( [ $this->reports, $method ], $args );
87 }
88
89 // Check if method exists in integrations page class
90 if ( method_exists( $this->integrations, $method ) ) {
91 return call_user_func_array( [ $this->integrations, $method ], $args );
92 }
93
94 // Check if method exists in other page class
95 if ( method_exists( $this->other, $method ) ) {
96 return call_user_func_array( [ $this->other, $method ], $args );
97 }
98
99 // Method not found
100 throw new BadMethodCallException( "Method {$method} does not exist" );
101 }
102
103 /**
104 * Add hidden inputs to redirect to valid page after changing menu position.
105 *
106 * @param string $setting
107 * @param string $page_type
108 * @param string $url_page
109 * @param string $tab_key
110 *
111 * @return void
112 */
113 public function settings_form( $setting, $page_type, $url_page, $tab_key ) {
114 // get main instance
115 $pvc = Post_Views_Counter();
116 $menu_position = $pvc->get_menu_position();
117
118 // topmenu referer
119 $topmenu = '<input type="hidden" name="_wp_http_referer" data-pvc-menu="topmenu" value="' .esc_url( admin_url( 'admin.php?page=post-views-counter' . ( $tab_key !== '' ? '&tab=' . $tab_key : '' ) ) ) . '" />';
120
121 // submenu referer
122 $submenu = '<input type="hidden" name="_wp_http_referer" data-pvc-menu="submenu" value="' .esc_url( admin_url( 'options-general.php?page=post-views-counter' . ( $tab_key !== '' ? '&tab=' . $tab_key : '' ) ) ) . '" />';
123
124 if ( $menu_position === 'sub' )
125 echo $topmenu . $submenu;
126 else
127 echo $submenu . $topmenu;
128 }
129
130 /**
131 * Display settings sidebar.
132 *
133 * @param string $setting
134 * @param string $page_type
135 * @param string $url_page
136 * @param string $tab_key
137 *
138 * @return void
139 */
140 public function settings_sidebar( $setting = '', $page_type = '', $url_page = '', $tab_key = 'general' ) {
141 if ( class_exists( 'Post_Views_Counter_Pro' ) ) {
142 return;
143 }
144
145 // get sidebar content for current tab
146 $content = $this->get_sidebar_content();
147
148 // use general tab content if tab not found
149 if ( ! isset( $content[$tab_key] ) ) {
150 $tab_key = 'general';
151 }
152
153 $tab_content = $content[$tab_key];
154
155 // build body HTML
156 $body_html = '<h4 class="pvc-sidebar-subtitle">' . esc_html( $tab_content['subtitle'] ) . '</h4>';
157
158 // add bullets if present
159 if ( ! empty( $tab_content['bullets'] ) ) {
160 foreach ( $tab_content['bullets'] as $bullet ) {
161 $body_html .= '<p><span class="pvc-icon pvc-icon-check"></span>' . $bullet . '</p>';
162 }
163 }
164
165 // add one-liner
166 $body_html .= '<div class="pvc-sidebar-one-liner">' . esc_html( $tab_content['one_liner'] ) . '</div>';
167
168 echo '
169 <div class="post-views-sidebar">
170 <div class="post-views-credits">
171 <div class="inside">
172 <div class="inner">
173 <div class="pvc-sidebar-info">
174 <div class="pvc-sidebar-head">
175 <h3 class="pvc-sidebar-title">' . 'Post Views Counter' . '</h3>
176 </div>
177 <div class="pvc-sidebar-body">
178 ' . $body_html . '
179 </div>
180 <div class="pvc-sidebar-footer">
181 <a href="https://postviewscounter.com/upgrade/?utm_source=post-views-counter-lite&utm_medium=button&utm_campaign=upgrade-to-pro" class="button button-secondary button-hero pvc-button" target="_blank">' . esc_html__( 'Upgrade to Pro', 'post-views-counter' ) . ' &rarr;</a>
182 <p>' . esc_html__( 'Starting from $29 per year', 'post-views-counter' ) . '<br />' . esc_html__( '14-day money back guarantee.', 'post-views-counter' ) . '</p>
183 </div>
184 </div>
185 </div>
186 </div>
187 </div>
188 </div>';
189 }
190
191 /**
192 * Get sidebar content for each settings tab.
193 *
194 * @return array
195 */
196 private function get_sidebar_content() {
197 return [
198 'general' => [
199 'subtitle' => __( 'Reliable view counting', 'post-views-counter' ),
200 'bullets' => [
201 __( 'Accurate counts with caching enabled', 'post-views-counter' ),
202 __( 'Better performance under high traffic', 'post-views-counter' ),
203 __( 'Protection against fake and repeated views', 'post-views-counter' )
204 ],
205 'one_liner' => __( 'Upgrade to Pro to make your view counts reliable on real-world, cached WordPress sites.', 'post-views-counter' )
206 ],
207 'display' => [
208 'subtitle' => __( 'Accurate & meaningful view display', 'post-views-counter' ),
209 'bullets' => [
210 __( 'Choose which time period is displayed', 'post-views-counter' ),
211 __( 'Always show up-to-date view counts', 'post-views-counter' ),
212 __( 'Control where the counter appears', 'post-views-counter' )
213 ],
214 'one_liner' => __( 'Upgrade to Pro to control what view data visitors actually see - not just total counts.', 'post-views-counter' )
215 ],
216 'reports' => [
217 'subtitle' => __( 'Advanced view reports', 'post-views-counter' ),
218 'bullets' => [],
219 'one_liner' => __( 'Upgrade to Pro to access detailed reports and visual insights for your content.', 'post-views-counter' )
220 ],
221 'integrations' => [
222 'subtitle' => __( 'Use view counts where they actually matter', 'post-views-counter' ),
223 'bullets' => [
224 __( 'Display and sort content by popularity', 'post-views-counter' ),
225 __( 'Works with the tools you already use', 'post-views-counter' ),
226 __( 'No custom queries or advanced setup', 'post-views-counter' )
227 ],
228 'one_liner' => __( 'Upgrade to Pro to use view data across layouts and blocks - not just store it.', 'post-views-counter' )
229 ],
230 'other' => [
231 'subtitle' => __( 'Migrate your view data safely', 'post-views-counter' ),
232 'bullets' => [
233 __( 'Import view data from other WordPress plugins', 'post-views-counter' ),
234 __( 'Choose how existing data is handled', 'post-views-counter' ),
235 __( 'Support for posts and other content types', 'post-views-counter' )
236 ],
237 'one_liner' => __( 'Upgrade to Pro to migrate historical view data without losing control over existing counts.', 'post-views-counter' )
238 ]
239 ];
240 }
241
242 /**
243 * Add settings data.
244 *
245 * @param array $settings
246 *
247 * @return array
248 */
249 public function settings_data( $settings ) {
250 // add settings
251 $settings['post-views-counter'] = [
252 'label' => __( 'Post Views Counter', 'post-views-counter' ),
253 'form' => [
254 'reports' => [
255 'buttons' => false
256 ]
257 ],
258 'option_name' => [
259 'general' => 'post_views_counter_settings_general',
260 'display' => 'post_views_counter_settings_display',
261 'reports' => 'post_views_counter_settings_reports',
262 'integrations' => 'post_views_counter_settings_integrations',
263 'other' => 'post_views_counter_settings_other'
264 ],
265 'validate' => [ $this, 'validate_settings' ],
266 'sections' => array_merge(
267 $this->general->get_sections(),
268 $this->display->get_sections(),
269 $this->reports->get_sections(),
270 $this->other->get_sections(),
271 $this->integrations->get_sections()
272 ),
273 'fields' => array_merge(
274 $this->general->get_fields(),
275 $this->display->get_fields(),
276 $this->reports->get_fields(),
277 $this->other->get_fields(),
278 $this->integrations->get_fields()
279 )
280 ];
281
282 // Backward compatibility: ensure legacy settings keys exist for older Pro versions.
283 if ( ! isset( $settings['post-views-counter'] ) || ! is_array( $settings['post-views-counter'] ) )
284 $settings['post-views-counter'] = [];
285
286 if ( empty( $settings['post-views-counter']['fields'] ) || ! is_array( $settings['post-views-counter']['fields'] ) )
287 $settings['post-views-counter']['fields'] = [];
288
289 if ( empty( $settings['post-views-counter']['sections'] ) || ! is_array( $settings['post-views-counter']['sections'] ) )
290 $settings['post-views-counter']['sections'] = [];
291
292 if ( ! isset( $settings['post-views-counter']['sections']['post_views_counter_reports_settings'] ) || ! is_array( $settings['post-views-counter']['sections']['post_views_counter_reports_settings'] ) ) {
293 $settings['post-views-counter']['sections']['post_views_counter_reports_settings'] = [
294 'tab' => 'reports',
295 'callback' => null
296 ];
297 }
298
299 // Backward compatibility: allow to hook into old filter name
300 if ( has_filter( 'post_views_counter_settings_data' ) ) {
301 // Add compatibility fields BEFORE deprecated filter
302 $settings = $this->add_compat_fields( $settings );
303
304 $settings = apply_filters_deprecated(
305 'post_views_counter_settings_data',
306 [ $settings ],
307 '1.7.1',
308 'pvc_settings_data'
309 );
310
311 // Copy Pro's changes from 'exclude' field back to 'exclude_groups' (Pro v1.7.0 compatibility)
312 $settings = $this->sync_compat_fields( $settings );
313 }
314
315 return $settings;
316 }
317
318 /**
319 * Normalize Pro-gated settings when Pro is active.
320 *
321 * This runs at priority 10 (after settings_fields_compat at priority 2,
322 * before Pro hooks at priority 11), ensuring the normalizer executes in
323 * the main settings flow.
324 *
325 * @param array $settings
326 * @return array
327 */
328 public function normalize_pro_settings( $settings ) {
329 // Only run if Pro is active
330 if ( ! class_exists( 'Post_Views_Counter_Pro' ) ) {
331 return $settings;
332 }
333
334 // Normalize fields
335 if ( ! empty( $settings['post-views-counter']['fields'] ) ) {
336 $settings['post-views-counter']['fields'] = $this->normalize_pro_fields( $settings['post-views-counter']['fields'] );
337 }
338
339 return $settings;
340 }
341
342 /**
343 * Add compatibility fields before deprecated filter runs.
344 *
345 * @param array $settings
346 * @return array
347 */
348 private function add_compat_fields( $settings ) {
349 if ( empty( $settings['post-views-counter']['fields'] ) )
350 return $settings;
351
352 $fields =& $settings['post-views-counter']['fields'];
353
354 // Define fallback fields
355 $fallback_fields = [
356 'exclude' => isset( $fields['exclude_groups'] ) ? $fields['exclude_groups'] : [
357 'tab' => 'general',
358 'section' => 'post_views_counter_general_exclusions',
359 'type' => 'custom',
360 'class' => 'pvc-pro',
361 'skip_rendering' => true
362 ],
363 'strict_counts' => [
364 'tab' => 'general',
365 'section' => 'post_views_counter_general_tracking_behavior',
366 'type' => 'boolean',
367 'class' => 'pvc-pro',
368 'skip_rendering' => true
369 ],
370 'amp_support' => [
371 'tab' => 'general',
372 'section' => 'post_views_counter_general_tracking_behavior',
373 'type' => 'boolean',
374 'class' => 'pvc-pro',
375 'skip_rendering' => true
376 ]
377 ];
378
379 // Add missing fields
380 foreach ( $fallback_fields as $field_key => $field_def ) {
381 if ( ! isset( $fields[$field_key] ) ) {
382 $fields[$field_key] = $field_def;
383 }
384 }
385
386 return $settings;
387 }
388
389 /**
390 * Sync changes from compatibility fields back to actual fields.
391 *
392 * @param array $settings
393 * @return array
394 */
395 private function sync_compat_fields( $settings ) {
396 if ( empty( $settings['post-views-counter']['fields'] ) )
397 return $settings;
398
399 $fields =& $settings['post-views-counter']['fields'];
400
401 // If modified 'exclude' field, copy ALL changes to 'exclude_groups'
402 if ( isset( $fields['exclude'] ) && isset( $fields['exclude_groups'] ) ) {
403 // Only copy if 'exclude' was actually modified
404 if ( ! isset( $fields['exclude']['skip_rendering'] ) || ! $fields['exclude']['skip_rendering'] ) {
405 // Copy all changes to exclude_groups
406 foreach ( $fields['exclude'] as $key => $value ) {
407 // Don't overwrite critical properties that define the field structure
408 if ( ! in_array( $key, [ 'tab', 'section', 'title', 'name', 'type' ], true ) ) {
409 $fields['exclude_groups'][$key] = $value;
410 }
411 }
412 }
413
414 // Always mark 'exclude' to not be rendered (it's just an alias)
415 $fields['exclude']['skip_rendering'] = true;
416 }
417
418 return $settings;
419 }
420
421 /**
422 * Normalize Pro-gated fields when Pro is active.
423 *
424 * This method removes PRO badges and disabled states from fields that are
425 * purely Pro-gated (marked with pro_only metadata), while preserving any
426 * disabled states that are based on runtime availability (like missing
427 * caching plugins, object cache, or integration dependencies).
428 *
429 * Runtime availability is checked for specific fields that have dynamic
430 * requirements (not static unavailable flags).
431 *
432 * @param array $fields
433 * @return array
434 */
435 private function normalize_pro_fields( $fields ) {
436 foreach ( $fields as $field_key => &$field ) {
437 // Skip if field doesn't have pro_only flag
438 if ( empty( $field['pro_only'] ) ) {
439 continue;
440 }
441
442 // Check runtime availability for fields with dynamic requirements
443 $is_available = $this->check_field_availability( $field_key, $field );
444
445 // Check if pro_only is an array (for option-level gating like counter_mode['ajax'])
446 if ( is_array( $field['pro_only'] ) ) {
447 // Remove pvc-pro-extended class from field (preserve other classes)
448 if ( isset( $field['class'] ) ) {
449 $classes = array_filter( array_map( 'trim', explode( ' ', $field['class'] ) ) );
450 $classes = array_diff( $classes, [ 'pvc-pro', 'pvc-pro-extended' ] );
451 $field['class'] = implode( ' ', $classes );
452 }
453
454 // Remove pro_only options from disabled array
455 if ( isset( $field['disabled'] ) && is_array( $field['disabled'] ) ) {
456 $field['disabled'] = array_values( array_diff( $field['disabled'], $field['pro_only'] ) );
457
458 // If disabled array is now empty, set to empty array or false
459 if ( empty( $field['disabled'] ) ) {
460 $field['disabled'] = [];
461 }
462 }
463
464 // Clear skip_saving for option-level pro_only fields
465 if ( isset( $field['skip_saving'] ) ) {
466 $field['skip_saving'] = false;
467 }
468 } else {
469 // Field-level gating - only unlock if available
470 if ( $is_available ) {
471 // Remove pvc-pro classes (preserve other classes)
472 if ( isset( $field['class'] ) ) {
473 $classes = array_filter( array_map( 'trim', explode( ' ', $field['class'] ) ) );
474 $classes = array_diff( $classes, [ 'pvc-pro', 'pvc-pro-extended' ] );
475 $field['class'] = implode( ' ', $classes );
476 }
477
478 // Remove disabled flag
479 if ( isset( $field['disabled'] ) ) {
480 $field['disabled'] = false;
481 }
482
483 // Remove skip_saving flag
484 if ( isset( $field['skip_saving'] ) ) {
485 $field['skip_saving'] = false;
486 }
487 } else {
488 // Not available - only remove badge, keep disabled
489 if ( isset( $field['class'] ) ) {
490 $classes = array_filter( array_map( 'trim', explode( ' ', $field['class'] ) ) );
491 $classes = array_diff( $classes, [ 'pvc-pro', 'pvc-pro-extended' ] );
492 $field['class'] = implode( ' ', $classes );
493 }
494 }
495 }
496 }
497
498 return $fields;
499 }
500
501 /**
502 * Check runtime availability for fields with dynamic requirements.
503 *
504 * @param string $field_key
505 * @param array $field
506 * @return bool
507 */
508 private function check_field_availability( $field_key, $field ) {
509 // Default to available (pure Pro licensing gate)
510 $is_available = true;
511
512 // Check specific fields with runtime requirements
513 switch ( $field_key ) {
514 case 'caching_compatibility':
515 // Check if any caching plugins are active
516 $active_plugins = $this->get_active_caching_plugins();
517 $is_available = ! empty( $active_plugins );
518 break;
519
520 case 'object_cache':
521 // Check if persistent object cache is available
522 $is_available = wp_using_ext_object_cache();
523 break;
524
525 // Other fields are purely license-gated (no runtime requirements)
526 default:
527 $is_available = true;
528 break;
529 }
530
531 return $is_available;
532 }
533
534 /**
535 * Backward compatibility for missing fields.
536 *
537 * @param array $settings
538 * @return array
539 */
540 public function settings_fields_compat( $settings ) {
541 if ( empty( $settings['post-views-counter']['fields'] ) )
542 return $settings;
543
544 $fields =& $settings['post-views-counter']['fields'];
545
546 $canonical_fields = array_merge(
547 $this->general->get_fields(),
548 $this->display->get_fields(),
549 $this->reports->get_fields(),
550 $this->other->get_fields(),
551 $this->integrations->get_fields()
552 );
553
554 $compat_fields = [
555 'data_storage',
556 'restrict_edit_views',
557 'post_views_column',
558 'count_time',
559 'caching_compatibility',
560 'counter_mode',
561 'other_count'
562 ];
563
564 $fallback_fields = [];
565
566 foreach ( $compat_fields as $field_key ) {
567 if ( empty( $fields[$field_key] ) || ! is_array( $fields[$field_key] ) ) {
568 if ( isset( $canonical_fields[$field_key] ) && is_array( $canonical_fields[$field_key] ) ) {
569 $fields[$field_key] = $canonical_fields[$field_key];
570 } else if ( isset( $fallback_fields[$field_key] ) ) {
571 $fields[$field_key] = $fallback_fields[$field_key];
572 } else {
573 $fields[$field_key] = [];
574 }
575 }
576 }
577
578 return $settings;
579 }
580
581 /**
582 * Add settings page.
583 *
584 * @param array $pages
585 *
586 * @return array
587 */
588 public function settings_page( $pages ) {
589 // get main instance
590 $pvc = Post_Views_Counter();
591 $menu_position = $pvc->get_menu_position();
592
593 // default page
594 $pages['post-views-counter'] = [
595 'menu_slug' => 'post-views-counter',
596 'page_title' => __( 'Post Views Counter', 'post-views-counter' ),
597 'menu_title' => $menu_position === 'sub' ? __( 'Post Views Counter', 'post-views-counter' ) : __( 'Post Views', 'post-views-counter' ),
598 'capability' => apply_filters( 'pvc_settings_capability', 'manage_options' ),
599 'callback' => null,
600 'tabs' => [
601 'general' => [
602 'label' => __( 'Counting', 'post-views-counter' ),
603 'option_name' => 'post_views_counter_settings_general',
604 'use_plugin_title' => true
605 ],
606 'display' => [
607 'label' => __( 'Display', 'post-views-counter' ),
608 'option_name' => 'post_views_counter_settings_display',
609 'use_plugin_title' => true
610 ],
611 'reports' => [
612 'label' => __( 'Reports', 'post-views-counter' ),
613 'option_name' => 'post_views_counter_settings_reports',
614 'use_plugin_title' => true
615 ],
616 'integrations' => [
617 'label' => __( 'Integrations', 'post-views-counter' ),
618 'option_name' => 'post_views_counter_settings_integrations',
619 'use_plugin_title' => true
620 ],
621 'other' => [
622 'label' => __( 'Other', 'post-views-counter' ),
623 'option_name' => 'post_views_counter_settings_other',
624 'use_plugin_title' => true
625 ]
626 ]
627 ];
628
629 // update admin title
630 add_filter( 'admin_title', [ $this, 'admin_title' ], 10, 2 );
631
632 // submenu?
633 if ( $menu_position === 'sub' ) {
634 $pages['post-views-counter']['type'] = 'settings_page';
635 // topmenu?
636 } else {
637 // highlight submenus
638 add_filter( 'submenu_file', [ $this, 'submenu_file' ], 10, 2 );
639
640 // add parameters
641 $pages['post-views-counter']['type'] = 'page';
642 $pages['post-views-counter']['icon'] = 'dashicons-chart-bar';
643 $pages['post-views-counter']['position'] = '99.301';
644
645 // add subpages
646 $pages['post-views-counter-general'] = [
647 'menu_slug' => 'post-views-counter',
648 'parent_slug' => 'post-views-counter',
649 'type' => 'subpage',
650 'page_title' => __( 'Counting', 'post-views-counter' ),
651 'menu_title' => __( 'Counting', 'post-views-counter' ),
652 'capability' => apply_filters( 'pvc_settings_capability', 'manage_options' ),
653 'callback' => null
654 ];
655
656 $pages['post-views-counter-display'] = [
657 'menu_slug' => 'post-views-counter&tab=display',
658 'parent_slug' => 'post-views-counter',
659 'type' => 'subpage',
660 'page_title' => __( 'Display', 'post-views-counter' ),
661 'menu_title' => __( 'Display', 'post-views-counter' ),
662 'capability' => apply_filters( 'pvc_settings_capability', 'manage_options' ),
663 'callback' => null
664 ];
665
666 $pages['post-views-counter-reports'] = [
667 'menu_slug' => 'post-views-counter&tab=reports',
668 'parent_slug' => 'post-views-counter',
669 'type' => 'subpage',
670 'page_title' => __( 'Reports', 'post-views-counter' ),
671 'menu_title' => __( 'Reports', 'post-views-counter' ),
672 'capability' => apply_filters( 'pvc_settings_capability', 'manage_options' ),
673 'callback' => null
674 ];
675
676 $pages['post-views-counter-integrations'] = [
677 'menu_slug' => 'post-views-counter&tab=integrations',
678 'parent_slug' => 'post-views-counter',
679 'type' => 'subpage',
680 'page_title' => __( 'Integrations', 'post-views-counter' ),
681 'menu_title' => self::mark_new( __( 'Integrations', 'post-views-counter' ) ),
682 'capability' => apply_filters( 'pvc_settings_capability', 'manage_options' ),
683 'callback' => null
684 ];
685
686 $pages['post-views-counter-other'] = [
687 'menu_slug' => 'post-views-counter&tab=other',
688 'parent_slug' => 'post-views-counter',
689 'type' => 'subpage',
690 'page_title' => __( 'Other', 'post-views-counter' ),
691 'menu_title' => __( 'Other', 'post-views-counter' ),
692 'capability' => apply_filters( 'pvc_settings_capability', 'manage_options' ),
693 'callback' => null
694 ];
695 }
696
697 // Backward compatibility: ensure legacy reports tab exists for older Pro versions.
698 if ( ! isset( $pages['post-views-counter']['tabs'] ) || ! is_array( $pages['post-views-counter']['tabs'] ) )
699 $pages['post-views-counter']['tabs'] = [];
700
701 if ( ! isset( $pages['post-views-counter']['tabs']['reports'] ) || ! is_array( $pages['post-views-counter']['tabs']['reports'] ) ) {
702 $pages['post-views-counter']['tabs']['reports'] = [
703 'label' => __( 'Reports', 'post-views-counter' ),
704 'option_name' => 'post_views_counter_settings_reports',
705 'use_plugin_title' => true,
706 'disabled' => true
707 ];
708 }
709
710 // Backward compatibility: allow to hook into old filter name
711 if ( has_filter( 'post_views_counter_settings_pages' ) ) {
712 $pages = apply_filters_deprecated(
713 'post_views_counter_settings_pages',
714 [ $pages ],
715 '1.7.1',
716 'pvc_settings_pages'
717 );
718 }
719
720 return $pages;
721 }
722
723 /**
724 * Settings page CSS class(es).
725 *
726 * @param array $class
727 * @return array
728 */
729 public function settings_page_class( $class ) {
730 $is_pro = class_exists( 'Post_Views_Counter_Pro' );
731
732 if ( ! $is_pro )
733 $class[] = 'has-sidebar';
734
735 return $class;
736 }
737
738 /**
739 * Highlight submenu items.
740 *
741 * @param string|null $submenu_file
742 * @param string $parent_file
743 *
744 * @return string|null
745 */
746 public function submenu_file( $submenu_file, $parent_file ) {
747 if ( $parent_file === 'post-views-counter' ) {
748 $tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : 'general';
749
750 if ( $tab !== 'general' )
751 return 'post-views-counter&tab=' . $tab;
752 }
753
754 return $submenu_file;
755 }
756
757 /**
758 * Update admin title.
759 *
760 * @global array $submenu
761 * @global string $pagenow
762 *
763 * @param string $admin_title
764 * @param string $title
765 *
766 * @return string
767 */
768 public function admin_title( $admin_title, $title ) {
769 global $submenu, $pagenow;
770
771 // get main instance
772 $pvc = Post_Views_Counter();
773 $menu_position = $pvc->get_menu_position();
774
775 if ( isset( $_GET['page'] ) && $_GET['page'] === 'post-views-counter' ) {
776 if ( $menu_position === 'sub' && $pagenow === 'options-general.php' ) {
777 // get tab
778 $tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : 'general';
779
780 // get settings pages
781 $pages = $pvc->settings_api->get_pages();
782
783 if ( array_key_exists( $tab, $pages['post-views-counter']['tabs'] ) ) {
784 // update title
785 $admin_title = preg_replace( '/' . $pages['post-views-counter']['page_title'] . '/', $pages['post-views-counter']['page_title'] . ' - ' . $pages['post-views-counter']['tabs'][$tab]['label'], $admin_title, 1 );
786 }
787 } else if ( $menu_position === 'top' && get_admin_page_parent() === 'post-views-counter' && ! empty( $submenu['post-views-counter'] ) ) {
788 // get tab
789 $tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : 'general';
790
791 // get settings pages
792 $pages = $pvc->settings_api->get_pages();
793
794 if ( array_key_exists( 'post-views-counter-' . $tab, $pages ) ) {
795 // update title
796 $admin_title = $pages['post-views-counter']['page_title'] . ' - ' . preg_replace( '/' . $title . '/', $pages['post-views-counter-' . $tab]['page_title'], $admin_title, 1 );
797 }
798 }
799 }
800
801 return $admin_title;
802 }
803
804 /**
805 * Validate options.
806 *
807 * @global object $wpdb
808 *
809 * @param array $input
810 *
811 * @return array
812 */
813 public function validate_settings( $input ) {
814 // check capability
815 if ( ! current_user_can( 'manage_options' ) )
816 return $input;
817
818 global $wpdb;
819
820 // get main instance
821 $pvc = Post_Views_Counter();
822
823 // map exclude array to separate fields before validation (fields post as exclude[groups]/exclude[roles])
824 if ( isset( $input['exclude'] ) && is_array( $input['exclude'] ) ) {
825 if ( isset( $input['exclude']['groups'] ) )
826 $input['exclude_groups'] = $input['exclude']['groups'];
827
828 if ( isset( $input['exclude']['roles'] ) )
829 $input['exclude_roles'] = $input['exclude']['roles'];
830 }
831
832 // map restrict_display array to separate fields before validation (fields post as restrict_display[groups]/restrict_display[roles])
833 if ( isset( $input['restrict_display'] ) && is_array( $input['restrict_display'] ) ) {
834 if ( isset( $input['restrict_display']['groups'] ) )
835 $input['restrict_display_groups'] = $input['restrict_display']['groups'];
836
837 if ( isset( $input['restrict_display']['roles'] ) )
838 $input['restrict_display_roles'] = $input['restrict_display']['roles'];
839 }
840
841 // use internal settings api to validate settings first
842 $input = $pvc->settings_api->validate_settings( $input );
843
844 // merge exclude fields for backward compatibility
845 if ( isset( $input['exclude_groups'] ) || isset( $input['exclude_roles'] ) ) {
846 $input['exclude'] = [
847 'groups' => isset( $input['exclude_groups'] ) ? $input['exclude_groups'] : [],
848 'roles' => isset( $input['exclude_roles'] ) ? $input['exclude_roles'] : []
849 ];
850 }
851
852 // merge restrict display fields for backward compatibility
853 if ( isset( $input['restrict_display_groups'] ) || isset( $input['restrict_display_roles'] ) ) {
854 $input['restrict_display'] = [
855 'groups' => isset( $input['restrict_display_groups'] ) ? $input['restrict_display_groups'] : [],
856 'roles' => isset( $input['restrict_display_roles'] ) ? $input['restrict_display_roles'] : []
857 ];
858 unset( $input['restrict_display_groups'], $input['restrict_display_roles'] );
859 }
860
861 // handle new provider-based import/analyse
862 if ( isset( $_POST['post_views_counter_import_views'] ) || isset( $_POST['post_views_counter_analyse_views'] ) ) {
863 // make sure we do not change anything in the settings
864 $input = $pvc->options['other'];
865
866 // delegate to import class
867 $result = $pvc->import->handle_manual_action( $_POST );
868
869 if ( isset( $result['message'] ) ) {
870 add_settings_error( 'pvc_' . ( isset( $_POST['post_views_counter_analyse_views'] ) ? 'analyse' : 'import' ), 'pvc_' . ( isset( $_POST['post_views_counter_analyse_views'] ) ? 'analyse' : 'import' ), $result['message'], isset( $result['type'] ) ? $result['type'] : 'updated' );
871 }
872
873 if ( isset( $result['provider_settings'] ) ) {
874 $input['import_provider_settings'] = $result['provider_settings'];
875 }
876
877 return $input;
878 // delete all post views data
879 } elseif ( isset( $_POST['post_views_counter_reset_views'] ) ) {
880 // make sure we do not change anything in the settings
881 $input = $pvc->options['other'];
882
883 if ( $wpdb->query( 'TRUNCATE TABLE ' . $wpdb->prefix . 'post_views' ) )
884 add_settings_error( 'reset_post_views', 'reset_post_views', __( 'All existing data deleted successfully.', 'post-views-counter' ), 'updated' );
885 else
886 add_settings_error( 'reset_post_views', 'reset_post_views', __( 'Error occurred. All existing data were not deleted.', 'post-views-counter' ), 'error' );
887 // save general settings
888 } elseif ( isset( $_POST['save_post_views_counter_settings_general'] ) ) {
889 $input['update_version'] = $pvc->options['general']['update_version'];
890 $input['update_notice'] = $pvc->options['general']['update_notice'];
891 $input['update_delay_date'] = $pvc->options['general']['update_delay_date'];
892 // reset general settings
893 } elseif ( isset( $_POST['reset_post_views_counter_settings_general'] ) ) {
894 $input['update_version'] = $pvc->options['general']['update_version'];
895 $input['update_notice'] = $pvc->options['general']['update_notice'];
896 $input['update_delay_date'] = $pvc->options['general']['update_delay_date'];
897 // save other settings (handle provider inputs)
898 } elseif ( isset( $_POST['save_post_views_counter_settings_other'] ) ) {
899 $input['import_provider_settings'] = $pvc->import->prepare_provider_settings_from_request( $_POST );
900
901 // keep menu position for backward compatibility with older add-ons expecting it under "other" settings
902 if ( ! isset( $input['menu_position'] ) ) {
903 $input['menu_position'] = $pvc->get_menu_position();
904 }
905 // save integrations settings
906 } elseif ( isset( $_POST['save_post_views_counter_settings_integrations'] ) ) {
907 // ensure integrations array exists
908 if ( ! isset( $input['integrations'] ) ) {
909 $input['integrations'] = [];
910 }
911
912 // get all known integrations
913 $known_integrations = array_keys( Post_Views_Counter_Integrations::get_base_integrations() );
914
915 // preserve unknown slugs from existing settings
916 $existing = $pvc->options['integrations']['integrations'];
917 foreach ( $existing as $slug => $status ) {
918 if ( ! in_array( $slug, $known_integrations, true ) ) {
919 $input['integrations'][$slug] = $status;
920 }
921 }
922
923 // set missing known integrations to false (unchecked boxes don't submit)
924 foreach ( $known_integrations as $slug ) {
925 if ( ! isset( $input['integrations'][$slug] ) ) {
926 $input['integrations'][$slug] = false;
927 }
928 }
929 }
930
931 return $input;
932 }
933
934 /**
935 * Register core PVC database tables for status checking.
936 *
937 * @param array $tables Existing table definitions
938 * @return array
939 */
940 public function register_core_tables( $tables ) {
941 $tables[] = [
942 'name' => 'post_views',
943 'label' => 'post_views'
944 ];
945
946 return $tables;
947 }
948
949 /**
950 * Backward compatibility for section IDs.
951 *
952 * @param array $settings
953 * @return array
954 */
955 public function settings_sections_compat( $settings ) {
956 if ( empty( $settings['post-views-counter']['fields'] ) )
957 return $settings;
958
959 $fields =& $settings['post-views-counter']['fields'];
960
961 $compat_sections = [
962 'technology_count' => [
963 'legacy' => 'post_views_counter_general_settings',
964 'current' => 'post_views_counter_general_tracking_targets'
965 ],
966 'post_views_column' => [
967 'legacy' => 'post_views_counter_display_settings',
968 'current' => 'post_views_counter_display_admin'
969 ],
970 'restrict_edit_views' => [
971 'legacy' => 'post_views_counter_display_settings',
972 'current' => 'post_views_counter_display_admin'
973 ],
974 'menu_position' => [
975 'legacy' => 'post_views_counter_other_management',
976 'current' => 'post_views_counter_display_admin'
977 ]
978 ];
979
980 foreach ( $compat_sections as $field => $map ) {
981 if ( empty( $fields[$field] ) )
982 continue;
983
984 if ( isset( $fields[$field]['section'] ) && $fields[$field]['section'] === $map['legacy'] )
985 $fields[$field]['section'] = $map['current'];
986 }
987
988 return $settings;
989 }
990
991 /**
992 * Mark menu item as new.
993 *
994 * @param string $text
995 * @return string
996 */
997 public static function mark_new( $text ) {
998 return sprintf(
999 '%s<span class="pvc-admin-menu-new">&nbsp;%s</span>',
1000 $text,
1001 __( 'NEW!', 'post-views-counter' )
1002 );
1003 }
1004 }
1005