PluginProbe
Version Info – Server Health Monitor, PHP & MySQL Version Display, Environment Indicators / trunk
Version Info – Server Health Monitor, PHP & MySQL Version Display, Environment Indicators vtrunk
2.1.0 2.0.3 2.0.2 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.2.0 1.2.1 1.3.0 1.3.1 1.3.2 1.3.3 2.0.0 2.0.1
version-info / includes / SettingsPage.php

SettingsPage.php in Version Info – Server Health Monitor, PHP & MySQL Version Display, Environment Indicators trunk, at includes/SettingsPage.php

663 lines 23.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php // phpcs:disable WordPress.Files.FileName -- PSR-4 class file naming; renaming would break the autoloader.
2
3
4 namespace GauchoPlugins\VersionInfo;
5
6 defined( 'ABSPATH' ) || exit; // Exit if accessed directly.
7
8 /**
9 * Tabbed plugin settings page under Settings > Version Info.
10 */
11 class SettingsPage {
12
13 // Note: `private const` requires PHP 7.1+; plain `const` keeps these readable on PHP 5.6.
14 const PRO_TABS = array( 'system_resources', 'environment', 'server_location', 'version_history', 'health_advisor', 'system_export', 'email_alerts' );
15 const AGENCY_TABS = array( 'white_label', 'access_control', 'error_log' );
16
17 /**
18 * Hooks the settings page and option registration.
19 */
20 public function register() {
21 add_action( 'admin_menu', array( $this, 'add_page' ) );
22 add_action( 'admin_init', array( $this, 'register_settings' ) );
23 }
24
25 /**
26 * Adds the options page to the Settings menu.
27 */
28 public function add_page() {
29 add_options_page(
30 __( 'Version Info Settings', 'version-info' ),
31 __( 'Version Info', 'version-info' ),
32 'manage_options',
33 'version-info',
34 array( $this, 'render' )
35 );
36 }
37
38 /**
39 * Registers every plugin option in its per-tab settings group.
40 */
41 public function register_settings() {
42 // General tab — own group so saving doesn't affect other tabs.
43 register_setting(
44 'version_info_general_group',
45 'version_info_show_footer',
46 array(
47 'type' => 'boolean',
48 'sanitize_callback' => array( $this, 'sanitize_bool' ),
49 'default' => true,
50 )
51 );
52 register_setting(
53 'version_info_general_group',
54 'version_info_show_admin_bar',
55 array(
56 'type' => 'boolean',
57 'sanitize_callback' => array( $this, 'sanitize_bool' ),
58 'default' => false,
59 )
60 );
61 register_setting(
62 'version_info_general_group',
63 'version_info_show_dashboard_widget',
64 array(
65 'type' => 'boolean',
66 'sanitize_callback' => array( $this, 'sanitize_bool' ),
67 'default' => false,
68 )
69 );
70 register_setting(
71 'version_info_general_group',
72 'version_info_widget_show_resources',
73 array(
74 'type' => 'boolean',
75 'sanitize_callback' => array( $this, 'sanitize_bool' ),
76 'default' => false,
77 )
78 );
79 // In version_info_general_group, so its <tr> MUST live in the General
80 // form below — an option registered to a group whose form doesn't
81 // render it gets reset to false on every save (the 2.0.3 bug class).
82 register_setting(
83 'version_info_general_group',
84 'version_info_show_memory',
85 array(
86 'type' => 'boolean',
87 'sanitize_callback' => array( $this, 'sanitize_bool' ),
88 'default' => true,
89 )
90 );
91 // Server Location tab — own group so saving doesn't clobber the General-tab
92 // checkboxes (which would otherwise get reset to false on every Server Location
93 // save because unchecked checkboxes aren't in $_POST). Reported by Steve
94 // Guccione 2026-05-27 — saving Server Location was disabling the dashboard widget.
95 register_setting(
96 'version_info_location_group',
97 'version_info_location_enabled',
98 array(
99 'type' => 'boolean',
100 'sanitize_callback' => array( $this, 'sanitize_bool' ),
101 'default' => true,
102 )
103 );
104 register_setting(
105 'version_info_location_group',
106 'version_info_location_provider',
107 array(
108 'type' => 'string',
109 'sanitize_callback' => array( $this, 'sanitize_location_provider' ),
110 'default' => 'vi_anonymous',
111 )
112 );
113 register_setting(
114 'version_info_location_group',
115 'version_info_location_maxmind_key',
116 array(
117 'type' => 'string',
118 'sanitize_callback' => 'sanitize_text_field',
119 'default' => '',
120 )
121 );
122
123 // Environment tab — isolated group.
124 register_setting(
125 'version_info_environment_group',
126 'version_info_show_env_badge',
127 array(
128 'type' => 'boolean',
129 'sanitize_callback' => array( $this, 'sanitize_bool' ),
130 'default' => false,
131 )
132 );
133 register_setting(
134 'version_info_environment_group',
135 'version_info_env_admin_bar_highlight',
136 array(
137 'type' => 'boolean',
138 'sanitize_callback' => array( $this, 'sanitize_bool' ),
139 'default' => false,
140 )
141 );
142
143 // White Label tab — isolated group.
144 register_setting(
145 'version_info_white_label_group',
146 'version_info_wl_plugin_name',
147 array(
148 'type' => 'string',
149 'sanitize_callback' => 'sanitize_text_field',
150 'default' => '',
151 )
152 );
153 register_setting(
154 'version_info_white_label_group',
155 'version_info_wl_author_name',
156 array(
157 'type' => 'string',
158 'sanitize_callback' => 'sanitize_text_field',
159 'default' => '',
160 )
161 );
162 register_setting(
163 'version_info_white_label_group',
164 'version_info_wl_hide_branding',
165 array(
166 'type' => 'boolean',
167 'sanitize_callback' => array( $this, 'sanitize_bool' ),
168 'default' => false,
169 )
170 );
171 register_setting(
172 'version_info_white_label_group',
173 'version_info_wl_lock_owner_id',
174 array(
175 'type' => 'integer',
176 'sanitize_callback' => array( $this, 'sanitize_lock_owner' ),
177 'default' => 0,
178 )
179 );
180 register_setting(
181 'version_info_white_label_group',
182 'version_info_wl_hide_doc_links',
183 array(
184 'type' => 'boolean',
185 'sanitize_callback' => array( $this, 'sanitize_bool' ),
186 'default' => false,
187 )
188 );
189
190 // Access Control tab — isolated group.
191 register_setting(
192 'version_info_access_control_group',
193 'version_info_allowed_roles',
194 array(
195 'type' => 'array',
196 'sanitize_callback' => array( $this, 'sanitize_roles' ),
197 'default' => array( 'administrator' ),
198 )
199 );
200
201 // Email Alerts tab — isolated group.
202 register_setting(
203 'version_info_email_alerts_group',
204 'version_info_alert_enabled',
205 array(
206 'type' => 'boolean',
207 'sanitize_callback' => array( $this, 'sanitize_bool' ),
208 'default' => false,
209 )
210 );
211 register_setting(
212 'version_info_email_alerts_group',
213 'version_info_alert_recipients',
214 array(
215 'type' => 'string',
216 'sanitize_callback' => 'sanitize_text_field',
217 'default' => '',
218 )
219 );
220 register_setting(
221 'version_info_email_alerts_group',
222 'version_info_alert_types',
223 array(
224 'type' => 'array',
225 'sanitize_callback' => array( $this, 'sanitize_alert_types' ),
226 'default' => array( 'wordpress', 'php', 'mysql' ),
227 )
228 );
229 }
230
231 /**
232 * Normalizes a checkbox value to a strict boolean.
233 *
234 * @param mixed $input Raw option value.
235 */
236 public function sanitize_bool( $input ) {
237 return filter_var( $input, FILTER_VALIDATE_BOOLEAN );
238 }
239
240 /**
241 * Convert the "lock to me" checkbox state into a user-ID owner.
242 *
243 * Form posts "1" when checked or "0" via the paired hidden field when
244 * unchecked. We map "1" to the current user's ID so the option stores
245 * "who owns the lock". Only the existing owner is allowed to flip the
246 * value — anyone else attempting to edit gets the old value back.
247 *
248 * @param mixed $input Raw option value.
249 * @return int
250 */
251 public function sanitize_lock_owner( $input ) {
252 $current_owner = (int) get_option( 'version_info_wl_lock_owner_id', 0 );
253 $current_user = (int) get_current_user_id();
254
255 if ( $current_owner > 0 && $current_owner !== $current_user ) {
256 return $current_owner;
257 }
258
259 $checked = ( '1' === (string) $input || 1 === $input || true === $input );
260
261 if ( $checked ) {
262 return $current_user > 0 ? $current_user : 0;
263 }
264
265 return 0;
266 }
267
268 /**
269 * Restrict the location-provider option to the known set.
270 *
271 * @param mixed $input Raw option value.
272 * @return string
273 */
274 public function sanitize_location_provider( $input ) {
275 $valid = array(
276 'vi_anonymous',
277 'cloudflare_trace',
278 'ip_api',
279 'maxmind',
280 );
281 $input = is_string( $input ) ? $input : '';
282 return in_array( $input, $valid, true ) ? $input : 'vi_anonymous';
283 }
284
285 /**
286 * Sanitizes the allowed-roles option into a list of role slugs.
287 *
288 * @param mixed $input Raw option value.
289 * @return string[]
290 */
291 public function sanitize_roles( $input ) {
292 if ( ! is_array( $input ) ) {
293 return array( 'administrator' );
294 }
295 return array_map( 'sanitize_key', $input );
296 }
297
298 /**
299 * Restricts the alert-types option to the known component set.
300 *
301 * @param mixed $input Raw option value.
302 * @return string[]
303 */
304 public function sanitize_alert_types( $input ) {
305 if ( ! is_array( $input ) ) {
306 return array( 'wordpress', 'php', 'mysql' );
307 }
308 $valid = array( 'wordpress', 'php', 'mysql' );
309 return array_values( array_intersect( array_map( 'sanitize_key', $input ), $valid ) );
310 }
311
312 /**
313 * Renders the settings page shell and the active tab.
314 */
315 public function render() {
316 if ( ! current_user_can( 'manage_options' ) ) {
317 return;
318 }
319
320 $tabs = array(
321 'general' => __( 'General', 'version-info' ),
322 'system_resources' => __( 'System Resources', 'version-info' ),
323 'environment' => __( 'Environment', 'version-info' ),
324 'server_location' => __( 'Server Location', 'version-info' ),
325 'version_history' => __( 'Version History', 'version-info' ),
326 'health_advisor' => __( 'Health Advisor', 'version-info' ),
327 'system_export' => __( 'System Export', 'version-info' ),
328 'white_label' => __( 'White Label', 'version-info' ),
329 'access_control' => __( 'Access Control', 'version-info' ),
330 'email_alerts' => __( 'Email Alerts', 'version-info' ),
331 'error_log' => __( 'Error Log', 'version-info' ),
332 );
333
334 /**
335 * Filtered settings-tab map.
336 *
337 * @var array<string, string> $tabs
338 */
339 $tabs = apply_filters( 'version_info_settings_tabs', $tabs );
340
341 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
342 $current_tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : 'general';
343 if ( ! array_key_exists( $current_tab, $tabs ) ) {
344 $current_tab = 'general';
345 }
346
347 $can_use_premium = vi_fs()->can_use_premium_code();
348 // is_plan_or_trial matches the registration gate in Plugin::maybe_init_pro()
349 // — Agency-trial users previously saw locked tabs while their hooks ran.
350 $is_agency = $can_use_premium && vi_fs()->is_plan_or_trial( 'agency' );
351
352 echo '<div class="wrap">';
353 echo '<h1>' . esc_html__( 'Version Info Settings', 'version-info' ) . '</h1>';
354
355 echo '<h2 class="nav-tab-wrapper">';
356 foreach ( $tabs as $tab_id => $tab_label ) {
357 $url = add_query_arg(
358 array(
359 'page' => 'version-info',
360 'tab' => $tab_id,
361 ),
362 admin_url( 'options-general.php' )
363 );
364 $class = ( $current_tab === $tab_id ) ? ' nav-tab-active' : '';
365
366 echo '<a href="' . esc_url( $url ) . '" class="nav-tab' . esc_attr( $class ) . '">';
367 echo esc_html( $tab_label );
368
369 if ( in_array( $tab_id, self::AGENCY_TABS, true ) && ! $is_agency ) {
370 echo ' <span class="dashicons dashicons-lock" style="font-size:14px;line-height:inherit;vertical-align:text-bottom;"></span>';
371 } elseif ( in_array( $tab_id, self::PRO_TABS, true ) && ! $can_use_premium ) {
372 echo ' <span class="dashicons dashicons-lock" style="font-size:14px;line-height:inherit;vertical-align:text-bottom;"></span>';
373 }
374
375 echo '</a>';
376 }
377 echo '</h2>';
378
379 $this->render_tab( $current_tab, $can_use_premium, $is_agency );
380
381 echo '</div>';
382 }
383
384 /**
385 * Renders the requested tab, or a locked placeholder for unavailable tiers.
386 *
387 * @param string $tab_id Active tab identifier.
388 * @param bool $can_use_premium Whether premium code may run.
389 * @param bool $is_agency Whether the Agency plan (or trial) is active.
390 */
391 private function render_tab( $tab_id, $can_use_premium, $is_agency ) {
392 if ( in_array( $tab_id, self::AGENCY_TABS, true ) && ! $is_agency ) {
393 $this->render_agency_tab_placeholder( $tab_id );
394 return;
395 }
396
397 if ( in_array( $tab_id, self::PRO_TABS, true ) && ! $can_use_premium ) {
398 $this->render_pro_tab_placeholder( $tab_id );
399 return;
400 }
401
402 switch ( $tab_id ) {
403 case 'general':
404 $this->render_general_tab();
405 break;
406 default:
407 do_action( "version_info_render_tab_{$tab_id}" );
408 break;
409 }
410 }
411
412 /**
413 * Renders the General settings tab.
414 */
415 private function render_general_tab() {
416 $can_use_premium = vi_fs()->can_use_premium_code();
417 $widget_enabled = (bool) get_option( 'version_info_show_dashboard_widget', false );
418 $doc_link = Plugin::doc_link( 'getting-started-installation-and-setup', __( 'Setup & display-locations docs', 'version-info' ) );
419 if ( '' !== $doc_link ) {
420 echo '<p style="margin-top:12px;">' . $doc_link . '</p>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- helper returns safe markup
421 }
422 ?>
423 <form method="post" action="options.php">
424 <?php settings_fields( 'version_info_general_group' ); ?>
425 <table class="form-table" role="presentation">
426 <tr>
427 <th scope="row"><?php esc_html_e( 'Show Version Info in Admin Bar', 'version-info' ); ?></th>
428 <td>
429 <input type="checkbox" name="version_info_show_admin_bar" value="1"
430 <?php checked( 1, get_option( 'version_info_show_admin_bar', false ) ); ?> />
431 </td>
432 </tr>
433 <tr>
434 <th scope="row">
435 <label for="vi_show_dashboard_widget"><?php esc_html_e( 'Show Version Info as Dashboard Widget', 'version-info' ); ?></label>
436 </th>
437 <td>
438 <input type="checkbox" id="vi_show_dashboard_widget" name="version_info_show_dashboard_widget" value="1"
439 <?php checked( 1, $widget_enabled ); ?> />
440 </td>
441 </tr>
442 <tr id="vi_widget_resources_row" style="<?php echo $widget_enabled ? '' : 'display:none;'; ?>">
443 <th scope="row">
444 <?php esc_html_e( 'Show Live System Resources in Dashboard Widget', 'version-info' ); ?>
445 <?php if ( ! $can_use_premium ) : ?>
446 <span class="dashicons dashicons-lock" style="font-size:14px;line-height:inherit;vertical-align:text-bottom;"></span>
447 <?php endif; ?>
448 </th>
449 <td<?php echo $can_use_premium ? '' : ' style="opacity:0.45;pointer-events:none;"'; ?>>
450 <input type="checkbox" name="version_info_widget_show_resources" value="1"
451 <?php checked( 1, get_option( 'version_info_widget_show_resources', false ) ); ?>
452 <?php disabled( ! $can_use_premium ); ?> />
453 <p class="description">
454 <?php esc_html_e( 'Adds live CPU, memory, database, and system details to the Version Info dashboard widget.', 'version-info' ); ?>
455 <?php if ( ! $can_use_premium ) : ?>
456 <br><em><?php esc_html_e( 'PRO feature.', 'version-info' ); ?></em>
457 <a href="<?php echo esc_url( vi_fs()->get_upgrade_url() ); ?>"><?php esc_html_e( 'Upgrade to PRO', 'version-info' ); ?></a>
458 <?php endif; ?>
459 </p>
460 </td>
461 </tr>
462 <tr>
463 <th scope="row"><?php esc_html_e( 'Show Version Info in Footer', 'version-info' ); ?></th>
464 <td>
465 <input type="checkbox" name="version_info_show_footer" value="1"
466 <?php checked( 1, get_option( 'version_info_show_footer', true ) ); ?> />
467 </td>
468 </tr>
469 <tr>
470 <th scope="row"><?php esc_html_e( 'Show Memory & Server IP', 'version-info' ); ?></th>
471 <td>
472 <input type="checkbox" name="version_info_show_memory" value="1"
473 <?php checked( 1, get_option( 'version_info_show_memory', true ) ); ?> />
474 <p class="description"><?php esc_html_e( 'Adds the PHP memory limit, live usage %, and server IP to the footer, admin bar, and dashboard widget.', 'version-info' ); ?></p>
475 </td>
476 </tr>
477 </table>
478 <?php submit_button(); ?>
479 </form>
480 <script>
481 (function () {
482 var parent = document.getElementById( 'vi_show_dashboard_widget' );
483 var row = document.getElementById( 'vi_widget_resources_row' );
484 if ( ! parent || ! row ) { return; }
485 var sync = function () { row.style.display = parent.checked ? '' : 'none'; };
486 parent.addEventListener( 'change', sync );
487 sync();
488 }());
489 </script>
490 <?php
491 }
492
493 /**
494 * Renders the locked preview shown to free users on PRO tabs.
495 *
496 * @param string $tab_id Active tab identifier.
497 */
498 private function render_pro_tab_placeholder( $tab_id ) {
499 $previews = array(
500 'system_resources' => array(
501 'title' => __( 'System Resources', 'version-info' ),
502 'description' => __( 'Monitor real-time CPU load and memory usage directly from your WordPress dashboard.', 'version-info' ),
503 'features' => array(
504 __( 'CPU load percentage with visual bar', 'version-info' ),
505 __( 'RAM usage percentage with visual bar', 'version-info' ),
506 __( 'Database size tracking (data + index)', 'version-info' ),
507 __( 'Live updates via WordPress Heartbeat API', 'version-info' ),
508 ),
509 ),
510 'environment' => array(
511 'title' => __( 'Environment Indicators', 'version-info' ),
512 'description' => __( 'Instantly identify your environment type with a color-coded badge in the Admin Bar.', 'version-info' ),
513 'features' => array(
514 __( 'Auto-detects Production, Staging, Development, and Local', 'version-info' ),
515 __( 'Supports WP_ENVIRONMENT_TYPE, Bedrock, Kinsta, WP Engine, and more', 'version-info' ),
516 __( 'Color-coded Admin Bar badge', 'version-info' ),
517 ),
518 ),
519 'server_location' => array(
520 'title' => __( 'Server Location', 'version-info' ),
521 'description' => __( 'Auto-detect where your server lives, with a privacy-first chooser. Anonymous default that logs nothing — or pick your own provider.', 'version-info' ),
522 'features' => array(
523 __( 'Four selectable providers (Version Info anonymous, Cloudflare trace, ip-api, MaxMind)', 'version-info' ),
524 __( 'Enable/disable auto-detect entirely with one checkbox', 'version-info' ),
525 __( '30-day transient cache + one-click Detect Now', 'version-info' ),
526 __( 'Reverse-DNS fallback when providers are unreachable', 'version-info' ),
527 ),
528 ),
529 'version_history' => array(
530 'title' => __( 'Version History', 'version-info' ),
531 'description' => __( 'Track changes in your WordPress, PHP, and MySQL versions over time.', 'version-info' ),
532 'features' => array(
533 __( 'Automatic detection of version changes', 'version-info' ),
534 __( 'Hooks into WordPress core, plugin, and theme update process', 'version-info' ),
535 __( 'Sortable history table with timestamps', 'version-info' ),
536 ),
537 ),
538 'health_advisor' => array(
539 'title' => __( 'Health Advisor', 'version-info' ),
540 'description' => __( 'Predictive alerts for end-of-life software and critical server issues.', 'version-info' ),
541 'features' => array(
542 __( 'PHP end-of-life date tracking', 'version-info' ),
543 __( 'MySQL end-of-life date tracking', 'version-info' ),
544 __( 'SSL certificate expiry monitoring', 'version-info' ),
545 __( 'Integrates with WordPress Site Health screen', 'version-info' ),
546 ),
547 ),
548 'email_alerts' => array(
549 'title' => __( 'Email Alerts', 'version-info' ),
550 'description' => __( 'Receive priority email notifications whenever a version change is detected on your site.', 'version-info' ),
551 'features' => array(
552 __( 'Alerts for WordPress, PHP, and MySQL version changes', 'version-info' ),
553 __( 'Configurable recipient list', 'version-info' ),
554 __( 'Per-component alert toggles', 'version-info' ),
555 ),
556 ),
557 'system_export' => array(
558 'title' => __( 'System Info Export', 'version-info' ),
559 'description' => __( 'Download a complete snapshot of your technical stack as a JSON file.', 'version-info' ),
560 'features' => array(
561 __( 'One-click JSON download of all system info', 'version-info' ),
562 __( 'Includes PHP extensions, active plugins, and theme data', 'version-info' ),
563 __( 'Ideal for support ticket attachments', 'version-info' ),
564 ),
565 ),
566 );
567
568 $preview = isset( $previews[ $tab_id ] ) ? $previews[ $tab_id ] : null;
569 if ( ! $preview ) {
570 return;
571 }
572
573 $this->render_locked_placeholder( $preview, 'pro' );
574 }
575
576 /**
577 * Renders the locked preview shown to non-Agency users on Agency tabs.
578 *
579 * @param string $tab_id Active tab identifier.
580 */
581 private function render_agency_tab_placeholder( $tab_id ) {
582 $previews = array(
583 'white_label' => array(
584 'title' => __( 'White Label', 'version-info' ),
585 'description' => __( 'Fully rebrand the plugin with your own name and remove all Gaucho Plugins branding.', 'version-info' ),
586 'features' => array(
587 __( 'Custom plugin name throughout the dashboard', 'version-info' ),
588 __( 'Custom author name attribution', 'version-info' ),
589 __( 'Hide Freemius account and support menu items', 'version-info' ),
590 __( 'Hide in-plugin links to docs.versioninfoplugin.com', 'version-info' ),
591 __( 'Lock the White Label tab to a single administrator', 'version-info' ),
592 ),
593 ),
594 'access_control' => array(
595 'title' => __( 'Access Control', 'version-info' ),
596 'description' => __( 'Control which WordPress user roles can see version information across your site.', 'version-info' ),
597 'features' => array(
598 __( 'Per-role visibility toggles', 'version-info' ),
599 __( 'Applies to admin bar, footer, and dashboard widget', 'version-info' ),
600 __( 'Default restricted to administrators', 'version-info' ),
601 ),
602 ),
603 'error_log' => array(
604 'title' => __( 'PHP Error Log', 'version-info' ),
605 'description' => __( 'View and download your PHP error log directly from the WordPress dashboard.', 'version-info' ),
606 'features' => array(
607 __( 'Tail last 100 lines without loading full log into memory', 'version-info' ),
608 __( 'Sensitive file paths automatically masked', 'version-info' ),
609 __( 'Download full log as ZIP for offline analysis', 'version-info' ),
610 ),
611 ),
612 );
613
614 $preview = isset( $previews[ $tab_id ] ) ? $previews[ $tab_id ] : null;
615 if ( ! $preview ) {
616 return;
617 }
618
619 $this->render_locked_placeholder( $preview, 'agency' );
620 }
621
622 /**
623 * Renders the greyed-out feature preview plus the upgrade notice.
624 *
625 * @param array{title: string, description: string, features: string[]} $preview Preview copy for the locked tab.
626 * @param string $tier Either 'pro' or 'agency'.
627 */
628 private function render_locked_placeholder( array $preview, $tier ) {
629 if ( 'agency' === $tier ) {
630 $label = __( 'This is an Agency feature.', 'version-info' );
631 $message = __( 'Upgrade to the Agency plan to unlock this feature.', 'version-info' );
632 $button = __( 'Upgrade to Agency', 'version-info' );
633 } else {
634 $label = __( 'This is a PRO feature.', 'version-info' );
635 $message = __( 'Upgrade to Version Info PRO to unlock this feature.', 'version-info' );
636 $button = __( 'Upgrade to PRO', 'version-info' );
637 }
638
639 echo '<div style="position:relative;margin-top:20px;">';
640
641 echo '<div style="opacity:0.45;pointer-events:none;">';
642 echo '<h2>' . esc_html( $preview['title'] ) . '</h2>';
643 echo '<p>' . esc_html( $preview['description'] ) . '</p>';
644 echo '<table class="form-table" role="presentation">';
645 foreach ( $preview['features'] as $feature ) {
646 echo '<tr><th scope="row">' . esc_html( $feature ) . '</th>';
647 echo '<td><span class="dashicons dashicons-yes-alt" style="color:#999;"></span></td></tr>';
648 }
649 echo '</table>';
650 echo '</div>';
651
652 echo '<div class="notice notice-info" style="margin-top:15px;padding:15px;">';
653 echo '<p><strong>' . esc_html( $label ) . '</strong> ';
654 echo esc_html( $message ) . '</p>';
655 echo '<p><a href="' . esc_url( vi_fs()->get_upgrade_url() ) . '" class="button button-primary">';
656 echo esc_html( $button );
657 echo '</a></p>';
658 echo '</div>';
659
660 echo '</div>';
661 }
662 }
663