PluginProbe ʕ •ᴥ•ʔ
Post Views Counter / 1.7.2
Post Views Counter v1.7.2
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 5 months ago class-columns.php 5 months ago class-counter.php 5 months ago class-crawler-detect.php 5 months ago class-cron.php 5 months ago class-dashboard.php 5 months ago class-frontend.php 5 months ago class-functions.php 5 months ago class-import.php 5 months ago class-integration-gutenberg.php 5 months ago class-integrations.php 5 months ago class-query.php 5 months ago class-settings-api.php 5 months ago class-settings-display.php 5 months ago class-settings-general.php 5 months ago class-settings-integrations.php 5 months ago class-settings-other.php 5 months ago class-settings-reports.php 5 months ago class-settings.php 5 months ago class-toolbar.php 5 months ago class-update.php 5 months ago class-widgets.php 5 months ago functions.php 5 months ago
class-settings.php
950 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: allow to hook into old filter name
283 if ( has_filter( 'post_views_counter_settings_data' ) ) {
284 // Add compatibility fields BEFORE deprecated filter
285 $settings = $this->add_compat_fields( $settings );
286
287 $settings = apply_filters_deprecated(
288 'post_views_counter_settings_data',
289 [ $settings ],
290 '1.7.1',
291 'pvc_settings_data'
292 );
293
294 // Copy Pro's changes from 'exclude' field back to 'exclude_groups' (Pro v1.7.0 compatibility)
295 $settings = $this->sync_compat_fields( $settings );
296 }
297
298 return $settings;
299 }
300
301 /**
302 * Normalize Pro-gated settings when Pro is active.
303 *
304 * This runs at priority 10 (after settings_fields_compat at priority 2,
305 * before Pro hooks at priority 11), ensuring the normalizer executes in
306 * the main settings flow.
307 *
308 * @param array $settings
309 * @return array
310 */
311 public function normalize_pro_settings( $settings ) {
312 // Only run if Pro is active
313 if ( ! class_exists( 'Post_Views_Counter_Pro' ) ) {
314 return $settings;
315 }
316
317 // Normalize fields
318 if ( ! empty( $settings['post-views-counter']['fields'] ) ) {
319 $settings['post-views-counter']['fields'] = $this->normalize_pro_fields( $settings['post-views-counter']['fields'] );
320 }
321
322 return $settings;
323 }
324
325 /**
326 * Add compatibility fields before deprecated filter runs.
327 *
328 * @param array $settings
329 * @return array
330 */
331 private function add_compat_fields( $settings ) {
332 if ( empty( $settings['post-views-counter']['fields'] ) )
333 return $settings;
334
335 $fields =& $settings['post-views-counter']['fields'];
336
337 // Define fallback fields
338 $fallback_fields = [
339 'exclude' => isset( $fields['exclude_groups'] ) ? $fields['exclude_groups'] : [
340 'tab' => 'general',
341 'section' => 'post_views_counter_general_exclusions',
342 'type' => 'custom',
343 'class' => 'pvc-pro',
344 'skip_rendering' => true
345 ],
346 'strict_counts' => [
347 'tab' => 'general',
348 'section' => 'post_views_counter_general_tracking_behavior',
349 'type' => 'boolean',
350 'class' => 'pvc-pro',
351 'skip_rendering' => true
352 ]
353 ];
354
355 // Add missing fields
356 foreach ( $fallback_fields as $field_key => $field_def ) {
357 if ( ! isset( $fields[$field_key] ) ) {
358 $fields[$field_key] = $field_def;
359 }
360 }
361
362 return $settings;
363 }
364
365 /**
366 * Sync changes from compatibility fields back to actual fields.
367 *
368 * @param array $settings
369 * @return array
370 */
371 private function sync_compat_fields( $settings ) {
372 if ( empty( $settings['post-views-counter']['fields'] ) )
373 return $settings;
374
375 $fields =& $settings['post-views-counter']['fields'];
376
377 // If modified 'exclude' field, copy ALL changes to 'exclude_groups'
378 if ( isset( $fields['exclude'] ) && isset( $fields['exclude_groups'] ) ) {
379 // Only copy if 'exclude' was actually modified
380 if ( ! isset( $fields['exclude']['skip_rendering'] ) || ! $fields['exclude']['skip_rendering'] ) {
381 // Copy all changes to exclude_groups
382 foreach ( $fields['exclude'] as $key => $value ) {
383 // Don't overwrite critical properties that define the field structure
384 if ( ! in_array( $key, [ 'tab', 'section', 'title', 'name', 'type' ], true ) ) {
385 $fields['exclude_groups'][$key] = $value;
386 }
387 }
388 }
389
390 // Always mark 'exclude' to not be rendered (it's just an alias)
391 $fields['exclude']['skip_rendering'] = true;
392 }
393
394 return $settings;
395 }
396
397 /**
398 * Normalize Pro-gated fields when Pro is active.
399 *
400 * This method removes PRO badges and disabled states from fields that are
401 * purely Pro-gated (marked with pro_only metadata), while preserving any
402 * disabled states that are based on runtime availability (like missing
403 * caching plugins, object cache, or integration dependencies).
404 *
405 * Runtime availability is checked for specific fields that have dynamic
406 * requirements (not static unavailable flags).
407 *
408 * @param array $fields
409 * @return array
410 */
411 private function normalize_pro_fields( $fields ) {
412 foreach ( $fields as $field_key => &$field ) {
413 // Skip if field doesn't have pro_only flag
414 if ( empty( $field['pro_only'] ) ) {
415 continue;
416 }
417
418 // Check runtime availability for fields with dynamic requirements
419 $is_available = $this->check_field_availability( $field_key, $field );
420
421 // Check if pro_only is an array (for option-level gating like counter_mode['ajax'])
422 if ( is_array( $field['pro_only'] ) ) {
423 // Remove pvc-pro-extended class from field (preserve other classes)
424 if ( isset( $field['class'] ) ) {
425 $classes = array_filter( array_map( 'trim', explode( ' ', $field['class'] ) ) );
426 $classes = array_diff( $classes, [ 'pvc-pro', 'pvc-pro-extended' ] );
427 $field['class'] = implode( ' ', $classes );
428 }
429
430 // Remove pro_only options from disabled array
431 if ( isset( $field['disabled'] ) && is_array( $field['disabled'] ) ) {
432 $field['disabled'] = array_values( array_diff( $field['disabled'], $field['pro_only'] ) );
433
434 // If disabled array is now empty, set to empty array or false
435 if ( empty( $field['disabled'] ) ) {
436 $field['disabled'] = [];
437 }
438 }
439
440 // Clear skip_saving for option-level pro_only fields
441 if ( isset( $field['skip_saving'] ) ) {
442 $field['skip_saving'] = false;
443 }
444 } else {
445 // Field-level gating - only unlock if available
446 if ( $is_available ) {
447 // Remove pvc-pro classes (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 disabled flag
455 if ( isset( $field['disabled'] ) ) {
456 $field['disabled'] = false;
457 }
458
459 // Remove skip_saving flag
460 if ( isset( $field['skip_saving'] ) ) {
461 $field['skip_saving'] = false;
462 }
463 } else {
464 // Not available - only remove badge, keep disabled
465 if ( isset( $field['class'] ) ) {
466 $classes = array_filter( array_map( 'trim', explode( ' ', $field['class'] ) ) );
467 $classes = array_diff( $classes, [ 'pvc-pro', 'pvc-pro-extended' ] );
468 $field['class'] = implode( ' ', $classes );
469 }
470 }
471 }
472 }
473
474 return $fields;
475 }
476
477 /**
478 * Check runtime availability for fields with dynamic requirements.
479 *
480 * @param string $field_key
481 * @param array $field
482 * @return bool
483 */
484 private function check_field_availability( $field_key, $field ) {
485 // Default to available (pure Pro licensing gate)
486 $is_available = true;
487
488 // Check specific fields with runtime requirements
489 switch ( $field_key ) {
490 case 'caching_compatibility':
491 // Check if any caching plugins are active
492 $active_plugins = $this->get_active_caching_plugins();
493 $is_available = ! empty( $active_plugins );
494 break;
495
496 case 'object_cache':
497 // Check if persistent object cache is available
498 $is_available = wp_using_ext_object_cache();
499 break;
500
501 // Other fields are purely license-gated (no runtime requirements)
502 default:
503 $is_available = true;
504 break;
505 }
506
507 return $is_available;
508 }
509
510 /**
511 * Backward compatibility for missing fields.
512 *
513 * @param array $settings
514 * @return array
515 */
516 public function settings_fields_compat( $settings ) {
517 if ( empty( $settings['post-views-counter']['fields'] ) )
518 return $settings;
519
520 $fields =& $settings['post-views-counter']['fields'];
521
522 $canonical_fields = array_merge(
523 $this->general->get_fields(),
524 $this->display->get_fields(),
525 $this->reports->get_fields(),
526 $this->other->get_fields(),
527 $this->integrations->get_fields()
528 );
529
530 $compat_fields = [
531 'data_storage',
532 'restrict_edit_views',
533 'post_views_column',
534 'count_time',
535 'caching_compatibility',
536 'counter_mode',
537 'other_count'
538 ];
539
540 $fallback_fields = [];
541
542 foreach ( $compat_fields as $field_key ) {
543 if ( empty( $fields[$field_key] ) || ! is_array( $fields[$field_key] ) ) {
544 if ( isset( $canonical_fields[$field_key] ) && is_array( $canonical_fields[$field_key] ) ) {
545 $fields[$field_key] = $canonical_fields[$field_key];
546 } else if ( isset( $fallback_fields[$field_key] ) ) {
547 $fields[$field_key] = $fallback_fields[$field_key];
548 } else {
549 $fields[$field_key] = [];
550 }
551 }
552 }
553
554 return $settings;
555 }
556
557 /**
558 * Add settings page.
559 *
560 * @param array $pages
561 *
562 * @return array
563 */
564 public function settings_page( $pages ) {
565 // get main instance
566 $pvc = Post_Views_Counter();
567 $menu_position = $pvc->get_menu_position();
568
569 // default page
570 $pages['post-views-counter'] = [
571 'menu_slug' => 'post-views-counter',
572 'page_title' => __( 'Post Views Counter', 'post-views-counter' ),
573 'menu_title' => $menu_position === 'sub' ? __( 'Post Views Counter', 'post-views-counter' ) : __( 'Post Views', 'post-views-counter' ),
574 'capability' => apply_filters( 'pvc_settings_capability', 'manage_options' ),
575 'callback' => null,
576 'tabs' => [
577 'general' => [
578 'label' => __( 'Counting', 'post-views-counter' ),
579 'option_name' => 'post_views_counter_settings_general',
580 'use_plugin_title' => true
581 ],
582 'display' => [
583 'label' => __( 'Display', 'post-views-counter' ),
584 'option_name' => 'post_views_counter_settings_display',
585 'use_plugin_title' => true
586 ],
587 'reports' => [
588 'label' => __( 'Reports', 'post-views-counter' ),
589 'option_name' => 'post_views_counter_settings_reports',
590 'use_plugin_title' => true
591 ],
592 'integrations' => [
593 'label' => __( 'Integrations', 'post-views-counter' ),
594 'option_name' => 'post_views_counter_settings_integrations',
595 'use_plugin_title' => true
596 ],
597 'other' => [
598 'label' => __( 'Other', 'post-views-counter' ),
599 'option_name' => 'post_views_counter_settings_other',
600 'use_plugin_title' => true
601 ]
602 ]
603 ];
604
605 // update admin title
606 add_filter( 'admin_title', [ $this, 'admin_title' ], 10, 2 );
607
608 // submenu?
609 if ( $menu_position === 'sub' ) {
610 $pages['post-views-counter']['type'] = 'settings_page';
611 // topmenu?
612 } else {
613 // highlight submenus
614 add_filter( 'submenu_file', [ $this, 'submenu_file' ], 10, 2 );
615
616 // add parameters
617 $pages['post-views-counter']['type'] = 'page';
618 $pages['post-views-counter']['icon'] = 'dashicons-chart-bar';
619 $pages['post-views-counter']['position'] = '99.301';
620
621 // add subpages
622 $pages['post-views-counter-general'] = [
623 'menu_slug' => 'post-views-counter',
624 'parent_slug' => 'post-views-counter',
625 'type' => 'subpage',
626 'page_title' => __( 'Counting', 'post-views-counter' ),
627 'menu_title' => __( 'Counting', 'post-views-counter' ),
628 'capability' => apply_filters( 'pvc_settings_capability', 'manage_options' ),
629 'callback' => null
630 ];
631
632 $pages['post-views-counter-display'] = [
633 'menu_slug' => 'post-views-counter&tab=display',
634 'parent_slug' => 'post-views-counter',
635 'type' => 'subpage',
636 'page_title' => __( 'Display', 'post-views-counter' ),
637 'menu_title' => __( 'Display', 'post-views-counter' ),
638 'capability' => apply_filters( 'pvc_settings_capability', 'manage_options' ),
639 'callback' => null
640 ];
641
642 $pages['post-views-counter-reports'] = [
643 'menu_slug' => 'post-views-counter&tab=reports',
644 'parent_slug' => 'post-views-counter',
645 'type' => 'subpage',
646 'page_title' => __( 'Reports', 'post-views-counter' ),
647 'menu_title' => __( 'Reports', 'post-views-counter' ),
648 'capability' => apply_filters( 'pvc_settings_capability', 'manage_options' ),
649 'callback' => null
650 ];
651
652 $pages['post-views-counter-integrations'] = [
653 'menu_slug' => 'post-views-counter&tab=integrations',
654 'parent_slug' => 'post-views-counter',
655 'type' => 'subpage',
656 'page_title' => __( 'Integrations', 'post-views-counter' ),
657 'menu_title' => self::mark_new( __( 'Integrations', 'post-views-counter' ) ),
658 'capability' => apply_filters( 'pvc_settings_capability', 'manage_options' ),
659 'callback' => null
660 ];
661
662 $pages['post-views-counter-other'] = [
663 'menu_slug' => 'post-views-counter&tab=other',
664 'parent_slug' => 'post-views-counter',
665 'type' => 'subpage',
666 'page_title' => __( 'Other', 'post-views-counter' ),
667 'menu_title' => __( 'Other', 'post-views-counter' ),
668 'capability' => apply_filters( 'pvc_settings_capability', 'manage_options' ),
669 'callback' => null
670 ];
671 }
672
673 // Backward compatibility: allow to hook into old filter name
674 if ( has_filter( 'post_views_counter_settings_pages' ) ) {
675 $pages = apply_filters_deprecated(
676 'post_views_counter_settings_pages',
677 [ $pages ],
678 '1.7.1',
679 'pvc_settings_pages'
680 );
681 }
682
683 return $pages;
684 }
685
686 /**
687 * Settings page CSS class(es).
688 *
689 * @param array $class
690 * @return array
691 */
692 public function settings_page_class( $class ) {
693 $is_pro = class_exists( 'Post_Views_Counter_Pro' );
694
695 if ( ! $is_pro )
696 $class[] = 'has-sidebar';
697
698 return $class;
699 }
700
701 /**
702 * Highlight submenu items.
703 *
704 * @param string|null $submenu_file
705 * @param string $parent_file
706 *
707 * @return string|null
708 */
709 public function submenu_file( $submenu_file, $parent_file ) {
710 if ( $parent_file === 'post-views-counter' ) {
711 $tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : 'general';
712
713 if ( $tab !== 'general' )
714 return 'post-views-counter&tab=' . $tab;
715 }
716
717 return $submenu_file;
718 }
719
720 /**
721 * Update admin title.
722 *
723 * @global array $submenu
724 * @global string $pagenow
725 *
726 * @param string $admin_title
727 * @param string $title
728 *
729 * @return string
730 */
731 public function admin_title( $admin_title, $title ) {
732 global $submenu, $pagenow;
733
734 // get main instance
735 $pvc = Post_Views_Counter();
736 $menu_position = $pvc->get_menu_position();
737
738 if ( isset( $_GET['page'] ) && $_GET['page'] === 'post-views-counter' ) {
739 if ( $menu_position === 'sub' && $pagenow === 'options-general.php' ) {
740 // get tab
741 $tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : 'general';
742
743 // get settings pages
744 $pages = $pvc->settings_api->get_pages();
745
746 if ( array_key_exists( $tab, $pages['post-views-counter']['tabs'] ) ) {
747 // update title
748 $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 );
749 }
750 } else if ( $menu_position === 'top' && get_admin_page_parent() === 'post-views-counter' && ! empty( $submenu['post-views-counter'] ) ) {
751 // get tab
752 $tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : 'general';
753
754 // get settings pages
755 $pages = $pvc->settings_api->get_pages();
756
757 if ( array_key_exists( 'post-views-counter-' . $tab, $pages ) ) {
758 // update title
759 $admin_title = $pages['post-views-counter']['page_title'] . ' - ' . preg_replace( '/' . $title . '/', $pages['post-views-counter-' . $tab]['page_title'], $admin_title, 1 );
760 }
761 }
762 }
763
764 return $admin_title;
765 }
766
767 /**
768 * Validate options.
769 *
770 * @global object $wpdb
771 *
772 * @param array $input
773 *
774 * @return array
775 */
776 public function validate_settings( $input ) {
777 // check capability
778 if ( ! current_user_can( 'manage_options' ) )
779 return $input;
780
781 global $wpdb;
782
783 // get main instance
784 $pvc = Post_Views_Counter();
785
786 // use internal settings api to validate settings first
787 $input = $pvc->settings_api->validate_settings( $input );
788
789 // merge exclude fields for backward compatibility
790 if ( isset( $input['exclude_groups'] ) || isset( $input['exclude_roles'] ) ) {
791 $input['exclude'] = [
792 'groups' => isset( $input['exclude_groups'] ) ? $input['exclude_groups'] : [],
793 'roles' => isset( $input['exclude_roles'] ) ? $input['exclude_roles'] : []
794 ];
795 }
796
797 // merge restrict display fields for backward compatibility
798 if ( isset( $input['restrict_groups'] ) || isset( $input['restrict_roles'] ) ) {
799 $input['restrict_display'] = [
800 'groups' => isset( $input['restrict_groups'] ) ? $input['restrict_groups'] : [],
801 'roles' => isset( $input['restrict_roles'] ) ? $input['restrict_roles'] : []
802 ];
803 unset( $input['restrict_groups'], $input['restrict_roles'] );
804 }
805
806 // handle new provider-based import/analyse
807 if ( isset( $_POST['post_views_counter_import_views'] ) || isset( $_POST['post_views_counter_analyse_views'] ) ) {
808 // make sure we do not change anything in the settings
809 $input = $pvc->options['other'];
810
811 // delegate to import class
812 $result = $pvc->import->handle_manual_action( $_POST );
813
814 if ( isset( $result['message'] ) ) {
815 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' );
816 }
817
818 if ( isset( $result['provider_settings'] ) ) {
819 $input['import_provider_settings'] = $result['provider_settings'];
820 }
821
822 return $input;
823 // delete all post views data
824 } elseif ( isset( $_POST['post_views_counter_reset_views'] ) ) {
825 // make sure we do not change anything in the settings
826 $input = $pvc->options['other'];
827
828 if ( $wpdb->query( 'TRUNCATE TABLE ' . $wpdb->prefix . 'post_views' ) )
829 add_settings_error( 'reset_post_views', 'reset_post_views', __( 'All existing data deleted successfully.', 'post-views-counter' ), 'updated' );
830 else
831 add_settings_error( 'reset_post_views', 'reset_post_views', __( 'Error occurred. All existing data were not deleted.', 'post-views-counter' ), 'error' );
832 // save general settings
833 } elseif ( isset( $_POST['save_post_views_counter_settings_general'] ) ) {
834 $input['update_version'] = $pvc->options['general']['update_version'];
835 $input['update_notice'] = $pvc->options['general']['update_notice'];
836 $input['update_delay_date'] = $pvc->options['general']['update_delay_date'];
837 // reset general settings
838 } elseif ( isset( $_POST['reset_post_views_counter_settings_general'] ) ) {
839 $input['update_version'] = $pvc->options['general']['update_version'];
840 $input['update_notice'] = $pvc->options['general']['update_notice'];
841 $input['update_delay_date'] = $pvc->options['general']['update_delay_date'];
842 // save other settings (handle provider inputs)
843 } elseif ( isset( $_POST['save_post_views_counter_settings_other'] ) ) {
844 $input['import_provider_settings'] = $pvc->import->prepare_provider_settings_from_request( $_POST );
845
846 // keep menu position for backward compatibility with older add-ons expecting it under "other" settings
847 if ( ! isset( $input['menu_position'] ) ) {
848 $input['menu_position'] = $pvc->get_menu_position();
849 }
850 // save integrations settings
851 } elseif ( isset( $_POST['save_post_views_counter_settings_integrations'] ) ) {
852 // ensure integrations array exists
853 if ( ! isset( $input['integrations'] ) ) {
854 $input['integrations'] = [];
855 }
856
857 // get all known integrations
858 $known_integrations = array_keys( Post_Views_Counter_Integrations::get_base_integrations() );
859
860 // preserve unknown slugs from existing settings
861 $existing = $pvc->options['integrations']['integrations'];
862 foreach ( $existing as $slug => $status ) {
863 if ( ! in_array( $slug, $known_integrations, true ) ) {
864 $input['integrations'][$slug] = $status;
865 }
866 }
867
868 // set missing known integrations to false (unchecked boxes don't submit)
869 foreach ( $known_integrations as $slug ) {
870 if ( ! isset( $input['integrations'][$slug] ) ) {
871 $input['integrations'][$slug] = false;
872 }
873 }
874 }
875
876 return $input;
877 }
878
879 /**
880 * Register core PVC database tables for status checking.
881 *
882 * @param array $tables Existing table definitions
883 * @return array
884 */
885 public function register_core_tables( $tables ) {
886 $tables[] = [
887 'name' => 'post_views',
888 'label' => 'post_views'
889 ];
890
891 return $tables;
892 }
893
894 /**
895 * Backward compatibility for section IDs.
896 *
897 * @param array $settings
898 * @return array
899 */
900 public function settings_sections_compat( $settings ) {
901 if ( empty( $settings['post-views-counter']['fields'] ) )
902 return $settings;
903
904 $fields =& $settings['post-views-counter']['fields'];
905
906 $compat_sections = [
907 'technology_count' => [
908 'legacy' => 'post_views_counter_general_settings',
909 'current' => 'post_views_counter_general_tracking_targets'
910 ],
911 'post_views_column' => [
912 'legacy' => 'post_views_counter_display_settings',
913 'current' => 'post_views_counter_display_admin'
914 ],
915 'restrict_edit_views' => [
916 'legacy' => 'post_views_counter_display_settings',
917 'current' => 'post_views_counter_display_admin'
918 ],
919 'menu_position' => [
920 'legacy' => 'post_views_counter_other_management',
921 'current' => 'post_views_counter_display_admin'
922 ]
923 ];
924
925 foreach ( $compat_sections as $field => $map ) {
926 if ( empty( $fields[$field] ) )
927 continue;
928
929 if ( isset( $fields[$field]['section'] ) && $fields[$field]['section'] === $map['legacy'] )
930 $fields[$field]['section'] = $map['current'];
931 }
932
933 return $settings;
934 }
935
936 /**
937 * Mark menu item as new.
938 *
939 * @param string $text
940 * @return string
941 */
942 public static function mark_new( $text ) {
943 return sprintf(
944 '%s<span class="pvc-admin-menu-new">&nbsp;%s</span>',
945 $text,
946 __( 'NEW!', 'post-views-counter' )
947 );
948 }
949 }
950