Changeset 3696831
- Timestamp:
- 09/15/2026 11:02:13 AM (8 weeks ago)
- Location:
- inquiryflow/trunk
- Files:
-
- 6 deleted
- 34 edited
-
core/assets/build/admin-shell.css (modified) (5 diffs)
-
core/assets/build/admin-shell.js (modified) (1 diff)
-
core/assets/build/license-center.js (deleted)
-
core/assets/src/admin-shell.css (modified) (5 diffs)
-
core/assets/src/admin-shell.js (modified) (1 diff)
-
core/assets/src/design-system/tokens.css (modified) (2 diffs)
-
core/assets/src/license-center.js (deleted)
-
core/src/Admin/AdminShell.php (modified) (8 diffs)
-
core/src/Admin/AdminUrls.php (modified) (2 diffs)
-
core/src/Admin/Assets.php (modified) (1 diff)
-
core/src/Admin/HubPage.php (modified) (6 diffs)
-
core/src/Admin/LegacyRedirects.php (modified) (2 diffs)
-
core/src/Admin/LicenseCenterPage.php (deleted)
-
core/src/Admin/Menu.php (modified) (8 diffs)
-
core/src/Admin/SettingsShell.php (modified) (2 diffs)
-
core/src/Admin/ShellAdminNotices.php (modified) (8 diffs)
-
core/src/Admin/UpsellRegistry.php (deleted)
-
core/src/Admin/WorkspaceRegistry.php (modified) (1 diff)
-
core/src/Admin/WorkspaceShell.php (modified) (3 diffs)
-
core/src/Core.php (modified) (7 diffs)
-
core/src/Licensing (deleted)
-
core/src/Modules/AdminBootstrap.php (modified) (1 diff)
-
core/src/Rest/LicensesController.php (deleted)
-
core/src/Scheduler/ActionSchedulerLoader.php (modified) (2 diffs)
-
core/src/Settings/SettingsPanelRegistry.php (modified) (1 diff)
-
core/version.php (modified) (1 diff)
-
core/wptk-core-shim.php (modified) (1 diff)
-
inquiryflow.php (modified) (3 diffs)
-
modules/inquiryflow/assets/build/settings-admin.js (modified) (6 diffs)
-
modules/inquiryflow/src/Admin/Dashboard.php (modified) (1 diff)
-
modules/inquiryflow/src/Admin/SettingsIntegration.php (modified) (4 diffs)
-
modules/inquiryflow/src/Email/Notifications.php (modified) (1 diff)
-
modules/inquiryflow/src/Frontend/Frontend.php (modified) (1 diff)
-
modules/inquiryflow/src/Module.php (modified) (8 diffs)
-
modules/inquiryflow/src/ProductBootstrap.php (modified) (6 diffs)
-
modules/inquiryflow/src/Rest/AnalyticsController.php (modified) (1 diff)
-
modules/inquiryflow/src/Rest/LeadsController.php (modified) (9 diffs)
-
modules/inquiryflow/src/Rest/SettingsController.php (modified) (1 diff)
-
modules/inquiryflow/src/Security/Captcha.php (modified) (5 diffs)
-
readme.txt (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
inquiryflow/trunk/core/assets/build/admin-shell.css
r3608474 r3696831 15 15 } 16 16 17 /* ---- Upsell bar ---- */18 .wptk-shell-upsell {19 background:#00aa63;20 color: #fff;21 border-bottom: 1px solid rgba(255, 255, 255, 0.12);22 }23 24 .wptk-shell-upsell[hidden] {25 display: none !important;26 }27 28 .wptk-shell-upsell-inner {29 max-width: 1140px;30 margin: 0 auto;31 padding: 10px 24px;32 display: flex;33 align-items: center;34 gap: 16px;35 }36 37 .wptk-shell-upsell-text {38 margin: 0;39 flex: 1;40 font-size: 13.5px;41 line-height: 1.45;42 font-weight: 500;43 text-align: center;44 }45 46 .wptk-shell-upsell-link {47 color: var(--amber-wash, #fef3e2);48 font-weight: 700;49 text-decoration: none;50 white-space: nowrap;51 }52 53 .wptk-shell-upsell-link:hover {54 color: #fff;55 text-decoration: underline;56 }57 58 .wptk-shell-upsell-close {59 flex: none;60 background: transparent;61 border: none;62 color: rgba(255, 255, 255, 0.85);63 font-size: 22px;64 line-height: 1;65 cursor: pointer;66 padding: 4px 8px;67 border-radius: 6px;68 }69 70 .wptk-shell-upsell-close:hover {71 background: rgba(255, 255, 255, 0.12);72 color: #fff;73 }74 75 17 /* ---- Header ---- */ 76 18 .wptk-shell-header { … … 124 66 } 125 67 126 .wptk-shell-pro-badge {127 flex: none;128 font-size: 10px;129 font-weight: 800;130 letter-spacing: 0.06em;131 padding: 4px 8px;132 border-radius: 6px;133 background: var(--amber-wash, #fef3e2);134 color: var(--amber, #b45309);135 }136 68 137 69 .wptk-shell-actions { … … 243 175 } 244 176 245 .wptk-shell-tab-pro {246 font-size: 9px;247 font-weight: 800;248 letter-spacing: 0.05em;249 padding: 2px 5px;250 border-radius: 4px;251 background: var(--amber-wash, #fef3e2);252 color: var(--amber, #b45309);253 }254 177 255 178 /* ---- Body ---- */ … … 328 251 } 329 252 330 .wptk-hub-card-upgrade {331 display: inline-flex;332 align-items: center;333 gap: 4px;334 flex: none;335 font-size: 13px;336 font-weight: 600;337 color: var(--brand, #3858e9);338 text-decoration: none;339 white-space: nowrap;340 }341 342 .wptk-hub-card-upgrade:hover {343 color: var(--brand-hover, #2e75b6);344 text-decoration: underline;345 }346 347 .wptk-hub-card-pro-badge {348 flex: none;349 font-size: 10px;350 font-weight: 800;351 letter-spacing: 0.06em;352 padding: 4px 8px;353 border-radius: 6px;354 background: var(--amber-wash, #fef3e2);355 color: var(--amber, #b45309);356 }357 253 358 254 .wptk-hub-card-body { … … 436 332 } 437 333 438 .wptk-license-stack {439 display: flex;440 flex-direction: column;441 gap: 16px;442 max-width: 640px;443 }444 334 445 335 /* Inquiries / workspace content spacing */ -
inquiryflow/trunk/core/assets/build/admin-shell.js
r3608474 r3696831 1 1 /** 2 * WP Travel Kit — Admin shell (upsell dismiss, placeholders).2 * WP Travel Kit — Admin shell. 3 3 */ 4 4 (function () { 5 5 'use strict'; 6 7 document.querySelectorAll('.wptk-shell-upsell').forEach(function (bar) {8 var key = bar.getAttribute('data-upsell-key') || 'default';9 var storageKey = 'wptk_upsell_dismissed_' + key;10 11 if (sessionStorage.getItem(storageKey)) {12 bar.hidden = true;13 return;14 }15 16 var close = bar.querySelector('.wptk-shell-upsell-close');17 if (!close) {18 return;19 }20 21 close.addEventListener('click', function () {22 sessionStorage.setItem(storageKey, '1');23 bar.hidden = true;24 });25 });26 6 })(); -
inquiryflow/trunk/core/assets/src/admin-shell.css
r3608474 r3696831 15 15 } 16 16 17 /* ---- Upsell bar ---- */18 .wptk-shell-upsell {19 background: linear-gradient(90deg, var(--brand, #1f4e79) 0%, var(--brand-ink, #173a5b) 100%);20 color: #fff;21 border-bottom: 1px solid rgba(255, 255, 255, 0.12);22 }23 24 .wptk-shell-upsell[hidden] {25 display: none !important;26 }27 28 .wptk-shell-upsell-inner {29 max-width: 1140px;30 margin: 0 auto;31 padding: 10px 24px;32 display: flex;33 align-items: center;34 gap: 16px;35 }36 37 .wptk-shell-upsell-text {38 margin: 0;39 flex: 1;40 font-size: 13.5px;41 line-height: 1.45;42 font-weight: 500;43 }44 45 .wptk-shell-upsell-link {46 color: var(--amber-wash, #fef3e2);47 font-weight: 700;48 text-decoration: none;49 white-space: nowrap;50 }51 52 .wptk-shell-upsell-link:hover {53 color: #fff;54 text-decoration: underline;55 }56 57 .wptk-shell-upsell-close {58 flex: none;59 background: transparent;60 border: none;61 color: rgba(255, 255, 255, 0.85);62 font-size: 22px;63 line-height: 1;64 cursor: pointer;65 padding: 4px 8px;66 border-radius: 6px;67 }68 69 .wptk-shell-upsell-close:hover {70 background: rgba(255, 255, 255, 0.12);71 color: #fff;72 }73 74 17 /* ---- Header ---- */ 75 18 .wptk-shell-header { … … 123 66 } 124 67 125 .wptk-shell-pro-badge {126 flex: none;127 font-size: 10px;128 font-weight: 800;129 letter-spacing: 0.06em;130 padding: 4px 8px;131 border-radius: 6px;132 background: var(--amber-wash, #fef3e2);133 color: var(--amber, #b45309);134 }135 68 136 69 .wptk-shell-actions { … … 242 175 } 243 176 244 .wptk-shell-tab-pro {245 font-size: 9px;246 font-weight: 800;247 letter-spacing: 0.05em;248 padding: 2px 5px;249 border-radius: 4px;250 background: var(--amber-wash, #fef3e2);251 color: var(--amber, #b45309);252 }253 177 254 178 /* ---- Body ---- */ … … 327 251 } 328 252 329 .wptk-hub-card-upgrade {330 display: inline-flex;331 align-items: center;332 gap: 4px;333 flex: none;334 font-size: 13px;335 font-weight: 600;336 color: var(--brand, #3858e9);337 text-decoration: none;338 white-space: nowrap;339 }340 341 .wptk-hub-card-upgrade:hover {342 color: var(--brand-hover, #2e75b6);343 text-decoration: underline;344 }345 346 .wptk-hub-card-pro-badge {347 flex: none;348 font-size: 10px;349 font-weight: 800;350 letter-spacing: 0.06em;351 padding: 4px 8px;352 border-radius: 6px;353 background: var(--amber-wash, #fef3e2);354 color: var(--amber, #b45309);355 }356 253 357 254 .wptk-hub-card-body { … … 443 340 } 444 341 445 .wptk-license-stack {446 display: flex;447 flex-direction: column;448 gap: 16px;449 max-width: 640px;450 }451 342 452 343 /* Inquiries / workspace content spacing */ -
inquiryflow/trunk/core/assets/src/admin-shell.js
r3608474 r3696831 1 1 /** 2 * WP Travel Kit — Admin shell (upsell dismiss, placeholders).2 * WP Travel Kit — Admin shell. 3 3 */ 4 4 (function () { 5 5 'use strict'; 6 7 document.querySelectorAll('.wptk-shell-upsell').forEach(function (bar) {8 var key = bar.getAttribute('data-upsell-key') || 'default';9 var storageKey = 'wptk_upsell_dismissed_' + key;10 11 if (sessionStorage.getItem(storageKey)) {12 bar.hidden = true;13 return;14 }15 16 var close = bar.querySelector('.wptk-shell-upsell-close');17 if (!close) {18 return;19 }20 21 close.addEventListener('click', function () {22 sessionStorage.setItem(storageKey, '1');23 bar.hidden = true;24 });25 });26 6 })(); -
inquiryflow/trunk/core/assets/src/design-system/tokens.css
r3608474 r3696831 11 11 --brand-wash: #EAF1F8; /* tinted surface */ 12 12 13 /* Accent (Pro / premium)*/13 /* Accent */ 14 14 --amber: #B45309; 15 15 --amber-wash: #FEF3E2; … … 170 170 .ib-chip--ghost { background: #fff; border-color: var(--hairline); color: var(--ink); } 171 171 172 .ib-badge-pro {173 display: inline-flex; align-items: center; gap: 4px;174 font-size: 11px; font-weight: 700; letter-spacing: .03em;175 padding: 3px 7px; border-radius: 6px;176 background: var(--amber-wash); color: var(--amber);177 text-transform: uppercase;178 }179 180 172 /* status chips */ 181 173 .st { display:inline-flex; align-items:center; gap:6px; font-size:13px; font-weight:600; padding:5px 10px; border-radius: var(--r-pill); } -
inquiryflow/trunk/core/src/Admin/AdminShell.php
r3670262 r3696831 1 1 <?php 2 2 /** 3 * Shared suite admin page chrome ( upsell,header, tabs, centered body).3 * Shared suite admin page chrome (header, tabs, centered body). 4 4 * 5 5 * This file is part of the shared WP Travel Kit suite layer bundled into free suite plugins. … … 50 50 'product_name' => self::SUITE_NAME, 51 51 'page_title' => '', 52 'license_id' => '',53 'show_upsell' => false,54 'upsell_key' => '',55 'upgrade_url' => '',56 'upsell_message' => '',57 'upsell_cta' => '',58 52 'tabs' => array(), 59 53 'active_tab' => '', … … 64 58 65 59 echo '<div class="wptk-admin-shell">'; 66 67 if ( ! empty( $args['show_upsell'] ) ) {68 $this->render_upsell( $args );69 }70 60 71 61 $this->render_header( $args ); … … 88 78 } 89 79 80 /** 81 * Fires inside the admin shell before the main body callback. 82 * 83 * @param array<string, mixed> $args Shell args. 84 * @param Core $core Core instance. 85 */ 86 do_action( 'wptk_admin_shell_before_body', $args, $this->core ); 87 90 88 call_user_func( $content ); 91 89 92 90 echo '</div></div></div>'; 93 }94 95 /**96 * Whether a product license is active for the shell header badge.97 *98 * @param string $license_id License product id.99 * @return bool100 */101 private function is_pro_badge_active( $license_id ) {102 if ( '' === $license_id ) {103 return false;104 }105 106 $registry = $this->core->container()->get( 'license_registry' );107 return $registry->has_product( $license_id ) && $registry->is_active( $license_id );108 }109 110 /**111 * Render upsell bar.112 *113 * @param array<string, mixed> $args Shell args.114 */115 private function render_upsell( array $args ) {116 $key = sanitize_key( (string) ( $args['upsell_key'] ?? 'default' ) );117 $url = (string) ( $args['upgrade_url'] ?? '' );118 $message = (string) ( $args['upsell_message'] ?? '' );119 $cta = (string) ( $args['upsell_cta'] ?? '' );120 121 if ( '' === $cta ) {122 $cta = __( 'Upgrade to Pro', 'inquiryflow' );123 }124 125 if ( '' === $message ) {126 $message = sprintf(127 /* translators: %s: product name */128 __( 'You\'re using %s Free. To unlock more features, consider upgrading to Pro', 'inquiryflow' ),129 (string) ( $args['product_name'] ?? self::SUITE_NAME )130 );131 }132 133 echo '<div class="wptk-shell-upsell" data-upsell-key="' . esc_attr( $key ) . '" role="region" aria-label="' . esc_attr__( 'Upgrade notice', 'inquiryflow' ) . '">';134 echo '<div class="wptk-shell-upsell-inner">';135 echo '<p class="wptk-shell-upsell-text">';136 echo esc_html( $message );137 if ( $url ) {138 echo ' <a class="wptk-shell-upsell-link" href="' . esc_url( $url ) . '" target="_blank" rel="noopener noreferrer">';139 echo esc_html( $cta );140 echo ' →</a>';141 }142 echo '</p>';143 echo '<button type="button" class="wptk-shell-upsell-close" aria-label="' . esc_attr__( 'Dismiss', 'inquiryflow' ) . '">';144 echo '<span aria-hidden="true">×</span>';145 echo '</button>';146 echo '</div></div>';147 91 } 148 92 … … 155 99 $product = (string) $args['product_name']; 156 100 $page_title = (string) $args['page_title']; 157 $license_id = (string) $args['license_id'];158 $pro_active = $this->is_pro_badge_active( $license_id );159 101 160 102 echo '<header class="wptk-shell-header">'; … … 175 117 } 176 118 echo '</div>'; 177 178 if ( $pro_active ) {179 echo '<span class="wptk-shell-pro-badge">' . esc_html__( 'PRO', 'inquiryflow' ) . '</span>';180 }181 119 182 120 echo '</div>'; … … 212 150 $url = add_query_arg( 'tab', $tab_id, admin_url( 'admin.php?page=' . $slug ) ); 213 151 $is_active = $tab_id === $active_tab; 214 $is_pro = ! empty( $tab['pro'] );215 152 $classes = 'wptk-shell-tab' . ( $is_active ? ' is-active' : '' ); 216 153 217 154 echo '<a class="' . esc_attr( $classes ) . '" href="' . esc_url( $url ) . '">'; 218 155 echo esc_html( $tab_label ); 219 if ( $is_pro ) {220 echo ' <span class="wptk-shell-tab-pro">' . esc_html__( 'PRO', 'inquiryflow' ) . '</span>';221 }222 156 echo '</a>'; 223 157 } … … 225 159 echo '</div></nav>'; 226 160 } 227 228 /**229 * Resolve license id for a workspace product.230 *231 * @param string $product_id Workspace product id.232 * @return string233 */234 public static function workspace_license_id( $product_id ) {235 /**236 * Map a workspace product id to its Pro license registry id.237 *238 * @param string $license_id License id (empty if none).239 * @param string $product_id Workspace product id.240 */241 return (string) apply_filters( 'wptk_workspace_license_id', '', $product_id );242 }243 161 } -
inquiryflow/trunk/core/src/Admin/AdminUrls.php
r3670262 r3696831 22 22 const HOME = 'wptk-home'; 23 23 const SETTINGS = 'wptk-kit-settings'; 24 const LICENSES = 'wptk-kit-licenses';25 24 26 25 /** … … 56 55 57 56 /** 58 * License center URL.59 *60 * @param string $product_id Optional product anchor.61 * @return string62 */63 public static function licenses( $product_id = '' ) {64 $url = admin_url( 'admin.php?page=' . self::LICENSES );65 if ( '' !== $product_id ) {66 $url .= '#' . sanitize_key( $product_id );67 }68 return $url;69 }70 71 /**72 57 * Product workspace URL. 73 58 * -
inquiryflow/trunk/core/src/Admin/Assets.php
r3670262 r3696831 74 74 75 75 /** 76 * Enqueue shared suite admin shell (tokens + chrome + dismiss script).76 * Enqueue shared suite admin shell (tokens + chrome). 77 77 */ 78 78 public function enqueue_admin_shell() { -
inquiryflow/trunk/core/src/Admin/HubPage.php
r3670262 r3696831 69 69 'product_name' => AdminShell::SUITE_NAME, 70 70 'page_title' => __( 'Home', 'inquiryflow' ), 71 'description' => __( 'Your travel conversion suite — manage products , settings, and licenses from one place.', 'inquiryflow' ),71 'description' => __( 'Your travel conversion suite — manage products and settings from one place.', 'inquiryflow' ), 72 72 ), 73 73 function () use ( $modules ) { … … 103 103 */ 104 104 private function get_module_cards() { 105 $cards = array(); 106 $workspace = $this->core->container()->get( 'workspace_registry' ); 107 $upsell_registry = $this->core->container()->get( 'upsell_registry' ); 105 $cards = array(); 106 $workspace = $this->core->container()->get( 'workspace_registry' ); 108 107 109 108 foreach ( $this->core->modules()->all() as $manifest ) { … … 112 111 } 113 112 114 $ws = $workspace->get( $manifest->id ); 115 $slug = $ws ? $ws['slug'] : ''; 116 $upsell = $upsell_registry->resolve( $this->core, $manifest->id, $manifest->name ); 117 $config = $upsell_registry->get( $manifest->id ); 118 $license_id = $config ? sanitize_key( (string) ( $config['license_id'] ?? '' ) ) : ''; 113 $ws = $workspace->get( $manifest->id ); 114 $slug = $ws ? $ws['slug'] : ''; 119 115 120 116 $description = (string) $manifest->description; … … 128 124 129 125 $card = array( 130 'id' => $manifest->id, 131 'name' => $manifest->name, 132 'description' => $description, 133 'version' => $manifest->version, 134 'workspace_url' => $slug ? AdminUrls::workspace( $slug ) : '', 135 'settings_url' => AdminUrls::settings( $manifest->id ), 136 'has_upsell' => $upsell_registry->has( $manifest->id ), 137 'license_active' => '' !== $license_id && $upsell_registry->is_pro_active( $this->core, $license_id ), 138 'show_get_pro' => $upsell['show'], 139 'upgrade_url' => $upsell['upgrade_url'], 140 'get_pro_label' => ! empty( $upsell['upsell_cta'] ) ? $upsell['upsell_cta'] : __( 'Upgrade to Pro', 'inquiryflow' ), 126 'id' => $manifest->id, 127 'name' => $manifest->name, 128 'description' => $description, 129 'version' => $manifest->version, 130 'workspace_url' => $slug ? AdminUrls::workspace( $slug ) : '', 131 'settings_url' => AdminUrls::settings( $manifest->id ), 141 132 ); 142 133 … … 163 154 echo '<div class="wptk-hub-card-header">'; 164 155 echo '<h3 class="wptk-hub-card-title">' . esc_html( $module['name'] ) . '</h3>'; 165 166 if ( ! empty( $module['has_upsell'] ) ) {167 if ( ! empty( $module['license_active'] ) ) {168 echo '<span class="wptk-hub-card-pro-badge">' . esc_html__( 'Pro', 'inquiryflow' ) . '</span>';169 } elseif ( ! empty( $module['show_get_pro'] ) && ! empty( $module['upgrade_url'] ) ) {170 echo '<a class="wptk-hub-card-upgrade" href="' . esc_url( $module['upgrade_url'] ) . '" target="_blank" rel="noopener noreferrer">';171 echo esc_html( $module['get_pro_label'] );172 echo '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true"><path d="M7 17L17 7"/><path d="M7 7h10v10"/></svg>';173 echo '</a>';174 }175 }176 177 156 echo '</div>'; 178 157 … … 180 159 if ( ! empty( $module['description'] ) ) { 181 160 echo '<p class="wptk-hub-card-desc">' . esc_html( $module['description'] ) . '</p>'; 182 }183 184 if ( ! empty( $module['has_upsell'] ) && empty( $module['license_active'] ) ) {185 echo '<span class="st st--closed">' . esc_html__( 'Free', 'inquiryflow' ) . '</span>';186 161 } 187 162 echo '</div>'; -
inquiryflow/trunk/core/src/Admin/LegacyRedirects.php
r3670262 r3696831 25 25 * @var array<string, string> 26 26 */ 27 private static $map = array( 28 'wptk-pro-license' => 'wptk-kit-licenses', 29 ); 27 private static $map = array(); 30 28 31 29 /** … … 55 53 56 54 $target = self::$map[ $page ]; 57 $args = array( 'page' => $target ); 58 59 if ( 'wptk-pro-license' === $page || LicenseCenterPage::SLUG === $target ) { 60 if ( function_exists( 'wptk_core_booted' ) && wptk_core_booted() ) { 61 $registry = wptk_core_instance()->container()->get( 'license_registry' ); 62 if ( ! $registry->has_products() ) { 63 wp_safe_redirect( AdminUrls::home() ); 64 exit; 65 } 66 } 67 } 68 69 wp_safe_redirect( add_query_arg( $args, admin_url( 'admin.php' ) ) ); 55 wp_safe_redirect( add_query_arg( array( 'page' => $target ), admin_url( 'admin.php' ) ) ); 70 56 exit; 71 57 } -
inquiryflow/trunk/core/src/Admin/Menu.php
r3670262 r3696831 53 53 * Constructor. 54 54 * 55 * @param \WPTravelKit\Suite\Core|null $core Optional core instance for license lookups.55 * @param \WPTravelKit\Suite\Core|null $core Optional core instance. 56 56 */ 57 57 public function __construct( $core = null ) { … … 81 81 array( 82 82 'order' => ( count( $this->submenus ) + 1 ) * 10, 83 'pro' => false,84 83 'section' => self::SECTION_PRODUCTS, 85 84 'product' => '', … … 98 97 'callback' => $callback, 99 98 'order' => (int) $args['order'], 100 'pro' => ! empty( $args['pro'] ),101 99 'section' => (string) $args['section'], 102 100 'product' => (string) $args['product'], … … 149 147 150 148 add_action( 'admin_menu', array( $this, 'reorder_submenu' ), 9999 ); 151 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_menu_styles' ) );152 149 153 150 add_action( … … 200 197 201 198 /** 202 * Build sidebar menu label , optionally with PRO badge.199 * Build sidebar menu label. 203 200 * 204 201 * @param array<string, mixed> $item Submenu config. … … 206 203 */ 207 204 private function menu_label( array $item ) { 208 $label = $item['title']; 209 210 $show_pro = ! empty( $item['pro'] ); 211 212 if ( ! $show_pro && ! empty( $item['product'] ) && $this->core ) { 213 $registry = $this->core->container()->get( 'license_registry' ); 214 if ( $registry && $registry->has_product( $item['product'] ) && ! $registry->is_active( $item['product'] ) ) { 215 $show_pro = true; 216 } 217 } 218 219 if ( $show_pro ) { 220 $label .= ' <span class="wptk-ib-menu-pro">PRO</span>'; 221 } 222 223 return $label; 205 return $item['title']; 224 206 } 225 207 … … 244 226 245 227 /** 246 * Reorder submenu items and ensure PRO badges render.228 * Reorder submenu items. 247 229 */ 248 230 public function reorder_submenu() { … … 276 258 $submenu[ self::SLUG ] = $ordered; 277 259 } 278 }279 280 /**281 * PRO badge styles for the admin sidebar.282 */283 public function enqueue_menu_styles() {284 $css = '285 #adminmenu .wp-submenu a .wptk-ib-menu-pro {286 display: inline-block;287 margin-left: 6px;288 padding: 1px 6px;289 font-size: 10px;290 font-weight: 600;291 line-height: 1.5;292 border-radius: 4px;293 vertical-align: middle;294 letter-spacing: 0.04em;295 background: var(--wp-admin-theme-color, #2271b1);296 color: #fff;297 }298 #adminmenu .wp-submenu li.current a .wptk-ib-menu-pro,299 #adminmenu .wp-submenu li a:focus .wptk-ib-menu-pro,300 #adminmenu .wp-submenu li a:hover .wptk-ib-menu-pro {301 background: #fff;302 color: var(--wp-admin-theme-color, #2271b1);303 }304 ';305 306 wp_register_style( 'wptk-ib-admin-menu', false, array(), defined( 'WPTK_SUITE_VERSION' ) ? WPTK_SUITE_VERSION : '1.0.0' );307 wp_enqueue_style( 'wptk-ib-admin-menu' );308 wp_add_inline_style( 'wptk-ib-admin-menu', $css );309 260 } 310 261 -
inquiryflow/trunk/core/src/Admin/SettingsShell.php
r3670262 r3696831 98 98 */ 99 99 public function render() { 100 $panels = $this->core->container()->get( 'settings_panels' )->all(); 101 $active = $this->active_product(); 102 $license = $this->core->container()->get( 'license_registry' ); 100 $panels = $this->core->container()->get( 'settings_panels' )->all(); 101 $active = $this->active_product(); 103 102 104 103 echo '<div class="wrap wptk-ib-admin-wrap wptk-settings-shell" style="padding-top:12px">'; … … 110 109 $classes = 'ib-btn' . ( $module_id === $active ? ' ib-btn--primary' : ' ib-btn--secondary' ); 111 110 $label = (string) ( $panel['label'] ?? $module_id ); 112 $is_pro = ! empty( $panel['is_pro'] );113 $lic_id = (string) ( $panel['license_id'] ?? '' );114 $locked = $is_pro && '' !== $lic_id && $license->has_product( $lic_id ) && ! $license->is_active( $lic_id );115 111 116 112 echo '<a class="' . esc_attr( $classes ) . '" href="' . esc_url( $url ) . '">'; 117 113 echo esc_html( $label ); 118 if ( $locked ) {119 echo ' <span class="wptk-ib-menu-pro">PRO</span>';120 }121 114 echo '</a>'; 122 115 } -
inquiryflow/trunk/core/src/Admin/ShellAdminNotices.php
r3670262 r3696831 2 2 /** 3 3 * Relocate WordPress admin notices into the suite shell chrome. 4 * 5 * On suite shell screens only, `admin_notices` / `all_admin_notices` callbacks are 6 * temporarily moved out of `$wp_filter` and re-fired inside AdminShell so notices 7 * appear in product chrome. They are not suppressed site-wide. A shutdown handler 8 * restores / re-fires captured hooks if the shell never rendered. 4 9 * 5 10 * This file is part of the shared WP Travel Kit suite layer bundled into free suite plugins. … … 36 41 37 42 /** 43 * Whether captured notice hooks were already re-fired (shell or shutdown). 44 * 45 * @var bool 46 */ 47 private static $fired = false; 48 49 /** 38 50 * Register hooks. 39 51 */ … … 41 53 add_action( 'current_screen', array( __CLASS__, 'maybe_relocate' ), 0 ); 42 54 add_filter( 'admin_body_class', array( __CLASS__, 'body_class' ) ); 55 add_action( 'shutdown', array( __CLASS__, 'maybe_restore_on_shutdown' ), 0 ); 43 56 } 44 57 … … 57 70 if ( isset( $wp_filter['admin_notices'] ) ) { 58 71 self::$admin_notices = $wp_filter['admin_notices']; 72 // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Suite shell screens only: relocate notices into AdminShell, re-fired via render()/shutdown. 59 73 unset( $wp_filter['admin_notices'] ); 60 74 } … … 62 76 if ( isset( $wp_filter['all_admin_notices'] ) ) { 63 77 self::$all_admin_notices = $wp_filter['all_admin_notices']; 78 // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Suite shell screens only: relocate notices into AdminShell, re-fired via render()/shutdown. 64 79 unset( $wp_filter['all_admin_notices'] ); 65 80 } … … 124 139 } 125 140 126 if ( HubPage::SLUG === $page || LicenseCenterPage::SLUG === $page ||SettingsShell::SLUG === $page ) {141 if ( HubPage::SLUG === $page || SettingsShell::SLUG === $page ) { 127 142 return true; 128 143 } … … 165 180 */ 166 181 public static function render() { 167 if ( ! self::$relocated ) { 168 return; 169 } 170 182 if ( ! self::$relocated || self::$fired ) { 183 return; 184 } 185 186 self::$fired = true; 187 188 $html = self::capture_notices_html(); 189 if ( '' === trim( $html ) ) { 190 return; 191 } 192 193 echo wp_kses_post( $html ); 194 } 195 196 /** 197 * If the shell never re-fired notices, re-fire from the captured hooks. 198 * 199 * Prevents a silent drop of other plugins' notices when a suite screen relocates 200 * hooks but exits before AdminShell::render(). Does not write back to `$wp_filter`. 201 */ 202 public static function maybe_restore_on_shutdown() { 203 if ( ! self::$relocated || self::$fired ) { 204 return; 205 } 206 207 // Normal admin_notices pass already ran empty — re-fire unless this is a non-HTML exit. 208 $should_refire = did_action( 'admin_notices' ) || did_action( 'all_admin_notices' ); 209 if ( ! $should_refire ) { 210 self::$fired = true; 211 return; 212 } 213 214 if ( wp_doing_ajax() || ( function_exists( 'wp_is_json_request' ) && wp_is_json_request() ) ) { 215 self::$fired = true; 216 return; 217 } 218 219 $status = (int) http_response_code(); 220 if ( $status >= 300 && $status < 400 ) { 221 self::$fired = true; 222 return; 223 } 224 225 self::$fired = true; 226 227 $html = self::capture_notices_html(); 228 if ( '' === trim( $html ) ) { 229 return; 230 } 231 232 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped via wp_kses_post. 233 echo wp_kses_post( $html ); 234 } 235 236 /** 237 * Run captured notice hooks and return buffered HTML. 238 * 239 * @return string 240 */ 241 private static function capture_notices_html() { 171 242 ob_start(); 172 243 … … 179 250 } 180 251 181 $html = (string) ob_get_clean(); 182 if ( '' === trim( $html ) ) { 183 return; 184 } 185 186 echo wp_kses_post( $html ); 252 return (string) ob_get_clean(); 187 253 } 188 254 } -
inquiryflow/trunk/core/src/Admin/WorkspaceRegistry.php
r3670262 r3696831 44 44 ); 45 45 46 // Store base tabs only. Apply `wptk_workspace_tabs` at render/enqueue 47 // time in WorkspaceShell so Pro (and other) filters are not baked in twice. 46 48 $this->workspaces[ $product_id ] = $args; 47 48 /**49 * Filter workspace tabs for a product.50 *51 * @param array<int, array<string, mixed>> $tabs Tabs.52 * @param string $product_id Product id.53 */54 $this->workspaces[ $product_id ]['tabs'] = apply_filters( 'wptk_workspace_tabs', $args['tabs'], $product_id );55 49 } 56 50 -
inquiryflow/trunk/core/src/Admin/WorkspaceShell.php
r3670262 r3696831 82 82 $product_label = (string) ( $workspace['label'] ?? $product_id ); 83 83 $page_title = $this->tab_label( $tabs, $active_tab ); 84 $license_id = AdminShell::workspace_license_id( $product_id );85 $upsell = $this->core->container()->get( 'upsell_registry' )->resolve( $this->core, $product_id, $product_label );86 84 87 85 $shell = $this->core->container()->get( 'admin_shell' ); … … 93 91 'product_name' => $product_label, 94 92 'page_title' => $page_title, 95 'license_id' => $license_id,96 'show_upsell' => $upsell['show'],97 'upsell_key' => $product_id,98 'upgrade_url' => $upsell['upgrade_url'],99 'upsell_message' => $upsell['upsell_message'],100 'upsell_cta' => $upsell['upsell_cta'],101 93 'tabs' => $tabs, 102 94 'active_tab' => $active_tab, … … 143 135 } 144 136 145 if ( ! empty( $tab['pro'] ) && ! $this->core->container()->get( 'license' )->is_pro_active() ) {146 $license_id = AdminShell::workspace_license_id( $product_id );147 echo '<div class="ib-card" style="padding:20px">';148 echo '<p style="margin:0 0 10px">';149 esc_html_e( 'Activate the Pro license to use this feature.', 'inquiryflow' );150 echo '</p>';151 echo '<a class="ib-btn ib-btn--primary" href="' . esc_url( AdminUrls::licenses( $license_id ) ) . '">';152 esc_html_e( 'Manage licenses', 'inquiryflow' );153 echo '</a></div>';154 $rendered = true;155 break;156 }157 158 137 if ( ! empty( $tab['render'] ) && is_callable( $tab['render'] ) ) { 159 138 call_user_func( $tab['render'] ); -
inquiryflow/trunk/core/src/Core.php
r3670262 r3696831 17 17 use WPTravelKit\Suite\Admin\HubPage; 18 18 use WPTravelKit\Suite\Admin\LegacyRedirects; 19 use WPTravelKit\Suite\Admin\LicenseCenterPage;20 19 use WPTravelKit\Suite\Admin\Menu; 21 20 use WPTravelKit\Suite\Admin\SettingsShell; 22 21 use WPTravelKit\Suite\Admin\ShellAdminNotices; 23 22 use WPTravelKit\Suite\Admin\WorkspaceRegistry; 24 use WPTravelKit\Suite\Admin\UpsellRegistry;25 23 use WPTravelKit\Suite\Admin\WorkspaceShell; 26 use WPTravelKit\Suite\Licensing\LicenseAdapter;27 use WPTravelKit\Suite\Licensing\LicenseRegistry;28 24 use WPTravelKit\Suite\Modules\AdminBootstrap; 29 25 use WPTravelKit\Suite\Modules\ModuleRegistry; 30 26 use WPTravelKit\Suite\Onboarding\Onboarding; 31 use WPTravelKit\Suite\Rest\LicensesController;32 27 use WPTravelKit\Suite\Rest\Server; 33 28 use WPTravelKit\Suite\Settings\Settings; … … 86 81 } 87 82 88 $this->container->get( 'rest' )->register_controller( new LicensesController( $this ) );89 83 $this->container->get( 'rest' )->init(); 90 84 … … 157 151 158 152 $this->container->set( 159 'license_registry',160 function () {161 return new LicenseRegistry();162 }163 );164 165 $this->container->set(166 153 'rest', 167 154 function () { … … 195 182 196 183 $this->container->set( 197 'upsell_registry',198 function () {199 return new UpsellRegistry();200 }201 );202 203 $this->container->set(204 184 'admin_shell', 205 185 function ( $c ) use ( $core ) { … … 222 202 unset( $c ); 223 203 return new SettingsShell( $core ); 224 }225 );226 227 $this->container->set(228 'license_center',229 function ( $c ) use ( $core ) {230 unset( $c );231 return new LicenseCenterPage( $core );232 204 } 233 205 ); … … 262 234 } 263 235 ); 264 265 $this->container->set(266 'license',267 function ( $c ) {268 return new LicenseAdapter( $c->get( 'license_registry' ) );269 }270 );271 236 } 272 237 … … 289 254 290 255 $this->container->get( 'hub_page' )->register_menu( $menu ); 291 $this->container->get( 'license_center' )->register_menu( $menu );292 256 293 257 /** -
inquiryflow/trunk/core/src/Modules/AdminBootstrap.php
r3670262 r3696831 66 66 } 67 67 68 if ( ! empty( $admin['upsell'] ) && is_array( $admin['upsell'] ) ) {69 $upsell = wp_parse_args(70 $admin['upsell'],71 array(72 'cta' => __( 'Upgrade to Pro', 'inquiryflow' ),73 'license_id' => '',74 )75 );76 $this->core->container()->get( 'upsell_registry' )->register( $manifest->id, $upsell );77 }78 79 if ( ! empty( $admin['license'] ) && is_array( $admin['license'] ) ) {80 $license = $admin['license'];81 if ( ! empty( $license['product_id'] ) && ! empty( $license['provider'] ) ) {82 $this->core->container()->get( 'license_registry' )->register(83 (string) $license['product_id'],84 array(85 'name' => (string) ( $license['name'] ?? $manifest->name ),86 'provider' => $license['provider'],87 'order' => (int) ( $license['order'] ?? 10 ),88 )89 );90 }91 }92 93 68 if ( ! empty( $admin['menu'] ) && is_array( $admin['menu'] ) && ! empty( $admin['workspace']['slug'] ) ) { 94 69 $menu_args = wp_parse_args( -
inquiryflow/trunk/core/src/Scheduler/ActionSchedulerLoader.php
r3670262 r3696831 3 3 * Background job scheduler (WP Cron by default). 4 4 * 5 * Free/org plugins schedule via WP Cron only. Premium pluginsmay call load()6 * to bundle Action Scheduler from theirown libraries/action-scheduler/ path;5 * Suite plugins schedule via WP Cron by default. A separate plugin may call load() 6 * to bundle Action Scheduler from its own libraries/action-scheduler/ path; 7 7 * schedule() and schedule_recurring() will use AS when those APIs are available. 8 8 * … … 25 25 26 26 /** 27 * Action Scheduler group slug for premium /external AS usage.27 * Action Scheduler group slug for external AS usage. 28 28 */ 29 29 const GROUP = 'wptk'; 30 30 31 31 /** 32 * Load Action Scheduler from a p remium plugin path.32 * Load Action Scheduler from a plugin path that bundles it. 33 33 * 34 34 * No-op when Action Scheduler is already available (e.g. WooCommerce). -
inquiryflow/trunk/core/src/Settings/SettingsPanelRegistry.php
r3670262 r3696831 35 35 $args, 36 36 array( 37 'label' => $module_id, 38 'order' => ( count( $this->panels ) + 1 ) * 10, 39 'is_pro' => false, 40 'nav' => array(), 41 'boot' => null, 37 'label' => $module_id, 38 'order' => ( count( $this->panels ) + 1 ) * 10, 39 'nav' => array(), 40 'boot' => null, 42 41 ) 43 42 ); -
inquiryflow/trunk/core/version.php
r3670262 r3696831 13 13 14 14 if ( ! defined( 'WPTK_SUITE_VERSION' ) ) { 15 define( 'WPTK_SUITE_VERSION', '1.0. 3' );15 define( 'WPTK_SUITE_VERSION', '1.0.7' ); 16 16 } -
inquiryflow/trunk/core/wptk-core-shim.php
r3670262 r3696831 1 1 <?php 2 defined( 'ABSPATH' ) || exit; 3 2 4 // phpcs:ignoreFile -- Shared suite bootstrap API; wptk_ globals and hooks are intentional cross-plugin surface. 3 5 /** -
inquiryflow/trunk/inquiryflow.php
r3670262 r3696831 4 4 * Plugin URI: https://wptravelkit.com/wordpress-travel-plugins/inquiryflow/ 5 5 * Description: Convert more visitors into qualified travel inquiries. Floating inquiry button, WhatsApp, quick popup, and inquiry dashboard for WP Travel Engine sites. 6 * Version: 1.0. 46 * Version: 1.0.5 7 7 * Author: WP Travel Kit 8 8 * Author URI: https://wptravelkit.com/ … … 12 12 * Domain Path: /languages 13 13 * Requires at least: 6.0 14 * Tested up to: 7. 014 * Tested up to: 7.1 15 15 * Requires PHP: 7.4 16 16 * … … 24 24 } 25 25 26 define( 'INQUIRYFLOW_VERSION', '1.0. 4' );26 define( 'INQUIRYFLOW_VERSION', '1.0.5' ); 27 27 define( 'INQUIRYFLOW_PLUGIN_FILE', __FILE__ ); 28 28 define( 'INQUIRYFLOW_PLUGIN_PATH', plugin_dir_path( __FILE__ ) ); -
inquiryflow/trunk/modules/inquiryflow/assets/build/settings-admin.js
r3608474 r3696831 104 104 var _e = useState('button'), activeNav = _e[0], setActiveNav = _e[1]; 105 105 var _f = useState(''), secretDraft = _f[0], setSecretDraft = _f[1]; 106 var _f2 = useState(''), recaptchaSecretDraft = _f2[0], setRecaptchaSecretDraft = _f2[1]; 106 107 var _g = useState(false), testSending = _g[0], setTestSending = _g[1]; 107 108 var _h = useState(''), testNotice = _h[0], setTestNotice = _h[1]; … … 196 197 } 197 198 199 if (recaptchaSecretDraft) { 200 payload.inquiryflow.recaptcha_secret = recaptchaSecretDraft; 201 } 202 198 203 if (markOnboarding) { 199 204 payload.mark_onboarding_complete = true; … … 211 216 setData(res.data); 212 217 setSecretDraft(''); 218 setRecaptchaSecretDraft(''); 213 219 showNotice('Settings saved.', true); 214 220 } else { … … 599 605 onChange: function (e) { 600 606 var value = e.target.value; 601 if (!isPro && value === 'recaptcha') {602 return;603 }604 607 setIb('captcha_provider', value); 605 608 boolSet('hcaptcha_enabled', value === 'hcaptcha'); … … 609 612 { id: 'none', label: 'None' }, 610 613 { id: 'hcaptcha', label: 'hCaptcha' }, 614 { id: 'recaptcha', label: 'Google reCAPTCHA' }, 611 615 ]).map(function (option) { 612 616 var locked = !!option.pro && !isPro; … … 640 644 onChange: function (e) { setSecretDraft(e.target.value); }, 641 645 placeholder: ib.hcaptcha_secret_set ? '••••••••••••' : '', 646 autoComplete: 'new-password', 647 }) 648 ) 649 ) : null, 650 captchaProviderValue(ib) === 'recaptcha' ? el(Fragment, null, 651 el(Field, { id: 'recaptcha_site_key', label: 'reCAPTCHA site key' }, 652 el('input', { 653 id: 'recaptcha_site_key', 654 className: 'ib-input', 655 value: ib.recaptcha_site_key || '', 656 onChange: function (e) { setIb('recaptcha_site_key', e.target.value); }, 657 }) 658 ), 659 el(Field, { 660 id: 'recaptcha_secret', 661 label: 'reCAPTCHA secret key', 662 help: ib.recaptcha_secret_set ? 'A secret is saved. Enter a new value only to replace it.' : 'Stored securely on the server; never shown after saving.', 663 }, 664 el('input', { 665 id: 'recaptcha_secret', 666 type: 'password', 667 className: 'ib-input', 668 value: recaptchaSecretDraft, 669 onChange: function (e) { setRecaptchaSecretDraft(e.target.value); }, 670 placeholder: ib.recaptcha_secret_set ? '••••••••••••' : '', 642 671 autoComplete: 'new-password', 643 672 }) -
inquiryflow/trunk/modules/inquiryflow/src/Admin/Dashboard.php
r3608474 r3696831 86 86 ); 87 87 88 $upsell_registry = $this->core->container()->get( 'upsell_registry' );89 $upsell = $upsell_registry->resolve( $this->core, 'inquiryflow', 'InquiryFlow' );90 91 88 $localize = array( 92 89 'restUrl' => rest_url( 'inquiryflow/v1/' ), 93 90 'nonce' => wp_create_nonce( 'wp_rest' ), 94 'isPro' => $upsell_registry->is_pro_active( $this->core, 'inquiryflow-pro' ),95 'upgradeUrl' => $upsell['upgrade_url'],96 91 'upgradeDismissed' => $this->core->container()->get( 'settings' )->get( 'inquiryflow', 'upgrade_dismissed', '0' ), 97 92 'columns' => Hooks::admin_lead_columns(), -
inquiryflow/trunk/modules/inquiryflow/src/Admin/SettingsIntegration.php
r3608474 r3696831 125 125 ); 126 126 127 $upsell_registry = $this->core->container()->get( 'upsell_registry' );128 $upsell = $upsell_registry->resolve( $this->core, 'inquiryflow', 'InquiryFlow' );129 130 127 $localize = array( 131 'restUrl' => rest_url( 'inquiryflow/v1/' ), 132 'nonce' => wp_create_nonce( 'wp_rest' ), 133 'isPro' => $upsell_registry->is_pro_active( $this->core, 'inquiryflow-pro' ), 134 'upgradeUrl' => $upsell['upgrade_url'], 135 'extraNav' => array(), 136 'productId' => 'inquiryflow', 128 'restUrl' => rest_url( 'inquiryflow/v1/' ), 129 'nonce' => wp_create_nonce( 'wp_rest' ), 130 'extraNav' => array(), 131 'productId' => 'inquiryflow', 137 132 ); 138 133 … … 176 171 unset( $values['hcaptcha_secret_set'] ); 177 172 173 if ( array_key_exists( 'recaptcha_secret', $values ) ) { 174 $secret = is_string( $values['recaptcha_secret'] ) ? sanitize_text_field( trim( $values['recaptcha_secret'] ) ) : ''; 175 if ( '' !== $secret ) { 176 $settings->update_module( 'inquiryflow', array( 'recaptcha_secret' => $secret ) ); 177 } 178 unset( $values['recaptcha_secret'] ); 179 } 180 181 unset( $values['recaptcha_secret_set'] ); 182 178 183 if ( isset( $values['captcha_provider'] ) ) { 179 184 $provider = Captcha::sanitize_provider( $values['captcha_provider'] ); 180 if ( 'recaptcha' === $provider && ! $this->core->container()->get( 'license' )->is_pro_active() ) {181 $provider = 'none';182 }183 185 $values['captcha_provider'] = $provider; 184 186 $values['hcaptcha_enabled'] = ( 'hcaptcha' === $provider ) ? '1' : '0'; … … 213 215 */ 214 216 private function get_meta() { 215 $wpte = $this->core->container()->get( ' wpte' );217 $wpte = $this->core->container()->get( 'inquiryflow_wpte' ); 216 218 $trip = $this->get_sample_trip(); 217 219 $privacy = admin_url( 'export-personal-data.php' ); … … 222 224 $provider = Captcha::get_provider( $this->core, $ib_settings ); 223 225 if ( 'none' !== $provider ) { 224 $site_key = (string) ( $ib_settings['hcaptcha_site_key'] ?? '' ); 225 $secret = (string) $settings->get( 'inquiryflow', 'hcaptcha_secret', '' ); 226 if ( 'hcaptcha' === $provider && ( '' === $site_key || '' === $secret ) ) { 227 $captcha_warning = __( 'hCaptcha is enabled but the site key or secret is missing. Submissions will be blocked until both are configured.', 'inquiryflow' ); 226 if ( 'hcaptcha' === $provider ) { 227 $site_key = (string) ( $ib_settings['hcaptcha_site_key'] ?? '' ); 228 $secret = (string) $settings->get( 'inquiryflow', 'hcaptcha_secret', '' ); 229 if ( '' === $site_key || '' === $secret ) { 230 $captcha_warning = __( 'hCaptcha is enabled but the site key or secret is missing. Submissions will be blocked until both are configured.', 'inquiryflow' ); 231 } 232 } elseif ( 'recaptcha' === $provider ) { 233 $site_key = (string) ( $ib_settings['recaptcha_site_key'] ?? '' ); 234 $secret = (string) $settings->get( 'inquiryflow', 'recaptcha_secret', '' ); 235 if ( '' === $secret ) { 236 $secret = (string) $settings->get( 'inquiryflow_pro', 'recaptcha_secret', '' ); 237 } 238 if ( '' === $site_key ) { 239 $pro = $settings->get_module( 'inquiryflow_pro' ); 240 $site_key = is_array( $pro ) ? (string) ( $pro['recaptcha_site_key'] ?? '' ) : ''; 241 } 242 if ( '' === $site_key || '' === $secret ) { 243 $captcha_warning = __( 'reCAPTCHA is enabled but the site key or secret is missing. Submissions will be blocked until both are configured.', 'inquiryflow' ); 244 } 228 245 } 229 246 } -
inquiryflow/trunk/modules/inquiryflow/src/Email/Notifications.php
r3608474 r3696831 41 41 */ 42 42 public function send_admin_email( $lead_id ) { 43 $lead = $this->core->container()->get( ' leads' )->get( absint( $lead_id ) );43 $lead = $this->core->container()->get( 'inquiryflow_leads' )->get( absint( $lead_id ) ); 44 44 if ( ! $lead ) { 45 45 return; -
inquiryflow/trunk/modules/inquiryflow/src/Frontend/Frontend.php
r3608474 r3696831 110 110 111 111 $settings = Hooks::filter_settings( $this->core->container()->get( 'settings' )->get_module( 'inquiryflow' ) ); 112 $trip = $this->core->container()->get( ' wpte' )->get_trip_context();112 $trip = $this->core->container()->get( 'inquiryflow_wpte' )->get_trip_context(); 113 113 114 114 wp_enqueue_style( -
inquiryflow/trunk/modules/inquiryflow/src/Module.php
r3670262 r3696831 18 18 use InquiryFlow\Rest\LeadsController; 19 19 use InquiryFlow\Shortcodes\Shortcodes; 20 use WPTravelKit\Suite\Admin\AdminUrls;21 20 use WPTravelKit\Suite\Admin\Menu; 22 21 use WPTravelKit\Suite\Autoloader; … … 39 38 private static $registered = false; 40 39 41 /** 42 * Checkout URL for InquiryFlow Pro (defined in the free plugin). 43 * 44 * @return string 45 */ 46 public static function pro_upgrade_url() { 47 return apply_filters( 'inquiryflow_upgrade_url', 'https://wptravelkit.com/wordpress-travel-plugins/inquiryflow/' ); 48 } 49 50 /** 51 * Upsell copy for admin surfaces (workspace bar, home card, settings). 52 * 53 * @return string 54 */ 55 public static function pro_upsell_message() { 56 return apply_filters( 57 'inquiryflow_upsell_message', 58 __( 'You\'re using InquiryFlow Free. To unlock more features, consider upgrading to Pro', 'inquiryflow' ) 59 ); 60 } 61 62 /** 63 * Init module on suite loaded. 64 */ 40 /** 41 * Checkout URL for InquiryFlow Pro (optional helper; not wired to suite upsell). 42 * 43 * @return string 44 */ 45 public static function pro_upgrade_url() { 46 return apply_filters( 'inquiryflow_upgrade_url', 'https://wptravelkit.com/wordpress-travel-plugins/inquiryflow/' ); 47 } 48 49 /** 50 * Init module on suite loaded. 51 */ 65 52 public static function init() { 66 53 self::register_autoloaders(); … … 124 111 $core->modules()->register( $manifest ); 125 112 $core->container()->get( 'settings_registry' )->register( 'inquiryflow', self::settings_schema() ); 126 SecretStorage::register( 'inquiryflow', array( 'hcaptcha_secret' ) );113 SecretStorage::register( 'inquiryflow', array( 'hcaptcha_secret', 'recaptcha_secret' ) ); 127 114 128 115 $core->container()->get( 'settings_panels' )->register( 129 116 'inquiryflow', 130 117 array( 131 'label' => __( 'InquiryFlow', 'inquiryflow' ), 132 'order' => 10, 133 'license_id' => 'inquiryflow-pro', 134 'render' => function () { 118 'label' => __( 'InquiryFlow', 'inquiryflow' ), 119 'order' => 10, 120 'render' => function () { 135 121 printf( 136 122 '<div id="%1$s" class="%2$s"></div>', … … 141 127 ) 142 128 ); 143 144 $core->container()->get( 'upsell_registry' )->register(145 'inquiryflow',146 array(147 'url' => self::pro_upgrade_url(),148 'message' => self::pro_upsell_message(),149 'cta' => __( 'Upgrade to Pro', 'inquiryflow' ),150 'license_id' => 'inquiryflow-pro',151 )152 );153 129 } 154 130 … … 236 212 }, 237 213 ), 214 'recaptcha_site_key' => array( 'default' => '' ), 215 'recaptcha_secret' => array( 216 'default' => '', 217 'sanitize' => function ( $v ) { 218 $secret = sanitize_text_field( (string) $v ); 219 return strlen( $secret ) > 255 ? substr( $secret, 0, 255 ) : $secret; 220 }, 221 ), 238 222 'upgrade_dismissed' => array( 239 223 'default' => '0', … … 261 245 */ 262 246 private static function boot( $core ) { 263 add_filter(264 'wptk_workspace_license_id',265 function ( $license_id, $product_id ) {266 if ( 'inquiryflow' === $product_id ) {267 return 'inquiryflow-pro';268 }269 return $license_id;270 },271 10,272 2273 );274 275 247 add_filter( 'inquiryflow_sanitize_inquiry_meta', array( Security\SubmissionValidator::class, 'sanitize_meta' ) ); 276 248 add_filter( 'inquiryflow_sanitize_lead_source', array( Security\SubmissionValidator::class, 'sanitize_source' ) ); … … 278 250 add_filter( 'inquiryflow_sanitize_lead_source_url', array( Security\SubmissionValidator::class, 'sanitize_source_url' ) ); 279 251 280 $product_rest = $core->container()->get( ' product_rest' );252 $product_rest = $core->container()->get( 'inquiryflow_rest' ); 281 253 $product_rest->register_controller( new LeadsController( $core ) ); 282 254 $product_rest->register_controller( new AnalyticsController( $core ) ); … … 320 292 array( 321 293 'section' => Menu::SECTION_PRODUCTS, 322 'product' => 'inquiryflow-pro',323 294 'order' => 10, 324 295 ) -
inquiryflow/trunk/modules/inquiryflow/src/ProductBootstrap.php
r3608474 r3696831 42 42 self::$registered = true; 43 43 $core->container()->set( 44 ' leads',44 'inquiryflow_leads', 45 45 function () { 46 46 return new LeadsStore(); … … 49 49 50 50 $core->container()->set( 51 ' analytics',51 'inquiryflow_analytics', 52 52 function ( $c ) { 53 return new Analytics( $c->get( ' leads' ) );53 return new Analytics( $c->get( 'inquiryflow_leads' ) ); 54 54 } 55 55 ); 56 56 57 57 $core->container()->set( 58 ' wpte',58 'inquiryflow_wpte', 59 59 function () { 60 60 return new WpteAdapter(); … … 63 63 64 64 $core->container()->set( 65 ' privacy',65 'inquiryflow_privacy', 66 66 function ( $c ) { 67 return new PrivacyExporter( $c->get( ' leads' ) );67 return new PrivacyExporter( $c->get( 'inquiryflow_leads' ) ); 68 68 } 69 69 ); 70 70 71 71 $core->container()->set( 72 ' product_rest',72 'inquiryflow_rest', 73 73 function () { 74 74 return new ProductRestServer(); … … 80 80 $runner->run(); 81 81 82 $core->container()->get( ' privacy' )->register();82 $core->container()->get( 'inquiryflow_privacy' )->register(); 83 83 84 $product_rest = $core->container()->get( ' product_rest' );84 $product_rest = $core->container()->get( 'inquiryflow_rest' ); 85 85 $product_rest->register_controller( new ProductSettingsController( $core ) ); 86 86 $product_rest->init(); … … 99 99 100 100 $core = wptk_core_instance(); 101 if ( ! $core || ! $core->container()->has( 'settings' ) || ! $core->container()->has( ' leads' ) ) {101 if ( ! $core || ! $core->container()->has( 'settings' ) || ! $core->container()->has( 'inquiryflow_leads' ) ) { 102 102 return; 103 103 } … … 105 105 $settings = $core->container()->get( 'settings' ); 106 106 $months = (int) $settings->get( 'inquiryflow', 'retention_months', 24 ); 107 $core->container()->get( ' leads' )->purge_expired( $months );107 $core->container()->get( 'inquiryflow_leads' )->purge_expired( $months ); 108 108 } 109 109 } -
inquiryflow/trunk/modules/inquiryflow/src/Rest/AnalyticsController.php
r3608474 r3696831 67 67 public function get_analytics( $request ) { 68 68 $days = min( 365, max( 1, absint( $request->get_param( 'days' ) ?? 30 ) ) ); 69 $summary = $this->core->container()->get( ' analytics' )->get_summary( array( 'days' => $days ) );69 $summary = $this->core->container()->get( 'inquiryflow_analytics' )->get_summary( array( 'days' => $days ) ); 70 70 return $this->success( $summary ); 71 71 } -
inquiryflow/trunk/modules/inquiryflow/src/Rest/LeadsController.php
r3608474 r3696831 329 329 } 330 330 331 $lead = $this->core->container()->get( ' leads' )->create(331 $lead = $this->core->container()->get( 'inquiryflow_leads' )->create( 332 332 array( 333 333 'name' => $name, … … 349 349 } 350 350 351 $this->core->container()->get( ' analytics' )->record(351 $this->core->container()->get( 'inquiryflow_analytics' )->record( 352 352 'submit', 353 353 array( … … 370 370 */ 371 371 public function list_leads( $request ) { 372 $result = $this->core->container()->get( ' leads' )->query(372 $result = $this->core->container()->get( 'inquiryflow_leads' )->query( 373 373 array( 374 374 'search' => sanitize_text_field( $request->get_param( 'search' ) ?? '' ), … … 402 402 */ 403 403 public function get_lead( $request ) { 404 $lead = $this->core->container()->get( ' leads' )->get( absint( $request['id'] ) );404 $lead = $this->core->container()->get( 'inquiryflow_leads' )->get( absint( $request['id'] ) ); 405 405 if ( ! $lead ) { 406 406 return $this->error( 'inquiryflow_not_found', __( 'Inquiry not found.', 'inquiryflow' ), 404 ); 407 407 } 408 408 $data = $this->format_lead( $lead ); 409 $data['notes'] = $this->core->container()->get( ' leads' )->get_notes( $lead->get_id() );409 $data['notes'] = $this->core->container()->get( 'inquiryflow_leads' )->get_notes( $lead->get_id() ); 410 410 return $this->success( $data ); 411 411 } … … 427 427 } 428 428 429 $lead = $this->core->container()->get( ' leads' )->update_status( $id, $status );429 $lead = $this->core->container()->get( 'inquiryflow_leads' )->update_status( $id, $status ); 430 430 if ( ! $lead ) { 431 431 return $this->error( 'inquiryflow_not_found', __( 'Inquiry not found.', 'inquiryflow' ), 404 ); 432 432 } 433 $this->core->container()->get( ' analytics' )->record( 'status_change', array( 'lead_id' => $id ) );433 $this->core->container()->get( 'inquiryflow_analytics' )->record( 'status_change', array( 'lead_id' => $id ) ); 434 434 return $this->success( $this->format_lead( $lead ) ); 435 435 } … … 446 446 public function add_note( $request ) { 447 447 $lead_id = absint( $request['id'] ); 448 $lead = $this->core->container()->get( ' leads' )->get( $lead_id );448 $lead = $this->core->container()->get( 'inquiryflow_leads' )->get( $lead_id ); 449 449 if ( ! $lead ) { 450 450 return $this->error( 'inquiryflow_not_found', __( 'Inquiry not found.', 'inquiryflow' ), 404 ); … … 456 456 return $this->error( 'inquiryflow_validation', __( 'Note text required.', 'inquiryflow' ), 400 ); 457 457 } 458 $note_id = $this->core->container()->get( ' leads' )->add_note( $lead_id, $text );458 $note_id = $this->core->container()->get( 'inquiryflow_leads' )->add_note( $lead_id, $text ); 459 459 if ( ! $note_id ) { 460 460 return $this->error( 'inquiryflow_save_failed', __( 'Could not save note.', 'inquiryflow' ), 500 ); … … 471 471 public function export_csv( $request ) { 472 472 unset( $request ); 473 $result = $this->core->container()->get( ' leads' )->query(473 $result = $this->core->container()->get( 'inquiryflow_leads' )->query( 474 474 array( 475 475 'per_page' => 5000, … … 512 512 $params = $request->get_json_params(); 513 513 $event = sanitize_key( $params['event'] ?? '' ); 514 $this->core->container()->get( ' analytics' )->record(514 $this->core->container()->get( 'inquiryflow_analytics' )->record( 515 515 $event, 516 516 array( -
inquiryflow/trunk/modules/inquiryflow/src/Rest/SettingsController.php
r3608474 r3696831 165 165 if ( 'inquiryflow_pro' === $module_id ) { 166 166 do_action( 'inquiryflow_settings_api_before_save', $values, $this->core ); 167 168 if ( ! $this->core->container()->get( 'license' )->is_pro_active() ) {169 continue;170 }171 167 } 172 168 -
inquiryflow/trunk/modules/inquiryflow/src/Security/Captcha.php
r3608474 r3696831 40 40 41 41 if ( ! in_array( $provider, self::PROVIDERS, true ) ) { 42 $provider = 'none';43 }44 45 if ( 'recaptcha' === $provider && ! $core->container()->get( 'license' )->is_pro_active() ) {46 42 $provider = 'none'; 47 43 } … … 98 94 } 99 95 96 if ( 'recaptcha' === $provider ) { 97 return self::verify_recaptcha( $core, $token ); 98 } 99 100 100 return false; 101 101 } … … 153 153 if ( 'hcaptcha' === $provider ) { 154 154 $config['captchaSiteKey'] = (string) ( $settings['hcaptcha_site_key'] ?? '' ); 155 } elseif ( 'recaptcha' === $provider ) { 156 $config['captchaSiteKey'] = (string) ( $settings['recaptcha_site_key'] ?? '' ); 155 157 } 156 158 … … 187 189 'label' => __( 'hCaptcha', 'inquiryflow' ), 188 190 ), 191 array( 192 'id' => 'recaptcha', 193 'label' => __( 'Google reCAPTCHA', 'inquiryflow' ), 194 ), 189 195 ); 190 196 } … … 231 237 return ! empty( $body['success'] ); 232 238 } 239 240 /** 241 * Verify Google reCAPTCHA token. 242 * 243 * @param Core $core Core instance. 244 * @param string $token Token. 245 * @return bool 246 */ 247 private static function verify_recaptcha( Core $core, $token ) { 248 $secret = $core->container()->get( 'settings' )->get( 'inquiryflow', 'recaptcha_secret', '' ); 249 if ( empty( $secret ) && $core->container()->has( 'settings' ) ) { 250 // Legacy Pro storage fallback. 251 $secret = (string) $core->container()->get( 'settings' )->get( 'inquiryflow_pro', 'recaptcha_secret', '' ); 252 } 253 if ( empty( $secret ) ) { 254 return false; 255 } 256 257 $response = wp_remote_post( 258 'https://www.google.com/recaptcha/api/siteverify', 259 array( 260 'body' => array( 261 'secret' => $secret, 262 'response' => $token, 263 ), 264 ) 265 ); 266 267 if ( is_wp_error( $response ) ) { 268 return false; 269 } 270 271 $body = json_decode( wp_remote_retrieve_body( $response ), true ); 272 return ! empty( $body['success'] ); 273 } 233 274 } -
inquiryflow/trunk/readme.txt
r3670262 r3696831 6 6 Tested up to: 7.1 7 7 Requires PHP: 7.4 8 Stable tag: 1.0. 48 Stable tag: 1.0.5 9 9 License: GPLv2 or later 10 10 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 166 166 167 167 == Changelog == 168 = 1.0.5 = 169 * Fixed 'core' parity with other plugins in suite 170 168 171 = 1.0.4 = 169 172 * Compatibility update required by InquiryFlow Pro.
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)