Plugin Directory

Changeset 3696831


Ignore:
Timestamp:
09/15/2026 11:02:13 AM (8 weeks ago)
Author:
wptravelkit
Message:

Release InquiryFlow 1.0.5 — sync trunk with 1.0.7 core, drop unused suite licensing stubs

Location:
inquiryflow/trunk
Files:
6 deleted
34 edited

Legend:

Unmodified
Added
Removed
  • inquiryflow/trunk/core/assets/build/admin-shell.css

    r3608474 r3696831  
    1515}
    1616
    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 
    7517/* ---- Header ---- */
    7618.wptk-shell-header {
    … …  
    12466}
    12567
    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 }
    13668
    13769.wptk-shell-actions {
    … …  
    243175}
    244176
    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 }
    254177
    255178/* ---- Body ---- */
    … …  
    328251}
    329252
    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 }
    357253
    358254.wptk-hub-card-body {
    … …  
    436332}
    437333
    438 .wptk-license-stack {
    439         display: flex;
    440         flex-direction: column;
    441         gap: 16px;
    442         max-width: 640px;
    443 }
    444334
    445335/* Inquiries / workspace content spacing */
  • inquiryflow/trunk/core/assets/build/admin-shell.js

    r3608474 r3696831  
    11/**
    2  * WP Travel Kit — Admin shell (upsell dismiss, placeholders).
     2 * WP Travel Kit — Admin shell.
    33 */
    44(function () {
    55        '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         });
    266})();
  • inquiryflow/trunk/core/assets/src/admin-shell.css

    r3608474 r3696831  
    1515}
    1616
    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 
    7417/* ---- Header ---- */
    7518.wptk-shell-header {
    … …  
    12366}
    12467
    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 }
    13568
    13669.wptk-shell-actions {
    … …  
    242175}
    243176
    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 }
    253177
    254178/* ---- Body ---- */
    … …  
    327251}
    328252
    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 }
    356253
    357254.wptk-hub-card-body {
    … …  
    443340}
    444341
    445 .wptk-license-stack {
    446         display: flex;
    447         flex-direction: column;
    448         gap: 16px;
    449         max-width: 640px;
    450 }
    451342
    452343/* Inquiries / workspace content spacing */
  • inquiryflow/trunk/core/assets/src/admin-shell.js

    r3608474 r3696831  
    11/**
    2  * WP Travel Kit — Admin shell (upsell dismiss, placeholders).
     2 * WP Travel Kit — Admin shell.
    33 */
    44(function () {
    55        '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         });
    266})();
  • inquiryflow/trunk/core/assets/src/design-system/tokens.css

    r3608474 r3696831  
    1111  --brand-wash: #EAF1F8;         /* tinted surface */
    1212
    13   /* Accent (Pro / premium) */
     13  /* Accent */
    1414  --amber: #B45309;
    1515  --amber-wash: #FEF3E2;
    … …  
    170170.ib-chip--ghost { background: #fff; border-color: var(--hairline); color: var(--ink); }
    171171
    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 
    180172/* status chips */
    181173.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  
    11<?php
    22/**
    3  * Shared suite admin page chrome (upsell, header, tabs, centered body).
     3 * Shared suite admin page chrome (header, tabs, centered body).
    44 *
    55 * This file is part of the shared WP Travel Kit suite layer bundled into free suite plugins.
    … …  
    5050                                'product_name'   => self::SUITE_NAME,
    5151                                'page_title'     => '',
    52                                 'license_id'     => '',
    53                                 'show_upsell'    => false,
    54                                 'upsell_key'     => '',
    55                                 'upgrade_url'    => '',
    56                                 'upsell_message' => '',
    57                                 'upsell_cta'     => '',
    5852                                'tabs'           => array(),
    5953                                'active_tab'     => '',
    … …  
    6458
    6559                echo '<div class="wptk-admin-shell">';
    66 
    67                 if ( ! empty( $args['show_upsell'] ) ) {
    68                         $this->render_upsell( $args );
    69                 }
    7060
    7161                $this->render_header( $args );
    … …  
    8878                }
    8979
     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
    9088                call_user_func( $content );
    9189
    9290                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 bool
    100          */
    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">&times;</span>';
    145                 echo '</button>';
    146                 echo '</div></div>';
    14791        }
    14892
    … …  
    15599                $product    = (string) $args['product_name'];
    156100                $page_title = (string) $args['page_title'];
    157                 $license_id = (string) $args['license_id'];
    158                 $pro_active = $this->is_pro_badge_active( $license_id );
    159101
    160102                echo '<header class="wptk-shell-header">';
    … …  
    175117                }
    176118                echo '</div>';
    177 
    178                 if ( $pro_active ) {
    179                         echo '<span class="wptk-shell-pro-badge">' . esc_html__( 'PRO', 'inquiryflow' ) . '</span>';
    180                 }
    181119
    182120                echo '</div>';
    … …  
    212150                        $url       = add_query_arg( 'tab', $tab_id, admin_url( 'admin.php?page=' . $slug ) );
    213151                        $is_active = $tab_id === $active_tab;
    214                         $is_pro    = ! empty( $tab['pro'] );
    215152                        $classes   = 'wptk-shell-tab' . ( $is_active ? ' is-active' : '' );
    216153
    217154                        echo '<a class="' . esc_attr( $classes ) . '" href="' . esc_url( $url ) . '">';
    218155                        echo esc_html( $tab_label );
    219                         if ( $is_pro ) {
    220                                 echo ' <span class="wptk-shell-tab-pro">' . esc_html__( 'PRO', 'inquiryflow' ) . '</span>';
    221                         }
    222156                        echo '</a>';
    223157                }
    … …  
    225159                echo '</div></nav>';
    226160        }
    227 
    228         /**
    229          * Resolve license id for a workspace product.
    230          *
    231          * @param string $product_id Workspace product id.
    232          * @return string
    233          */
    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         }
    243161}
  • inquiryflow/trunk/core/src/Admin/AdminUrls.php

    r3670262 r3696831  
    2222        const HOME     = 'wptk-home';
    2323        const SETTINGS = 'wptk-kit-settings';
    24         const LICENSES = 'wptk-kit-licenses';
    2524
    2625        /**
    … …  
    5655
    5756        /**
    58          * License center URL.
    59          *
    60          * @param string $product_id Optional product anchor.
    61          * @return string
    62          */
    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         /**
    7257         * Product workspace URL.
    7358         *
  • inquiryflow/trunk/core/src/Admin/Assets.php

    r3670262 r3696831  
    7474
    7575        /**
    76          * Enqueue shared suite admin shell (tokens + chrome + dismiss script).
     76         * Enqueue shared suite admin shell (tokens + chrome).
    7777         */
    7878        public function enqueue_admin_shell() {
  • inquiryflow/trunk/core/src/Admin/HubPage.php

    r3670262 r3696831  
    6969                                'product_name' => AdminShell::SUITE_NAME,
    7070                                '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' ),
    7272                        ),
    7373                        function () use ( $modules ) {
    … …  
    103103         */
    104104        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' );
    108107
    109108                foreach ( $this->core->modules()->all() as $manifest ) {
    … …  
    112111                        }
    113112
    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'] : '';
    119115
    120116                        $description = (string) $manifest->description;
    … …  
    128124
    129125                        $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 ),
    141132                        );
    142133
    … …  
    163154                echo '<div class="wptk-hub-card-header">';
    164155                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 
    177156                echo '</div>';
    178157
    … …  
    180159                if ( ! empty( $module['description'] ) ) {
    181160                        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>';
    186161                }
    187162                echo '</div>';
  • inquiryflow/trunk/core/src/Admin/LegacyRedirects.php

    r3670262 r3696831  
    2525         * @var array<string, string>
    2626         */
    27         private static $map = array(
    28                 'wptk-pro-license' => 'wptk-kit-licenses',
    29         );
     27        private static $map = array();
    3028
    3129        /**
    … …  
    5553
    5654                $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' ) ) );
    7056                exit;
    7157        }
  • inquiryflow/trunk/core/src/Admin/Menu.php

    r3670262 r3696831  
    5353         * Constructor.
    5454         *
    55          * @param \WPTravelKit\Suite\Core|null $core Optional core instance for license lookups.
     55         * @param \WPTravelKit\Suite\Core|null $core Optional core instance.
    5656         */
    5757        public function __construct( $core = null ) {
    … …  
    8181                        array(
    8282                                'order'   => ( count( $this->submenus ) + 1 ) * 10,
    83                                 'pro'     => false,
    8483                                'section' => self::SECTION_PRODUCTS,
    8584                                'product' => '',
    … …  
    9897                        'callback' => $callback,
    9998                        'order'    => (int) $args['order'],
    100                         'pro'      => ! empty( $args['pro'] ),
    10199                        'section'  => (string) $args['section'],
    102100                        'product'  => (string) $args['product'],
    … …  
    149147
    150148                add_action( 'admin_menu', array( $this, 'reorder_submenu' ), 9999 );
    151                 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_menu_styles' ) );
    152149
    153150                add_action(
    … …  
    200197
    201198        /**
    202          * Build sidebar menu label, optionally with PRO badge.
     199         * Build sidebar menu label.
    203200         *
    204201         * @param array<string, mixed> $item Submenu config.
    … …  
    206203         */
    207204        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'];
    224206        }
    225207
    … …  
    244226
    245227        /**
    246          * Reorder submenu items and ensure PRO badges render.
     228         * Reorder submenu items.
    247229         */
    248230        public function reorder_submenu() {
    … …  
    276258                        $submenu[ self::SLUG ] = $ordered;
    277259                }
    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 );
    309260        }
    310261
  • inquiryflow/trunk/core/src/Admin/SettingsShell.php

    r3670262 r3696831  
    9898         */
    9999        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();
    103102
    104103                echo '<div class="wrap wptk-ib-admin-wrap wptk-settings-shell" style="padding-top:12px">';
    … …  
    110109                                $classes = 'ib-btn' . ( $module_id === $active ? ' ib-btn--primary' : ' ib-btn--secondary' );
    111110                                $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 );
    115111
    116112                                echo '<a class="' . esc_attr( $classes ) . '" href="' . esc_url( $url ) . '">';
    117113                                echo esc_html( $label );
    118                                 if ( $locked ) {
    119                                         echo ' <span class="wptk-ib-menu-pro">PRO</span>';
    120                                 }
    121114                                echo '</a>';
    122115                        }
  • inquiryflow/trunk/core/src/Admin/ShellAdminNotices.php

    r3670262 r3696831  
    22/**
    33 * 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.
    49 *
    510 * This file is part of the shared WP Travel Kit suite layer bundled into free suite plugins.
    … …  
    3641
    3742        /**
     43         * Whether captured notice hooks were already re-fired (shell or shutdown).
     44         *
     45         * @var bool
     46         */
     47        private static $fired = false;
     48
     49        /**
    3850         * Register hooks.
    3951         */
    … …  
    4153                add_action( 'current_screen', array( __CLASS__, 'maybe_relocate' ), 0 );
    4254                add_filter( 'admin_body_class', array( __CLASS__, 'body_class' ) );
     55                add_action( 'shutdown', array( __CLASS__, 'maybe_restore_on_shutdown' ), 0 );
    4356        }
    4457
    … …  
    5770                if ( isset( $wp_filter['admin_notices'] ) ) {
    5871                        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.
    5973                        unset( $wp_filter['admin_notices'] );
    6074                }
    … …  
    6276                if ( isset( $wp_filter['all_admin_notices'] ) ) {
    6377                        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.
    6479                        unset( $wp_filter['all_admin_notices'] );
    6580                }
    … …  
    124139                }
    125140
    126                 if ( HubPage::SLUG === $page || LicenseCenterPage::SLUG === $page || SettingsShell::SLUG === $page ) {
     141                if ( HubPage::SLUG === $page || SettingsShell::SLUG === $page ) {
    127142                        return true;
    128143                }
    … …  
    165180         */
    166181        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() {
    171242                ob_start();
    172243
    … …  
    179250                }
    180251
    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();
    187253        }
    188254}
  • inquiryflow/trunk/core/src/Admin/WorkspaceRegistry.php

    r3670262 r3696831  
    4444                );
    4545
     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.
    4648                $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 );
    5549        }
    5650
  • inquiryflow/trunk/core/src/Admin/WorkspaceShell.php

    r3670262 r3696831  
    8282                $product_label = (string) ( $workspace['label'] ?? $product_id );
    8383                $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 );
    8684
    8785                $shell = $this->core->container()->get( 'admin_shell' );
    … …  
    9391                                'product_name'   => $product_label,
    9492                                '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'],
    10193                                'tabs'           => $tabs,
    10294                                'active_tab'     => $active_tab,
    … …  
    143135                        }
    144136
    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 
    158137                        if ( ! empty( $tab['render'] ) && is_callable( $tab['render'] ) ) {
    159138                                call_user_func( $tab['render'] );
  • inquiryflow/trunk/core/src/Core.php

    r3670262 r3696831  
    1717use WPTravelKit\Suite\Admin\HubPage;
    1818use WPTravelKit\Suite\Admin\LegacyRedirects;
    19 use WPTravelKit\Suite\Admin\LicenseCenterPage;
    2019use WPTravelKit\Suite\Admin\Menu;
    2120use WPTravelKit\Suite\Admin\SettingsShell;
    2221use WPTravelKit\Suite\Admin\ShellAdminNotices;
    2322use WPTravelKit\Suite\Admin\WorkspaceRegistry;
    24 use WPTravelKit\Suite\Admin\UpsellRegistry;
    2523use WPTravelKit\Suite\Admin\WorkspaceShell;
    26 use WPTravelKit\Suite\Licensing\LicenseAdapter;
    27 use WPTravelKit\Suite\Licensing\LicenseRegistry;
    2824use WPTravelKit\Suite\Modules\AdminBootstrap;
    2925use WPTravelKit\Suite\Modules\ModuleRegistry;
    3026use WPTravelKit\Suite\Onboarding\Onboarding;
    31 use WPTravelKit\Suite\Rest\LicensesController;
    3227use WPTravelKit\Suite\Rest\Server;
    3328use WPTravelKit\Suite\Settings\Settings;
    … …  
    8681                }
    8782
    88                 $this->container->get( 'rest' )->register_controller( new LicensesController( $this ) );
    8983                $this->container->get( 'rest' )->init();
    9084
    … …  
    157151
    158152                $this->container->set(
    159                         'license_registry',
    160                         function () {
    161                                 return new LicenseRegistry();
    162                         }
    163                 );
    164 
    165                 $this->container->set(
    166153                        'rest',
    167154                        function () {
    … …  
    195182
    196183                $this->container->set(
    197                         'upsell_registry',
    198                         function () {
    199                                 return new UpsellRegistry();
    200                         }
    201                 );
    202 
    203                 $this->container->set(
    204184                        'admin_shell',
    205185                        function ( $c ) use ( $core ) {
    … …  
    222202                                unset( $c );
    223203                                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 );
    232204                        }
    233205                );
    … …  
    262234                        }
    263235                );
    264 
    265                 $this->container->set(
    266                         'license',
    267                         function ( $c ) {
    268                                 return new LicenseAdapter( $c->get( 'license_registry' ) );
    269                         }
    270                 );
    271236        }
    272237
    … …  
    289254
    290255                $this->container->get( 'hub_page' )->register_menu( $menu );
    291                 $this->container->get( 'license_center' )->register_menu( $menu );
    292256
    293257                /**
  • inquiryflow/trunk/core/src/Modules/AdminBootstrap.php

    r3670262 r3696831  
    6666                }
    6767
    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 
    9368                if ( ! empty( $admin['menu'] ) && is_array( $admin['menu'] ) && ! empty( $admin['workspace']['slug'] ) ) {
    9469                        $menu_args = wp_parse_args(
  • inquiryflow/trunk/core/src/Scheduler/ActionSchedulerLoader.php

    r3670262 r3696831  
    33 * Background job scheduler (WP Cron by default).
    44 *
    5  * Free/org plugins schedule via WP Cron only. Premium plugins may call load()
    6  * to bundle Action Scheduler from their own 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;
    77 * schedule() and schedule_recurring() will use AS when those APIs are available.
    88 *
    … …  
    2525
    2626        /**
    27          * Action Scheduler group slug for premium / external AS usage.
     27         * Action Scheduler group slug for external AS usage.
    2828         */
    2929        const GROUP = 'wptk';
    3030
    3131        /**
    32          * Load Action Scheduler from a premium plugin path.
     32         * Load Action Scheduler from a plugin path that bundles it.
    3333         *
    3434         * No-op when Action Scheduler is already available (e.g. WooCommerce).
  • inquiryflow/trunk/core/src/Settings/SettingsPanelRegistry.php

    r3670262 r3696831  
    3535                        $args,
    3636                        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,
    4241                        )
    4342                );
  • inquiryflow/trunk/core/version.php

    r3670262 r3696831  
    1313
    1414if ( ! defined( 'WPTK_SUITE_VERSION' ) ) {
    15         define( 'WPTK_SUITE_VERSION', '1.0.3' );
     15        define( 'WPTK_SUITE_VERSION', '1.0.7' );
    1616}
  • inquiryflow/trunk/core/wptk-core-shim.php

    r3670262 r3696831  
    11<?php
     2defined( 'ABSPATH' ) || exit;
     3
    24// phpcs:ignoreFile -- Shared suite bootstrap API; wptk_ globals and hooks are intentional cross-plugin surface.
    35/**
  • inquiryflow/trunk/inquiryflow.php

    r3670262 r3696831  
    44 * Plugin URI: https://wptravelkit.com/wordpress-travel-plugins/inquiryflow/
    55 * 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.4
     6 * Version: 1.0.5
    77 * Author: WP Travel Kit
    88 * Author URI: https://wptravelkit.com/
    … …  
    1212 * Domain Path: /languages
    1313 * Requires at least: 6.0
    14  * Tested up to: 7.0
     14 * Tested up to: 7.1
    1515 * Requires PHP: 7.4
    1616 *
    … …  
    2424}
    2525
    26 define( 'INQUIRYFLOW_VERSION', '1.0.4' );
     26define( 'INQUIRYFLOW_VERSION', '1.0.5' );
    2727define( 'INQUIRYFLOW_PLUGIN_FILE', __FILE__ );
    2828define( 'INQUIRYFLOW_PLUGIN_PATH', plugin_dir_path( __FILE__ ) );
  • inquiryflow/trunk/modules/inquiryflow/assets/build/settings-admin.js

    r3608474 r3696831  
    104104                var _e = useState('button'), activeNav = _e[0], setActiveNav = _e[1];
    105105                var _f = useState(''), secretDraft = _f[0], setSecretDraft = _f[1];
     106                var _f2 = useState(''), recaptchaSecretDraft = _f2[0], setRecaptchaSecretDraft = _f2[1];
    106107                var _g = useState(false), testSending = _g[0], setTestSending = _g[1];
    107108                var _h = useState(''), testNotice = _h[0], setTestNotice = _h[1];
    … …  
    196197                        }
    197198
     199                        if (recaptchaSecretDraft) {
     200                                payload.inquiryflow.recaptcha_secret = recaptchaSecretDraft;
     201                        }
     202
    198203                        if (markOnboarding) {
    199204                                payload.mark_onboarding_complete = true;
    … …  
    211216                                        setData(res.data);
    212217                                        setSecretDraft('');
     218                                        setRecaptchaSecretDraft('');
    213219                                        showNotice('Settings saved.', true);
    214220                                } else {
    … …  
    599605                                                                onChange: function (e) {
    600606                                                                        var value = e.target.value;
    601                                                                         if (!isPro && value === 'recaptcha') {
    602                                                                                 return;
    603                                                                         }
    604607                                                                        setIb('captcha_provider', value);
    605608                                                                        boolSet('hcaptcha_enabled', value === 'hcaptcha');
    … …  
    609612                                                                        { id: 'none', label: 'None' },
    610613                                                                        { id: 'hcaptcha', label: 'hCaptcha' },
     614                                                                        { id: 'recaptcha', label: 'Google reCAPTCHA' },
    611615                                                                ]).map(function (option) {
    612616                                                                        var locked = !!option.pro && !isPro;
    … …  
    640644                                                                        onChange: function (e) { setSecretDraft(e.target.value); },
    641645                                                                        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 ? '••••••••••••' : '',
    642671                                                                        autoComplete: 'new-password',
    643672                                                                })
  • inquiryflow/trunk/modules/inquiryflow/src/Admin/Dashboard.php

    r3608474 r3696831  
    8686                );
    8787
    88                 $upsell_registry = $this->core->container()->get( 'upsell_registry' );
    89                 $upsell          = $upsell_registry->resolve( $this->core, 'inquiryflow', 'InquiryFlow' );
    90 
    9188                $localize = array(
    9289                        'restUrl'          => rest_url( 'inquiryflow/v1/' ),
    9390                        'nonce'            => wp_create_nonce( 'wp_rest' ),
    94                         'isPro'            => $upsell_registry->is_pro_active( $this->core, 'inquiryflow-pro' ),
    95                         'upgradeUrl'       => $upsell['upgrade_url'],
    9691                        'upgradeDismissed' => $this->core->container()->get( 'settings' )->get( 'inquiryflow', 'upgrade_dismissed', '0' ),
    9792                        'columns'          => Hooks::admin_lead_columns(),
  • inquiryflow/trunk/modules/inquiryflow/src/Admin/SettingsIntegration.php

    r3608474 r3696831  
    125125                );
    126126
    127                 $upsell_registry = $this->core->container()->get( 'upsell_registry' );
    128                 $upsell          = $upsell_registry->resolve( $this->core, 'inquiryflow', 'InquiryFlow' );
    129 
    130127                $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',
    137132                );
    138133
    … …  
    176171                unset( $values['hcaptcha_secret_set'] );
    177172
     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
    178183                if ( isset( $values['captcha_provider'] ) ) {
    179184                        $provider = Captcha::sanitize_provider( $values['captcha_provider'] );
    180                         if ( 'recaptcha' === $provider && ! $this->core->container()->get( 'license' )->is_pro_active() ) {
    181                                 $provider = 'none';
    182                         }
    183185                        $values['captcha_provider'] = $provider;
    184186                        $values['hcaptcha_enabled'] = ( 'hcaptcha' === $provider ) ? '1' : '0';
    … …  
    213215         */
    214216        private function get_meta() {
    215                 $wpte     = $this->core->container()->get( 'wpte' );
     217                $wpte     = $this->core->container()->get( 'inquiryflow_wpte' );
    216218                $trip     = $this->get_sample_trip();
    217219                $privacy  = admin_url( 'export-personal-data.php' );
    … …  
    222224                $provider        = Captcha::get_provider( $this->core, $ib_settings );
    223225                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                                }
    228245                        }
    229246                }
  • inquiryflow/trunk/modules/inquiryflow/src/Email/Notifications.php

    r3608474 r3696831  
    4141         */
    4242        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 ) );
    4444                if ( ! $lead ) {
    4545                        return;
  • inquiryflow/trunk/modules/inquiryflow/src/Frontend/Frontend.php

    r3608474 r3696831  
    110110
    111111                $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();
    113113
    114114                wp_enqueue_style(
  • inquiryflow/trunk/modules/inquiryflow/src/Module.php

    r3670262 r3696831  
    1818use InquiryFlow\Rest\LeadsController;
    1919use InquiryFlow\Shortcodes\Shortcodes;
    20 use WPTravelKit\Suite\Admin\AdminUrls;
    2120use WPTravelKit\Suite\Admin\Menu;
    2221use WPTravelKit\Suite\Autoloader;
    … …  
    3938        private static $registered = false;
    4039
    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                 */
    6552        public static function init() {
    6653                self::register_autoloaders();
    … …  
    124111                $core->modules()->register( $manifest );
    125112                $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' ) );
    127114
    128115                $core->container()->get( 'settings_panels' )->register(
    129116                        'inquiryflow',
    130117                        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 () {
    135121                                        printf(
    136122                                                '<div id="%1$s" class="%2$s"></div>',
    … …  
    141127                        )
    142128                );
    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                 );
    153129        }
    154130
    … …  
    236212                                },
    237213                        ),
     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                        ),
    238222                        'upgrade_dismissed'   => array(
    239223                                'default'  => '0',
    … …  
    261245         */
    262246        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                         2
    273                 );
    274 
    275247                add_filter( 'inquiryflow_sanitize_inquiry_meta', array( Security\SubmissionValidator::class, 'sanitize_meta' ) );
    276248                add_filter( 'inquiryflow_sanitize_lead_source', array( Security\SubmissionValidator::class, 'sanitize_source' ) );
    … …  
    278250                add_filter( 'inquiryflow_sanitize_lead_source_url', array( Security\SubmissionValidator::class, 'sanitize_source_url' ) );
    279251
    280                 $product_rest = $core->container()->get( 'product_rest' );
     252                $product_rest = $core->container()->get( 'inquiryflow_rest' );
    281253                $product_rest->register_controller( new LeadsController( $core ) );
    282254                $product_rest->register_controller( new AnalyticsController( $core ) );
    … …  
    320292                        array(
    321293                                'section' => Menu::SECTION_PRODUCTS,
    322                                 'product' => 'inquiryflow-pro',
    323294                                'order'   => 10,
    324295                        )
  • inquiryflow/trunk/modules/inquiryflow/src/ProductBootstrap.php

    r3608474 r3696831  
    4242                self::$registered = true;
    4343                $core->container()->set(
    44                         'leads',
     44                        'inquiryflow_leads',
    4545                        function () {
    4646                                return new LeadsStore();
    … …  
    4949
    5050                $core->container()->set(
    51                         'analytics',
     51                        'inquiryflow_analytics',
    5252                        function ( $c ) {
    53                                 return new Analytics( $c->get( 'leads' ) );
     53                                return new Analytics( $c->get( 'inquiryflow_leads' ) );
    5454                        }
    5555                );
    5656
    5757                $core->container()->set(
    58                         'wpte',
     58                        'inquiryflow_wpte',
    5959                        function () {
    6060                                return new WpteAdapter();
    … …  
    6363
    6464                $core->container()->set(
    65                         'privacy',
     65                        'inquiryflow_privacy',
    6666                        function ( $c ) {
    67                                 return new PrivacyExporter( $c->get( 'leads' ) );
     67                                return new PrivacyExporter( $c->get( 'inquiryflow_leads' ) );
    6868                        }
    6969                );
    7070
    7171                $core->container()->set(
    72                         'product_rest',
     72                        'inquiryflow_rest',
    7373                        function () {
    7474                                return new ProductRestServer();
    … …  
    8080                $runner->run();
    8181
    82                 $core->container()->get( 'privacy' )->register();
     82                $core->container()->get( 'inquiryflow_privacy' )->register();
    8383
    84                 $product_rest = $core->container()->get( 'product_rest' );
     84                $product_rest = $core->container()->get( 'inquiryflow_rest' );
    8585                $product_rest->register_controller( new ProductSettingsController( $core ) );
    8686                $product_rest->init();
    … …  
    9999
    100100                $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' ) ) {
    102102                        return;
    103103                }
    … …  
    105105                $settings = $core->container()->get( 'settings' );
    106106                $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 );
    108108        }
    109109}
  • inquiryflow/trunk/modules/inquiryflow/src/Rest/AnalyticsController.php

    r3608474 r3696831  
    6767        public function get_analytics( $request ) {
    6868                $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 ) );
    7070                return $this->success( $summary );
    7171        }
  • inquiryflow/trunk/modules/inquiryflow/src/Rest/LeadsController.php

    r3608474 r3696831  
    329329                }
    330330
    331                 $lead = $this->core->container()->get( 'leads' )->create(
     331                $lead = $this->core->container()->get( 'inquiryflow_leads' )->create(
    332332                        array(
    333333                                'name'       => $name,
    … …  
    349349                }
    350350
    351                 $this->core->container()->get( 'analytics' )->record(
     351                $this->core->container()->get( 'inquiryflow_analytics' )->record(
    352352                        'submit',
    353353                        array(
    … …  
    370370         */
    371371        public function list_leads( $request ) {
    372                 $result = $this->core->container()->get( 'leads' )->query(
     372                $result = $this->core->container()->get( 'inquiryflow_leads' )->query(
    373373                        array(
    374374                                'search'   => sanitize_text_field( $request->get_param( 'search' ) ?? '' ),
    … …  
    402402         */
    403403        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'] ) );
    405405                if ( ! $lead ) {
    406406                        return $this->error( 'inquiryflow_not_found', __( 'Inquiry not found.', 'inquiryflow' ), 404 );
    407407                }
    408408                $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() );
    410410                return $this->success( $data );
    411411        }
    … …  
    427427                        }
    428428
    429                         $lead = $this->core->container()->get( 'leads' )->update_status( $id, $status );
     429                        $lead = $this->core->container()->get( 'inquiryflow_leads' )->update_status( $id, $status );
    430430                        if ( ! $lead ) {
    431431                                return $this->error( 'inquiryflow_not_found', __( 'Inquiry not found.', 'inquiryflow' ), 404 );
    432432                        }
    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 ) );
    434434                        return $this->success( $this->format_lead( $lead ) );
    435435                }
    … …  
    446446        public function add_note( $request ) {
    447447                $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 );
    449449                if ( ! $lead ) {
    450450                        return $this->error( 'inquiryflow_not_found', __( 'Inquiry not found.', 'inquiryflow' ), 404 );
    … …  
    456456                        return $this->error( 'inquiryflow_validation', __( 'Note text required.', 'inquiryflow' ), 400 );
    457457                }
    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 );
    459459                if ( ! $note_id ) {
    460460                        return $this->error( 'inquiryflow_save_failed', __( 'Could not save note.', 'inquiryflow' ), 500 );
    … …  
    471471        public function export_csv( $request ) {
    472472                unset( $request );
    473                 $result  = $this->core->container()->get( 'leads' )->query(
     473                $result  = $this->core->container()->get( 'inquiryflow_leads' )->query(
    474474                        array(
    475475                                'per_page' => 5000,
    … …  
    512512                $params = $request->get_json_params();
    513513                $event  = sanitize_key( $params['event'] ?? '' );
    514                 $this->core->container()->get( 'analytics' )->record(
     514                $this->core->container()->get( 'inquiryflow_analytics' )->record(
    515515                        $event,
    516516                        array(
  • inquiryflow/trunk/modules/inquiryflow/src/Rest/SettingsController.php

    r3608474 r3696831  
    165165                        if ( 'inquiryflow_pro' === $module_id ) {
    166166                                do_action( 'inquiryflow_settings_api_before_save', $values, $this->core );
    167 
    168                                 if ( ! $this->core->container()->get( 'license' )->is_pro_active() ) {
    169                                         continue;
    170                                 }
    171167                        }
    172168
  • inquiryflow/trunk/modules/inquiryflow/src/Security/Captcha.php

    r3608474 r3696831  
    4040
    4141                if ( ! in_array( $provider, self::PROVIDERS, true ) ) {
    42                         $provider = 'none';
    43                 }
    44 
    45                 if ( 'recaptcha' === $provider && ! $core->container()->get( 'license' )->is_pro_active() ) {
    4642                        $provider = 'none';
    4743                }
    … …  
    9894                }
    9995
     96                if ( 'recaptcha' === $provider ) {
     97                        return self::verify_recaptcha( $core, $token );
     98                }
     99
    100100                return false;
    101101        }
    … …  
    153153                if ( 'hcaptcha' === $provider ) {
    154154                        $config['captchaSiteKey'] = (string) ( $settings['hcaptcha_site_key'] ?? '' );
     155                } elseif ( 'recaptcha' === $provider ) {
     156                        $config['captchaSiteKey'] = (string) ( $settings['recaptcha_site_key'] ?? '' );
    155157                }
    156158
    … …  
    187189                                'label' => __( 'hCaptcha', 'inquiryflow' ),
    188190                        ),
     191                        array(
     192                                'id'    => 'recaptcha',
     193                                'label' => __( 'Google reCAPTCHA', 'inquiryflow' ),
     194                        ),
    189195                );
    190196        }
    … …  
    231237                return ! empty( $body['success'] );
    232238        }
     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        }
    233274}
  • inquiryflow/trunk/readme.txt

    r3670262 r3696831  
    66Tested up to: 7.1
    77Requires PHP: 7.4
    8 Stable tag: 1.0.4
     8Stable tag: 1.0.5
    99License: GPLv2 or later
    1010License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    166166
    167167== Changelog ==
     168= 1.0.5 =
     169* Fixed 'core' parity with other plugins in suite
     170
    168171= 1.0.4 =
    169172* Compatibility update required by InquiryFlow Pro.
Note: See TracChangeset for help on using the changeset viewer.