Plugin Directory

Changeset 3666763


Ignore:
Timestamp:
08/26/2026 10:33:07 AM (2 months ago)
Author:
wisernotify
Message:

Updating trunk to version 3.7

Location:
wiser-review/trunk
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • wiser-review/trunk/assets/css/wiserrw-admin.css

    r3531870 r3666763  
    1111    flex-direction: column;
    1212    padding: 0 20px;
     13    box-sizing: border-box;
     14    overflow-x: clip;
    1315}
    1416.wr-form-contents { display: contents; }
    … …  
    265267.wr-order-6 { order: 6; }
    266268.wr-order-7 { order: 7; }
     269/* Google Customer Reviews sits just above the FAQ. .wr-form-contents uses
     270 * display: contents, so this section (which lives inside the main form for
     271 * AJAX save-all semantics) is still treated as a direct child of .wr-page
     272 * for flex ordering. */
    267273.wr-order-8 { order: 8; }
     274.wr-order-9 { order: 9; }
     275
     276/* ─── Universal "Saved" flash — positioned absolutely below the switch so it
     277 * flashes in briefly on toggle change without shifting siblings (previously
     278 * it was appended inline inside .wiserrw_toggle and stole horizontal space,
     279 * pushing the widget name to the right and snapping back on fade-out). */
     280.wiserrw_toggle { position: relative; }
     281.wiserrw_saved_msg {
     282    position: absolute !important;
     283    top: 100% !important;
     284    left: 0 !important;
     285    margin: 4px 0 0 !important;
     286    padding: 1px 8px !important;
     287    font-size: 11px !important;
     288    font-weight: 500 !important;
     289    color: #16a34a !important;
     290    background: #edfaef;
     291    border-radius: 999px;
     292    line-height: 1.5;
     293    white-space: nowrap;
     294    pointer-events: none;
     295    z-index: 2;
     296}
     297
     298/* ─── Google Customer Reviews section ───
     299 * Compact widget-row style: Merchant ID + delivery days sit side-by-side
     300 * under the toggle/description block, an inline save button reveals the
     301 * implementation-method dropdown, and only the currently-selected method's
     302 * detail panel is visible. Matches the density of the other More Features
     303 * rows (Rich Snippet, Leave a Review Page).
     304 */
     305.wr-gcr-hidden { display: none !important; }
     306
     307/* Config wrapper — plain inline (no boxed panel) so it reads as a
     308 * continuation of the description above, matching Leave a Review Page. */
     309.wr-gcr-config { margin-top: 10px; }
     310
     311/* Merchant ID + delivery days side-by-side; wraps on narrow widths. */
     312.wr-gcr-fields-row { display: flex; gap: 12px; align-items: flex-start; flex-wrap: wrap; }
     313.wr-gcr-field { min-width: 0; }
     314.wr-gcr-field-grow { flex: 1 1 200px; }
     315.wr-gcr-field-narrow { flex: 0 0 130px; }
     316.wr-gcr-field input[type="text"],
     317.wr-gcr-field input[type="number"] {
     318    width: 100%;
     319    padding: 6px 10px;
     320    font-size: 13px;
     321    border: 1px solid #8c8f94;
     322    border-radius: 4px;
     323    box-sizing: border-box;
     324}
     325.wr-gcr-hint { font-size: 11px; color: #757575; margin: 4px 0 0; line-height: 1.5; }
     326
     327/* Merchant ID inline hint + error. Hint sits under the input; error only
     328 * shows when the field has content that fails the 8–15 digit rule. */
     329.wr-gcr-field-hint {
     330    display: block;
     331    font-size: 11px;
     332    color: #757575;
     333    margin-top: 4px;
     334    line-height: 1.4;
     335}
     336.wr-gcr-field-error {
     337    display: block;
     338    font-size: 11px;
     339    color: #d63638;
     340    margin-top: 4px;
     341    line-height: 1.4;
     342}
     343.wr-gcr-field-error[hidden] { display: none; }
     344.wr-gcr-field input.wr-gcr-input-invalid {
     345    border-color: #d63638;
     346    box-shadow: 0 0 0 1px #d63638;
     347}
     348
     349/* "?" help icon next to a field label. Reuses the existing .wiserrw-tooltip
     350 * bubble (right-aligned above the anchor, wraps at 220px). Focusable so
     351 * keyboard users can trigger it too. */
     352.wr-gcr-help-icon {
     353    position: relative;
     354    display: inline-flex;
     355    align-items: center;
     356    margin-left: 4px;
     357    color: #757575;
     358    cursor: help;
     359    outline: none;
     360    vertical-align: baseline;
     361}
     362.wr-gcr-help-icon .dashicons {
     363    font-size: 14px;
     364    width: 14px;
     365    height: 14px;
     366    line-height: 1;
     367}
     368.wr-gcr-help-icon:hover,
     369.wr-gcr-help-icon:focus { color: #2271b1; }
     370.wr-gcr-help-icon:hover .wiserrw-tooltip,
     371.wr-gcr-help-icon:focus .wiserrw-tooltip { visibility: visible; opacity: 1; }
     372
     373/* Save row: inline button + hint next to it. */
     374.wr-gcr-save-row { display: flex; align-items: center; gap: 10px; margin-top: 12px; flex-wrap: wrap; }
     375.wr-gcr-savehint {
     376    font-size: 12px;
     377    color: #8a6d0b;
     378    line-height: 1.4;
     379    margin: 0;
     380}
     381
     382/* Help block — replaces the old dropdown-driven method picker. Just
     383 * documents that auto-injection happens on the WC thank-you page and
     384 * exposes the shortcode + JS API as fallback snippets for merchants
     385 * whose theme doesn't fire the standard hook. */
     386.wr-gcr-help { margin-top: 14px; padding-top: 12px; border-top: 1px solid #ececec; font-size: 12px; color: #50575e; line-height: 1.55; }
     387/* Status line — plain paragraph, no icon. Only visible when the feature
     388 * toggle is ON (help block is wrapped in a hide-on-off class server-side). */
     389.wr-gcr-help-status { margin: 0 0 8px; font-size: 12px; line-height: 1.55; color: #50575e; }
     390.wr-gcr-help-status code {
     391    display: inline-block;
     392    padding: 0 5px;
     393    background: #f0f1f3;
     394    border-radius: 3px;
     395    font-size: 11px;
     396    color: #1d2327;
     397}
     398.wr-gcr-help-fallback { margin: 0; }
     399
     400/* First-time enrollment warning — subtle amber tint so it reads as an
     401 * important prerequisite without being alarming. Placed between the
     402 * "auto-injects" success line and the collapsibles so merchants see it
     403 * before they wonder why nothing's rendering. */
     404.wr-gcr-help-warn {
     405    display: flex; align-items: flex-start; gap: 6px;
     406    margin: 0 0 8px;
     407    padding: 8px 10px;
     408    background: #fff8e1;
     409    border: 1px solid #f0d478;
     410    border-radius: 4px;
     411    font-size: 12px;
     412    color: #7a5b02;
     413    line-height: 1.55;
     414}
     415.wr-gcr-help-warn-icon { color: #b58900; font-size: 16px; width: 16px; height: 16px; flex-shrink: 0; margin-top: 1px; }
     416.wr-gcr-help-warn a { color: #7a5b02; text-decoration: underline; font-weight: 500; }
     417.wr-gcr-help-warn a:hover { color: #4a3800; }
     418
     419/* Setup steps inside the "How to enroll" collapsible. */
     420.wr-gcr-setup-steps { margin: 0 0 4px; padding-left: 20px; font-size: 12px; line-height: 1.6; color: #50575e; }
     421.wr-gcr-setup-steps li { margin-bottom: 4px; }
     422.wr-gcr-setup-steps li:last-child { margin-bottom: 0; }
     423
     424/* Collapsible bordered pill card (Design X). Used for both "First-time
     425 * setup" and "Custom / headless checkout" blocks so they read as real
     426 * accordions rather than plain link-style text. Left dashicon anchors the
     427 * card visually; right chevron rotates on open. Native <details> element
     428 * keeps this JS-free. */
     429.wr-gcr-card {
     430    margin-top: 8px;
     431    background: #fff;
     432    border: 1px solid #dcdcde;
     433    border-radius: 6px;
     434    overflow: hidden;
     435    transition: border-color .15s ease;
     436}
     437.wr-gcr-card + .wr-gcr-card { margin-top: 8px; }
     438.wr-gcr-card:hover { border-color: #b0b3b8; }
     439.wr-gcr-card[open] { border-color: #8c8f94; }
     440
     441.wr-gcr-card-summary {
     442    display: flex;
     443    align-items: center;
     444    gap: 8px;
     445    padding: 10px 12px;
     446    cursor: pointer;
     447    font-size: 13px;
     448    font-weight: 500;
     449    color: #1d2327;
     450    list-style: none;
     451    user-select: none;
     452}
     453.wr-gcr-card-summary::-webkit-details-marker { display: none; }
     454.wr-gcr-card-summary::marker { content: ""; }
     455.wr-gcr-card-summary:hover { background: #f9fbfe; }
     456.wr-gcr-card[open] > .wr-gcr-card-summary { background: #f8fafc; border-bottom: 1px solid #ececec; }
     457
     458.wr-gcr-card-icon {
     459    color: #2271b1;
     460    font-size: 16px !important;
     461    width: 16px !important;
     462    height: 16px !important;
     463    flex-shrink: 0;
     464}
     465.wr-gcr-card-title { flex: 1; min-width: 0; }
     466.wr-gcr-card-chevron {
     467    color: #757575;
     468    font-size: 18px !important;
     469    width: 18px !important;
     470    height: 18px !important;
     471    flex-shrink: 0;
     472    transition: transform .2s ease;
     473}
     474.wr-gcr-card[open] > .wr-gcr-card-summary > .wr-gcr-card-chevron {
     475    transform: rotate(180deg);
     476}
     477.wr-gcr-card-body {
     478    padding: 12px;
     479    font-size: 12px;
     480    color: #50575e;
     481    line-height: 1.55;
     482}
     483
     484.wr-gcr-snippet-block { padding: 10px 12px; background: #f8fafc; border: 1px solid #dcdcde; border-radius: 4px; }
     485.wr-gcr-snippet-block + .wr-gcr-snippet-block { margin-top: 8px; }
     486.wr-gcr-snippet-label { font-size: 12px; font-weight: 600; color: #1d2327; margin-bottom: 6px; }
     487
     488/* Code block copy button (JavaScript-API snippet). */
     489.wr-gcr-code { position: relative; }
     490.wr-gcr-copy-code {
     491    position: absolute;
     492    top: 6px;
     493    right: 6px;
     494    z-index: 1;
     495    display: inline-flex;
     496    align-items: center;
     497    gap: 3px;
     498    padding: 3px 8px;
     499    font-size: 11px;
     500    color: #2271b1;
     501    background: #fff;
     502    border: 1px solid #dcdcde;
     503    border-radius: 4px;
     504    cursor: pointer;
     505    line-height: 1.6;
     506}
     507.wr-gcr-copy-code:hover { background: #f0f6fc; border-color: #2271b1; }
     508.wr-gcr-copy-code.is-copied { color: #00822b; background: #edfaef; border-color: #a7d7ab; }
     509.wr-gcr-copy-code .dashicons { font-size: 14px; width: 14px; height: 14px; }
     510.wr-gcr-code pre {
     511    margin: 0;
     512    padding: 10px 12px;
     513    background: #fff;
     514    border: 1px solid #dcdcde;
     515    border-radius: 4px;
     516    font-size: 11px;
     517    line-height: 1.5;
     518    overflow: auto;
     519}
     520
     521/* Sidebar-mounted "First-time setup" collapsible. Compact pill card so
     522 * the 4-step enrollment path fits inside .wr-feature-guide (260px)
     523 * without shouting for attention. The manual-embed snippets card stays
     524 * in the main column because its JS code block needs the wider space. */
     525.wr-gcr-side-setup {
     526    display: block;
     527    margin: 12px 0 0;
     528    border: 1px solid #dcdcde;
     529    border-radius: 6px;
     530    background: #fff;
     531    overflow: hidden;
     532}
     533.wr-gcr-side-setup > summary {
     534    list-style: none;
     535    display: flex;
     536    align-items: center;
     537    gap: 6px;
     538    padding: 8px 10px;
     539    cursor: pointer;
     540    font-size: 12px;
     541    font-weight: 600;
     542    color: #1d2327;
     543    background: #f6f7f7;
     544}
     545.wr-gcr-side-setup > summary::-webkit-details-marker { display: none; }
     546.wr-gcr-side-setup > summary::marker { content: ""; }
     547.wr-gcr-side-setup > summary:hover { background: #eef2f7; }
     548.wr-gcr-side-setup[open] > summary { border-bottom: 1px solid #ececec; }
     549.wr-gcr-side-setup > summary > .dashicons:first-child {
     550    color: #2271b1;
     551    font-size: 14px;
     552    width: 14px;
     553    height: 14px;
     554}
     555.wr-gcr-side-setup > summary > span:nth-child(2) {
     556    flex: 1;
     557    min-width: 0;
     558    line-height: 1.35;
     559}
     560.wr-gcr-side-chevron {
     561    color: #757575;
     562    font-size: 14px;
     563    width: 14px;
     564    height: 14px;
     565    transition: transform .15s ease;
     566    flex-shrink: 0;
     567}
     568.wr-gcr-side-setup[open] > summary > .wr-gcr-side-chevron { transform: rotate(180deg); }
     569.wr-gcr-side-setup-body {
     570    padding: 10px 12px;
     571    font-size: 11px;
     572    color: #50575e;
     573    line-height: 1.5;
     574}
     575.wr-gcr-side-setup-body ol {
     576    margin: 0 0 8px;
     577    padding-left: 18px;
     578}
     579.wr-gcr-side-setup-body li + li { margin-top: 4px; }
     580.wr-gcr-side-setup-body a { font-weight: 500; }
    268581
    269582/* ─── FAQ ───
    … …  
    367680    visibility: hidden; opacity: 0; background-color: #333; color: #fff;
    368681    text-align: center; border-radius: 5px; padding: 6px 8px; font-size: 12px;
    369     position: absolute; z-index: 100001; bottom: 125%; left: 50%;
    370     transform: translateX(-50%); transition: opacity 0.2s; white-space: nowrap;
     682    position: absolute; z-index: 100001; bottom: 125%;
     683    right: 0; left: auto; transform: none;
     684    transition: opacity 0.2s;
     685    max-width: 220px; width: max-content; white-space: normal; line-height: 1.35;
    371686}
    372687.wiserrw-tooltip::after {
    373     content: ""; position: absolute; top: 100%; left: 50%; margin-left: -5px;
     688    content: ""; position: absolute; top: 100%;
     689    right: 15px; left: auto; margin-left: 0;
    374690    border-width: 5px; border-style: solid;
    375691    border-color: #333 transparent transparent transparent;
  • wiser-review/trunk/assets/js/wiserw-js.js

    r3531870 r3666763  
    116116   
    117117    // ✅ CRITICAL: Only attach change handler to CHECKBOX inputs within toggles, NOT submit buttons
    118     $(".wiserrw_toggle input[type='checkbox']").on('change', function(e) {
     118    // (.wiserrw_autosave_checkbox is the sub-option pattern — small inline checkboxes
     119    // outside a .wiserrw_toggle wrapper that still need to persist on change.)
     120    $(".wiserrw_toggle input[type='checkbox'], .wiserrw_autosave_checkbox").on('change', function(e) {
    119121        // ✅ Don't process if form is being submitted normally
    120122        if (isFormSubmitting) {
  • wiser-review/trunk/readme.txt

    r3559378 r3666763  
    44Donate link: https://wiserreview.com/
    55Requires at least: 5.6
    6 Tested up to: 6.9
     6Tested up to: 7.1
    77Requires PHP: 7.4
    8 Stable tag: 3.6
     8Stable tag: 3.7
    99License: GPLv2 or later
    1010License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    207207
    208208== Changelog ==
     209
     210= 3.7 =
     211* New: Option to hide the star icon next to the "Leave a Review" My Account menu item (Settings → Leave a Review Page)
     212* Improved: "Leave a Review" inline button and grid now only show on orders with status Completed — no more review invitations on Pending, Cancelled, or Refunded orders
     213* Improved: Google Customer Reviews settings redesigned to match other feature rows; Merchant ID and Delivery days sit side-by-side, and the implementation-method dropdown is removed (plugin always hooks the WooCommerce thank-you page)
     214* Improved: Added first-time enrollment note + setup walkthrough in the Google Customer Reviews help block — flags the "must enroll in Merchant Center first" prerequisite up front
     215* Improved: Google Customer Reviews help block hides itself when the toggle is off
     216* Improved: Clearer "auto-install vs manual snippet" note — green ✓ for standard thank-you page (nothing to do), amber ⚠ for custom / headless / page-builder checkouts (paste snippet)
     217* Improved: Google Merchant ID field now validates format (8–15 digits, numbers only), strips invalid characters as you type, and gates the toggle + Save button until well-formed
     218* Fixed: "Hide the star icon" sub-checkbox now persists correctly on save
     219* Fixed: "Saved" flash after AJAX save no longer widens the toggle column
     220* Fixed: Settings page could show a stray horizontal scrollbar caused by the disabled-state tooltip
     221* Compatibility: Tested with WordPress 7.1
    209222
    210223= 3.6 =
  • wiser-review/trunk/views/wiserw-plugin-settings.php

    r3559378 r3666763  
    8282$schema_enabled         = '';
    8383$review_page_enabled    = '';
     84$review_page_icon_hidden = '';
    8485$review_form_url        = '';
     86$gcr_enabled            = '';
     87$gcr_merchant_id        = '';
     88$gcr_delivery_days      = '7';
    8589
    8690$wiserrw_api_settings = get_option( 'wiserrw_api_settings', array() );
    … …  
    9599    $schema_enabled         = isset( $wiserrw_api_settings['wiserrw_schema_enabled'] ) ? 'checked' : '';
    96100    $review_page_enabled    = isset( $wiserrw_api_settings['wiserrw_review_page_enabled'] ) ? 'checked' : '';
     101    // "Hide" semantic — key absent = icon shown (preserves the look existing
     102    // stores already have after update).
     103    $review_page_icon_hidden = isset( $wiserrw_api_settings['wiserrw_review_menu_icon_hidden'] ) ? 'checked' : '';
    97104    $review_form_url        = isset( $wiserrw_api_settings['wiserrw_review_form_url'] ) ? $wiserrw_api_settings['wiserrw_review_form_url'] : '';
     105    $gcr_enabled            = isset( $wiserrw_api_settings['wiserrw_gcr_enabled'] ) ? 'checked' : '';
     106    $gcr_merchant_id        = isset( $wiserrw_api_settings['wiserrw_gcr_merchant_id'] ) ? $wiserrw_api_settings['wiserrw_gcr_merchant_id'] : '';
     107    $gcr_delivery_days      = isset( $wiserrw_api_settings['wiserrw_gcr_delivery_days'] ) ? $wiserrw_api_settings['wiserrw_gcr_delivery_days'] : '7';
    98108}
    99109
    … …  
    416426                                                                <?php endif; ?>
    417427                                                        </p>
     428
     429                                                        <!--
     430                                                        Sub-option: lets merchants on minimalist themes hide the
     431                                                        star icon prefixing the "Leave a Review" My Account menu
     432                                                        item. "Hide" semantic so the key is absent for new and
     433                                                        existing stores and the icon stays on by default — only
     434                                                        merchants who explicitly tick this lose the icon.
     435                                                        -->
     436                                                        <label class="wr-suboption" style="display:flex;align-items:center;gap:8px;margin-top:10px;font-size:12px;color:#50575e;">
     437                                                                <input type="checkbox"
     438                                                                        class="wiserrw_autosave_checkbox"
     439                                                                        id="wiserrw_review_menu_icon_hidden_checkbox"
     440                                                                        name="wiserrw_data[wiserrw_review_menu_icon_hidden]"
     441                                                                        value="1"
     442                                                                        <?php echo esc_attr( $review_page_icon_hidden ); ?>
     443                                                                        <?php disabled( $is_disabled ); ?> />
     444                                                                <span><?php esc_html_e( 'Hide the star icon next to the "Leave a Review" menu item', 'wiser-review' ); ?></span>
     445                                                        </label>
    418446                                                </div>
    419447                                        </div>
    … …  
    441469        </div>
    442470
     471        <?php
     472        // Methods are unlocked only once the feature is enabled AND Merchant ID + delivery days are SAVED.
     473        $gcr_is_on      = ( 'checked' === $gcr_enabled );
     474        $gcr_configured = ( $gcr_is_on && '' !== trim( (string) $gcr_merchant_id ) && '' !== trim( (string) $gcr_delivery_days ) );
     475        $gcr_hide       = ' wr-gcr-hidden';
     476        ?>
     477        <!-- ─── Google Customer Reviews (renders just above the FAQ via wr-order-8;
     478        kept inside the main form so the shared AJAX save persists it alongside
     479        all other settings without wiping them). Layout matches the other More
     480        Features rows exactly — toggle + name/desc on the left, guide on the
     481        right. Merchant ID + Delivery days are ALWAYS visible below the desc
     482        so merchants can fill in the ID before the toggle is enable-able
     483        (same pattern as Leave a Review Page's URL gate). Rendering method is
     484        no longer a picker: the plugin always hooks woocommerce_thankyou, and
     485        the shortcode + JS API stay available for custom / headless setups
     486        as fallback snippets in a collapsible help block. --><?php
     487        // Recompute view-level flags on the possibly-updated merchant-id state.
     488        $gcr_mid_empty = '' === trim( (string) $gcr_merchant_id );
     489        ?>
     490        <div class="wr-section wr-order-8" id="wiserrw_gcr_section" data-configured="<?php echo $gcr_configured ? '1' : '0'; ?>">
     491                <div class="wr-section-head">
     492                        <h2><?php esc_html_e( 'Google Customer Reviews', 'wiser-review' ); ?></h2>
     493                        <?php if ( ! $is_disabled && $gcr_configured ) : ?>
     494                                <span class="badge badge--done wr-gcr-active-badge"<?php echo $gcr_is_on ? '' : ' style="display:none;"'; ?>><?php esc_html_e( 'Active', 'wiser-review' ); ?></span>
     495                        <?php endif; ?>
     496                </div>
     497                <div class="wr-section-body" style="padding:0 16px;">
     498
     499                        <div class="wr-feature-row">
     500                                <div class="wr-feature-action">
     501                                        <div class="wiserrw_live_mode wiserrw_toggle">
     502                                                <label class="switch">
     503                                                        <input type="checkbox"
     504                                                                id="wiserrw_gcr_enabled_checkbox"
     505                                                                <?php echo esc_attr( $gcr_enabled ); ?>
     506                                                                name="wiserrw_data[wiserrw_gcr_enabled]"
     507                                                                value="1"
     508                                                                <?php disabled( $is_disabled ); ?>
     509                                                                <?php if ( $is_disabled ) : ?>data-api-locked="1"<?php endif; ?>
     510                                                                <?php if ( ! $is_disabled && $gcr_mid_empty ) : ?>disabled<?php endif; ?>>
     511                                                        <span class="slider round"></span>
     512                                                </label>
     513                                                <?php if ( $is_disabled ) : ?>
     514                                                        <span class="wiserrw-tooltip"><?php esc_html_e( 'Verify your API key first.', 'wiser-review' ); ?></span>
     515                                                <?php else : ?>
     516                                                        <span class="wiserrw-tooltip wr-gcr-tooltip-mid"<?php echo $gcr_mid_empty ? '' : ' style="display:none;"'; ?>><?php esc_html_e( 'Enter your Google Merchant ID first.', 'wiser-review' ); ?></span>
     517                                                <?php endif; ?>
     518                                        </div>
     519                                        <div style="min-width:0; flex:1;">
     520                                                <div class="wr-fname"><?php esc_html_e( 'Google Customer Reviews', 'wiser-review' ); ?></div>
     521                                                <div class="wr-fdesc"><?php esc_html_e( 'Show the Google Customer Reviews opt-in on the order-confirmation page so Google can collect seller ratings after purchase.', 'wiser-review' ); ?></div>
     522
     523                                                <!-- Merchant ID + Delivery days: always visible so the merchant can fill in
     524                                                the ID first (needed to unlock the toggle). Persists via AJAX. -->
     525                                                <div class="wr-gcr-config">
     526                                                        <div class="wr-gcr-fields-row">
     527                                                                <div class="wr-gcr-field wr-gcr-field-grow">
     528                                                                        <label class="wr-api-label" for="wiserrw_gcr_merchant_id"><?php esc_html_e( 'Google Merchant ID', 'wiser-review' ); ?> <span class="wiserrw_required_star">*</span><span class="wr-gcr-help-icon" tabindex="0" aria-label="<?php esc_attr_e( 'Find it in Google Merchant Center, top-right next to your account name.', 'wiser-review' ); ?>"><span class="dashicons dashicons-editor-help"></span><span class="wiserrw-tooltip"><?php esc_html_e( 'Find it in Google Merchant Center, top-right next to your account name.', 'wiser-review' ); ?></span></span></label>
     529                                                                        <input type="text" name="wiserrw_data[wiserrw_gcr_merchant_id]" id="wiserrw_gcr_merchant_id" value="<?php echo esc_attr( $gcr_merchant_id ); ?>" placeholder="<?php esc_attr_e( 'e.g. 123456789', 'wiser-review' ); ?>" autocomplete="off" inputmode="numeric" pattern="\d{8,15}" minlength="8" maxlength="15" aria-describedby="wiserrw_gcr_merchant_id_hint" <?php disabled( $is_disabled ); ?> />
     530                                                                        <span class="wr-gcr-field-hint" id="wiserrw_gcr_merchant_id_hint"><?php esc_html_e( '8–15 digits, numbers only.', 'wiser-review' ); ?></span>
     531                                                                        <span class="wr-gcr-field-error" id="wiserrw_gcr_merchant_id_error" hidden><?php esc_html_e( 'Merchant ID must be 8–15 digits (numbers only).', 'wiser-review' ); ?></span>
     532                                                                </div>
     533                                                                <div class="wr-gcr-field wr-gcr-field-narrow">
     534                                                                        <label class="wr-api-label" for="wiserrw_gcr_delivery_days"><?php esc_html_e( 'Delivery days', 'wiser-review' ); ?></label>
     535                                                                        <input type="number" min="1" step="1" name="wiserrw_data[wiserrw_gcr_delivery_days]" id="wiserrw_gcr_delivery_days" value="<?php echo esc_attr( $gcr_delivery_days ); ?>" <?php disabled( $is_disabled ); ?> />
     536                                                                </div>
     537                                                        </div>
     538
     539                                                        <div class="wr-gcr-save-row">
     540                                                                <button type="button" class="btn btn--primary wiserrw_gcr_save" <?php disabled( $is_disabled ); ?>><?php esc_html_e( 'Save Changes', 'wiser-review' ); ?></button>
     541                                                        </div>
     542                                                </div>
     543
     544                                                <!-- Main column: blocking Google-enrollment warning +
     545                                                manual-embed collapsible (needs the wider main column so the
     546                                                JS code block can breathe). The First-time setup collapsible
     547                                                lives in the right sidebar instead, since it's reference info
     548                                                a merchant reads once. -->
     549                                                <div class="wr-gcr-help wr-gcr-help-wrap<?php echo $gcr_is_on ? '' : ' wr-gcr-hidden'; ?>">
     550                                                        <div class="wr-gcr-help-warn">
     551                                                                <span class="dashicons dashicons-info wr-gcr-help-warn-icon"></span>
     552                                                                <span>
     553                                                                        <?php
     554                                                                        printf(
     555                                                                                /* translators: %s: link to Google's setup docs */
     556                                                                                esc_html__( 'Google requirement: enable Google Customer Reviews in your Merchant Center account and sign the agreement first. The opt-in only appears once Google verifies enrollment. %s', 'wiser-review' ),
     557                                                                                '<a href="https://support.google.com/merchants/answer/14629305" target="_blank" rel="noopener">' . esc_html__( "See Google's setup guide", 'wiser-review' ) . ' &rarr;</a>'
     558                                                                        );
     559                                                                        ?>
     560                                                                </span>
     561                                                        </div>
     562
     563                                                        <details class="wr-gcr-card wr-gcr-card-snippets">
     564                                                                <summary class="wr-gcr-card-summary">
     565                                                                        <span class="dashicons dashicons-editor-code wr-gcr-card-icon"></span>
     566                                                                        <span class="wr-gcr-card-title"><?php esc_html_e( 'Add it manually using shortcode or JS code', 'wiser-review' ); ?></span>
     567                                                                        <span class="dashicons dashicons-arrow-down-alt2 wr-gcr-card-chevron"></span>
     568                                                                </summary>
     569                                                                <div class="wr-gcr-card-body">
     570                                                                        <div style="background:#f6f7f7;border-left:3px solid #c3c4c7;padding:10px 12px;border-radius:0 4px 4px 0;margin:0 0 12px;font-size:13px;line-height:1.5;">
     571                                                                                <div style="margin-bottom:6px;">
     572                                                                                        <span style="color:#0a7d0a;font-weight:700;">✓</span>&nbsp;
     573                                                                                        <strong><?php esc_html_e( 'Standard WooCommerce Thank You page:', 'wiser-review' ); ?></strong>
     574                                                                                        <?php esc_html_e( 'nothing to paste — the opt-in appears automatically as soon as the toggle above is on.', 'wiser-review' ); ?>
     575                                                                                </div>
     576                                                                                <div>
     577                                                                                        <span style="color:#b57e00;font-weight:700;">⚠</span>&nbsp;
     578                                                                                        <strong><?php esc_html_e( 'Custom, external, or headless thank-you page:', 'wiser-review' ); ?></strong>
     579                                                                                        <?php esc_html_e( 'auto-install does NOT work — the plugin only hooks the stock Thank You page. If a theme, page-builder (Divi / Elementor), block-based checkout, or a JAMstack front-end replaces the default page, paste one of the snippets below wherever your order confirmation renders.', 'wiser-review' ); ?>
     580                                                                                </div>
     581                                                                        </div>
     582                                                                        <div class="wr-gcr-snippet-block">
     583                                                                                <div class="wr-gcr-snippet-label"><?php esc_html_e( 'Shortcode (paste on your custom thank-you page):', 'wiser-review' ); ?></div>
     584                                                                                <div class="wiserrw_copy_shortcode_wrapper">
     585                                                                                        <input readonly type="text" class="wiserrw_shortcode_input" value="[wiserrw_google_reviews]" id="wiserrw_google_reviews" />
     586                                                                                        <span class="dashicons dashicons-admin-page wiserrw_copy_shortcode action" data-shortcode="wiserrw_google_reviews"></span>
     587                                                                                        <span class="wiserrw_copied"><?php esc_html_e( 'Copied!', 'wiser-review' ); ?></span>
     588                                                                                </div>
     589                                                                        </div>
     590                                                                        <div class="wr-gcr-snippet-block">
     591                                                                                <div class="wr-gcr-snippet-label"><?php esc_html_e( "Google Customer Reviews JavaScript code (paste on your custom, external, or headless thank-you page and fill in your Merchant ID + order details):", 'wiser-review' ); ?></div>
     592                                                                                <div class="wr-gcr-code">
     593                                                                                        <button type="button" class="wr-gcr-copy-code" aria-label="<?php esc_attr_e( 'Copy code', 'wiser-review' ); ?>">
     594                                                                                                <span class="dashicons dashicons-admin-page"></span>
     595                                                                                                <span class="wr-gcr-copy-txt"><?php esc_html_e( 'Copy', 'wiser-review' ); ?></span>
     596                                                                                        </button>
     597                                                                                        <pre><code class="wr-gcr-js-snippet">&lt;script src="https://apis.google.com/js/platform.js?onload=renderGcrOptIn"&gt;&lt;/script&gt;
     598&lt;script&gt;
     599  window.renderGcrOptIn = function () {
     600    gapi.load('surveyoptin', function () {
     601      gapi.surveyoptin.render({
     602        "merchant_id": YOUR_MERCHANT_ID,
     603        "order_id": "ORDER_ID",
     604        "email": "CUSTOMER_EMAIL",
     605        "delivery_country": "US",
     606        "estimated_delivery_date": "YYYY-MM-DD"
     607      });
     608    });
     609  };
     610&lt;/script&gt;</code></pre>
     611                                                                                </div>
     612                                                                        </div>
     613                                                                </div>
     614                                                        </details>
     615                                                </div>
     616                                        </div>
     617                                </div>
     618
     619                                <div class="wr-feature-guide">
     620                                        <strong><?php esc_html_e( 'How it renders', 'wiser-review' ); ?></strong><br>
     621                                        <?php
     622                                        printf(
     623                                                /* translators: %s: <code>woocommerce_thankyou</code> */
     624                                                wp_kses(
     625                                                        __( 'Auto-injects on the WooCommerce thank-you page after checkout. Works with any theme or page builder that fires the standard %s hook.', 'wiser-review' ),
     626                                                        array( 'code' => array() )
     627                                                ),
     628                                                '<code>woocommerce_thankyou</code>'
     629                                        );
     630                                        ?>
     631
     632                                        <!-- First-time setup: compact <details> so a merchant can scan
     633                                        the 4-step enrollment path inline in the sidebar. Copy stays
     634                                        tight because Google's own guide (linked at the bottom of the
     635                                        card) is the source of truth if Merchant Center's UI shifts. -->
     636                                        <details class="wr-gcr-side-setup">
     637                                                <summary>
     638                                                        <span class="dashicons dashicons-info-outline"></span>
     639                                                        <span><?php esc_html_e( 'First-time setup (5 min)', 'wiser-review' ); ?></span>
     640                                                        <span class="dashicons dashicons-arrow-down-alt2 wr-gcr-side-chevron"></span>
     641                                                </summary>
     642                                                <div class="wr-gcr-side-setup-body">
     643                                                        <ol>
     644                                                                <li><?php esc_html_e( 'In Google Merchant Center, open Settings (gear icon) &rsaquo; Add-ons.', 'wiser-review' ); ?></li>
     645                                                                <li><?php esc_html_e( 'Under the Discover tab, find "Google Customer Reviews" and click Add.', 'wiser-review' ); ?></li>
     646                                                                <li><?php esc_html_e( 'Open the add-on under "Your Add-ons" and click Sign agreement.', 'wiser-review' ); ?></li>
     647                                                                <li><?php esc_html_e( 'Come back here. The opt-in starts appearing on your next order after Google verifies enrollment.', 'wiser-review' ); ?></li>
     648                                                        </ol>
     649                                                        <a href="https://support.google.com/merchants/answer/14629305" target="_blank" rel="noopener"><?php esc_html_e( "Google's setup guide", 'wiser-review' ); ?> &rarr;</a>
     650                                                </div>
     651                                        </details>
     652
     653                                        <br>
     654                                        <a href="https://support.google.com/merchants/answer/7106244" target="_blank" rel="noopener"><?php esc_html_e( 'About Google Customer Reviews', 'wiser-review' ); ?> &rarr;</a>
     655                                </div>
     656                        </div>
     657                </div>
     658        </div>
     659
     660        <script>
     661        ( function () {
     662                var section = document.getElementById( 'wiserrw_gcr_section' );
     663                if ( ! section ) { return; }
     664                var toggle      = document.getElementById( 'wiserrw_gcr_enabled_checkbox' );
     665                var merchantEl  = document.getElementById( 'wiserrw_gcr_merchant_id' );
     666                var errorEl     = document.getElementById( 'wiserrw_gcr_merchant_id_error' );
     667                var saveBtnEl   = section.querySelector( '.wiserrw_gcr_save' );
     668                var activeBadge = section.querySelector( '.wr-gcr-active-badge' );
     669                var midTooltip  = section.querySelector( '.wr-gcr-tooltip-mid' );
     670                var helpBlock   = section.querySelector( '.wr-gcr-help-wrap' );
     671
     672                // Merchant ID format: numeric, 8–15 digits.
     673                var MID_RE = /^\d{8,15}$/;
     674
     675                function midValue() { return merchantEl ? merchantEl.value.trim() : ''; }
     676                function midIsValid() { return MID_RE.test( midValue() ); }
     677
     678                // Strip non-digits as the user types so the field can only ever hold
     679                // digits. Prevents pasted formatted IDs (spaces, dashes) from silently
     680                // failing validation and confusing merchants.
     681                function sanitiseMid() {
     682                        if ( ! merchantEl ) { return; }
     683                        var cleaned = merchantEl.value.replace( /\D+/g, '' ).slice( 0, 15 );
     684                        if ( cleaned !== merchantEl.value ) { merchantEl.value = cleaned; }
     685                }
     686
     687                function syncMidError() {
     688                        if ( ! merchantEl || ! errorEl ) { return; }
     689                        var v = midValue();
     690                        // Show error only when the field has content but is invalid — an
     691                        // empty field is a "please fill in", not an error state.
     692                        var showErr = v.length > 0 && ! MID_RE.test( v );
     693                        errorEl.hidden = ! showErr;
     694                        merchantEl.classList.toggle( 'wr-gcr-input-invalid', showErr );
     695                }
     696
     697                // Live-gate the toggle on the Merchant ID input. Toggle becomes
     698                // clickable only once the field holds a well-formed 8–15 digit ID
     699                // (no save-then-reload). If the field is cleared or made invalid,
     700                // re-lock the toggle and auto-uncheck it so a stale ON state doesn't
     701                // persist without a valid Merchant ID.
     702                // Skip this gate entirely when the API-key gate is already applied —
     703                // data-api-locked wins.
     704                function syncToggleGate() {
     705                        if ( ! toggle ) { return; }
     706                        if ( toggle.getAttribute( 'data-api-locked' ) === '1' ) { return; }
     707                        var canEnable = midIsValid();
     708                        toggle.disabled = ! canEnable;
     709                        if ( saveBtnEl ) { saveBtnEl.disabled = ! canEnable; }
     710                        if ( midTooltip ) { midTooltip.style.display = canEnable ? 'none' : ''; }
     711                        if ( ! canEnable && toggle.checked ) {
     712                                toggle.checked = false;
     713                                syncBadge();
     714                        }
     715                }
     716                function syncBadge() {
     717                        if ( activeBadge ) { activeBadge.style.display = toggle && toggle.checked ? '' : 'none'; }
     718                }
     719                // Help block (status + enrollment note + collapsibles) is only useful
     720                // once the feature is on. Hide it entirely when the toggle is off so
     721                // merchants don't see instructions for something that isn't running.
     722                function syncHelpVisibility() {
     723                        if ( helpBlock ) { helpBlock.classList.toggle( 'wr-gcr-hidden', ! ( toggle && toggle.checked ) ); }
     724                }
     725                if ( merchantEl ) {
     726                        merchantEl.addEventListener( 'input', function () {
     727                                sanitiseMid();
     728                                syncMidError();
     729                                syncToggleGate();
     730                        } );
     731                        // Run once on page load in case a stored value is malformed
     732                        // (legacy installs, manual edits) — surfaces the error early.
     733                        sanitiseMid();
     734                        syncMidError();
     735                }
     736                if ( toggle ) {
     737                        toggle.addEventListener( 'change', function () {
     738                                syncBadge();
     739                                syncHelpVisibility();
     740                        } );
     741                }
     742
     743                // Shared AJAX save (wiserrw_save_settings) — persists ALL settings WITHOUT re-verifying the API key.
     744                function ajaxSave() {
     745                        var form = document.getElementById( 'wiserrw_plugin_settings_form' );
     746                        if ( ! form || typeof wiserrw_ajax_var === 'undefined' ) { return Promise.reject(); }
     747                        var params = new URLSearchParams( new FormData( form ) );
     748                        params.append( 'action', 'wiserrw_save_settings' );
     749                        params.append( 'nonce', wiserrw_ajax_var.nonce );
     750                        return fetch( wiserrw_ajax_var.url, {
     751                                method: 'POST',
     752                                headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
     753                                body: params.toString(),
     754                                credentials: 'same-origin'
     755                        } ).then( function ( r ) { return r.json(); } );
     756                }
     757
     758                // "Save Changes" — persists Merchant ID + Delivery days, then reloads
     759                // so the server recomputes the Active badge / configured state.
     760                var saveBtn = section.querySelector( '.wiserrw_gcr_save' );
     761                if ( saveBtn ) {
     762                        saveBtn.addEventListener( 'click', function () {
     763                                if ( typeof wiserrw_ajax_var === 'undefined' ) { return; }
     764                                var original = saveBtn.textContent;
     765                                saveBtn.disabled = true;
     766                                saveBtn.textContent = '<?php echo esc_js( __( 'Saving…', 'wiser-review' ) ); ?>';
     767                                ajaxSave()
     768                                        .then( function () { window.location.reload(); } )
     769                                        .catch( function () {
     770                                                saveBtn.disabled = false;
     771                                                saveBtn.textContent = original;
     772                                        } );
     773                        } );
     774                }
     775
     776                // Copy the Google script-block snippet.
     777                var copyCodeBtn = section.querySelector( '.wr-gcr-copy-code' );
     778                if ( copyCodeBtn ) {
     779                        copyCodeBtn.addEventListener( 'click', function () {
     780                                var codeEl = section.querySelector( '.wr-gcr-js-snippet' );
     781                                if ( ! codeEl ) { return; }
     782                                var text  = codeEl.innerText;
     783                                var label = copyCodeBtn.querySelector( '.wr-gcr-copy-txt' );
     784                                function flash() {
     785                                        if ( ! label ) { return; }
     786                                        var orig = label.getAttribute( 'data-orig' ) || label.textContent;
     787                                        label.setAttribute( 'data-orig', orig );
     788                                        label.textContent = '<?php echo esc_js( __( 'Copied!', 'wiser-review' ) ); ?>';
     789                                        copyCodeBtn.classList.add( 'is-copied' );
     790                                        setTimeout( function () {
     791                                                label.textContent = orig;
     792                                                copyCodeBtn.classList.remove( 'is-copied' );
     793                                        }, 1500 );
     794                                }
     795                                if ( navigator.clipboard && navigator.clipboard.writeText ) {
     796                                        navigator.clipboard.writeText( text ).then( flash ).catch( function () {} );
     797                                } else {
     798                                        var ta = document.createElement( 'textarea' );
     799                                        ta.value = text;
     800                                        document.body.appendChild( ta );
     801                                        ta.select();
     802                                        try { document.execCommand( 'copy' ); flash(); } catch ( e ) {}
     803                                        document.body.removeChild( ta );
     804                                }
     805                        } );
     806                }
     807
     808                syncToggleGate();
     809                syncBadge();
     810                syncHelpVisibility();
     811        } )();
     812        </script>
     813
    443814        <!-- ─── 5. Clear Cache ─── -->
    444815        <div class="wr-actions-bar wr-order-5">
    445                 <a class="btn btn--secondary btn--small" href="<?php echo esc_url( admin_url( 'admin.php?page=wiser-review&wiser_clear_cache' ) ); ?>"><?php esc_html_e( 'Clear Cache', 'wiser-review' ); ?></a>
     816                <a class="btn btn--secondary btn--small" href="<?php echo esc_url( admin_url( 'admin.php?page=wiser-review&wiserrw_clear_cache' ) ); ?>"><?php esc_html_e( 'Clear Cache', 'wiser-review' ); ?></a>
    446817                <span class="wr-actions-bar-label"><?php wiserrw_check_rest_api(); ?></span>
    447818        </div>
    … …  
    564935</div>
    565936
    566 <!-- ─── 8. FAQ ─── -->
    567 <div class="wr-section wr-order-8">
     937<!-- ─── 9. FAQ (renumbered from 8; Google Customer Reviews now sits at wr-order-8 just above the FAQ) ─── -->
     938<div class="wr-section wr-order-9">
    568939        <div class="wr-section-head">
    569940                <h2><?php esc_html_e( 'Frequently Asked Questions', 'wiser-review' ); ?></h2>
  • wiser-review/trunk/wiser-review.php

    r3559378 r3666763  
    44 * Plugin URI: https://wiserreview.com
    55 * Description: Collect, manage, and display powerful product reviews and testimonials in WooCommerce using the WiserReview platform. Boost trust and conversion with automated review collection.
    6  * Version: 3.6
     6 * Version: 3.7
    77 * Author: Wiser Notify
    88 * Author URI: https://wiserreview.com
    … …  
    2424define( 'WISERRW_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
    2525define( 'WISERRW_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
    26 define( 'WISERRW_PLUGIN_VERSION', '3.6' );
     26define( 'WISERRW_PLUGIN_VERSION', '3.7' );
    2727define( 'WISERRW_API_HOST', 'https://api.wiserreview.com/api/woocommerce/' );
    2828define( 'WISERRW_RS_HOST', 'https://rs.wiserreview.com' );
    … …  
    7979                $wiserrw_data['wiserrw_review_form_url'] = rtrim( strtok( trim( $wiserrw_data['wiserrw_review_form_url'] ), '?' ), '/' );
    8080        }
     81
     82        // Sanitize Google Merchant ID: digits only, 8–15 chars. Anything else
     83        // blanks the field AND force-disables the GCR toggle so a malformed ID
     84        // can't leave the feature "on" with a broken value.
     85        if ( isset( $wiserrw_data['wiserrw_gcr_merchant_id'] ) ) {
     86                $gcr_mid_clean = preg_replace( '/\D+/', '', (string) $wiserrw_data['wiserrw_gcr_merchant_id'] );
     87                if ( '' !== $gcr_mid_clean && preg_match( '/^\d{8,15}$/', $gcr_mid_clean ) ) {
     88                        $wiserrw_data['wiserrw_gcr_merchant_id'] = $gcr_mid_clean;
     89                } else {
     90                        $wiserrw_data['wiserrw_gcr_merchant_id'] = '';
     91                        unset( $wiserrw_data['wiserrw_gcr_enabled'] );
     92                }
     93        }
    8194        // Force review page toggle off if URL is invalid
    8295        $review_url = $wiserrw_data['wiserrw_review_form_url'] ?? '';
    … …  
    270283}
    271284add_action( 'wp_footer', 'wiserr_review_script' );
     285
     286/* ─────────────────────────────────────────────────────────────────────────
     287 * Google Customer Reviews (Google Review Collector)
     288 *
     289 * Self-contained: the plugin renders Google's opt-in survey directly on the
     290 * order-confirmation page using the Merchant ID + delivery-days configured on
     291 * the settings page. No WiserReview backend / reviewPixel.js involvement.
     292 * Three implementation methods: automatic (woocommerce_thankyou), shortcode,
     293 * and a JavaScript API for custom/headless checkouts.
     294 * ──────────────────────────────────────────────────────────────────────── */
     295
     296/**
     297 * Settings array for the Google Customer Reviews feature.
     298 */
     299function wiserrw_gcr_settings() {
     300    return (array) get_option( 'wiserrw_api_settings', array() );
     301}
     302
     303/**
     304 * Whether the feature is active. Gated on Live Mode + verified API key
     305 * (same requirement as the other widgets), the enable toggle, and a Merchant ID.
     306 */
     307function wiserrw_gcr_is_active() {
     308    $s = wiserrw_gcr_settings();
     309    if ( empty( $s['wiserrw_live_mode_checkbox'] ) ) {
     310        return false;
     311    }
     312    if ( (int) get_option( 'wiserrw_api_verified', 0 ) !== 1 ) {
     313        return false;
     314    }
     315    if ( empty( $s['wiserrw_gcr_enabled'] ) ) {
     316        return false;
     317    }
     318    if ( empty( $s['wiserrw_gcr_merchant_id'] ) ) {
     319        return false;
     320    }
     321    return true;
     322}
     323
     324/**
     325 * Estimated delivery days (defaults to 7).
     326 */
     327function wiserrw_gcr_delivery_days() {
     328    $s = wiserrw_gcr_settings();
     329    $d = isset( $s['wiserrw_gcr_delivery_days'] ) ? (int) $s['wiserrw_gcr_delivery_days'] : 7;
     330    return $d > 0 ? $d : 7;
     331}
     332
     333/**
     334 * estimated_delivery_date for Google (YYYY-MM-DD) = today + configured delivery days.
     335 */
     336function wiserrw_gcr_estimated_delivery_date() {
     337    return gmdate( 'Y-m-d', time() + ( wiserrw_gcr_delivery_days() * DAY_IN_SECONDS ) );
     338}
     339
     340/**
     341 * Build the Google survey payload from a WooCommerce order.
     342 */
     343function wiserrw_gcr_build_payload( $order ) {
     344    if ( ! $order instanceof WC_Order ) {
     345        return null;
     346    }
     347    $s = wiserrw_gcr_settings();
     348
     349    $payload = array(
     350        'merchant_id'             => (string) ( $s['wiserrw_gcr_merchant_id'] ?? '' ),
     351        'order_id'                => (string) $order->get_order_number(),
     352        'email'                   => $order->get_billing_email(),
     353        // Google wants the delivery country; prefer shipping, fall back to billing.
     354        'delivery_country'        => $order->get_shipping_country() ?: $order->get_billing_country(),
     355        'estimated_delivery_date' => wiserrw_gcr_estimated_delivery_date(),
     356    );
     357    return $payload;
     358}
     359
     360/**
     361 * Print Google's opt-in survey for a given order. Shared by the automatic and shortcode methods.
     362 */
     363function wiserrw_gcr_render_optin( $order ) {
     364    $payload = wiserrw_gcr_build_payload( $order );
     365    if ( empty( $payload ) || empty( $payload['merchant_id'] ) ) {
     366        return;
     367    }
     368    ?>
     369    <script>
     370        window.wiserRenderGcrOptIn = function () {
     371            try {
     372                window.gapi.load('surveyoptin', function () {
     373                    window.gapi.surveyoptin.render(<?php echo wp_json_encode( $payload ); ?>);
     374                });
     375            } catch (e) { console.log(e); }
     376        };
     377    </script>
     378    <script src="https://apis.google.com/js/platform.js?onload=wiserRenderGcrOptIn" async defer></script>
     379    <?php
     380}
     381
     382/**
     383 * Method 1 — Automatic: render on the standard WooCommerce thank-you page.
     384 * Always attempts when GCR is active; no picker/method setting to gate on
     385 * (matches how the plugin's other widgets auto-attach to WC hooks). If the
     386 * theme doesn't fire woocommerce_thankyou (headless / heavily-customized
     387 * checkout), this is a silent no-op and the merchant falls back to the
     388 * shortcode or JS API — both are always available for that reason.
     389 */
     390function wiserrw_gcr_thankyou( $order_id ) {
     391    if ( ! wiserrw_gcr_is_active() ) {
     392        return;
     393    }
     394    $order = wc_get_order( $order_id );
     395    if ( $order ) {
     396        wiserrw_gcr_render_optin( $order );
     397    }
     398}
     399add_action( 'woocommerce_thankyou', 'wiserrw_gcr_thankyou', 20 );
     400
     401/**
     402 * Method 2 — Shortcode: [wiserrw_google_reviews] for custom / replaced thank-you pages.
     403 * Resolves the order from the order-received page and verifies the order key.
     404 * Always registered when GCR is active; merchants who need it on a custom
     405 * page just paste it.
     406 */
     407function wiserrw_gcr_shortcode() {
     408    if ( ! wiserrw_gcr_is_active() ) {
     409        return '';
     410    }
     411
     412    $order_id = absint( get_query_var( 'order-received' ) );
     413    if ( ! $order_id && isset( $_GET['order-received'] ) ) {
     414        $order_id = absint( wp_unslash( $_GET['order-received'] ) );
     415    }
     416    if ( ! $order_id ) {
     417        return '';
     418    }
     419
     420    $order = wc_get_order( $order_id );
     421    if ( ! $order ) {
     422        return '';
     423    }
     424    // Verify the order key so the shortcode can't be used to render an arbitrary order.
     425    $key = isset( $_GET['key'] ) ? sanitize_text_field( wp_unslash( $_GET['key'] ) ) : '';
     426    if ( $key && ! hash_equals( $order->get_order_key(), $key ) ) {
     427        return '';
     428    }
     429
     430    ob_start();
     431    wiserrw_gcr_render_optin( $order );
     432    return ob_get_clean();
     433}
     434add_shortcode( 'wiserrw_google_reviews', 'wiserrw_gcr_shortcode' );
    272435
    273436
    … …  
    512675
    513676        // Extract wrapper
    514         $wiser_open_tag = '';
     677        $wiserrw_open_tag = '';
    515678        if ( preg_match( '/<div\b[^>]*>/i', $product_rating_count, $matches ) ) {
    516                 $wiser_open_tag = $matches[0];
     679                $wiserrw_open_tag = $matches[0];
    517680
    518681                // Ensure opid
    519                 if ( preg_match( '/data-opid=/', $wiser_open_tag ) ) {
    520                         $wiser_open_tag = preg_replace( '/data-opid=("|\')(.*?)\1/', 'data-opid="' . $opid_value . '"', $wiser_open_tag );
     682                if ( preg_match( '/data-opid=/', $wiserrw_open_tag ) ) {
     683                        $wiserrw_open_tag = preg_replace( '/data-opid=("|\')(.*?)\1/', 'data-opid="' . $opid_value . '"', $wiserrw_open_tag );
    521684                } else {
    522                         $wiser_open_tag = str_replace( '<div', '<div data-opid="' . esc_attr( $opid_value ) . '"', $wiser_open_tag );
     685                        $wiserrw_open_tag = str_replace( '<div', '<div data-opid="' . esc_attr( $opid_value ) . '"', $wiserrw_open_tag );
    523686                }
    524687
    525688                // Ensure lang
    526689                if ( ! empty( $current_lang ) ) {
    527                         if ( preg_match( '/data-lang=/', $wiser_open_tag ) ) {
    528                                 $wiser_open_tag = preg_replace( '/data-lang=("|\')(.*?)\1/', 'data-lang="' . $current_lang . '"', $wiser_open_tag );
     690                        if ( preg_match( '/data-lang=/', $wiserrw_open_tag ) ) {
     691                                $wiserrw_open_tag = preg_replace( '/data-lang=("|\')(.*?)\1/', 'data-lang="' . $current_lang . '"', $wiserrw_open_tag );
    529692                        } else {
    530                                 $wiser_open_tag = str_replace( '<div', '<div data-lang="' . esc_attr( $current_lang ) . '"', $wiser_open_tag );
     693                                $wiserrw_open_tag = str_replace( '<div', '<div data-lang="' . esc_attr( $current_lang ) . '"', $wiserrw_open_tag );
    531694                        }
    532695                }
    … …  
    556719        // Build output
    557720        $output = '';
    558         if ( $wiser_open_tag ) {
    559                 $output .= $wiser_open_tag;
     721        if ( $wiserrw_open_tag ) {
     722                $output .= $wiserrw_open_tag;
    560723        }
    561724        $output .= $inner_html;
    … …  
    668831
    669832        // --- Extract the wrapper div tag ---
    670         $wiser_open_tag = '';
     833        $wiserrw_open_tag = '';
    671834        if ( preg_match( '/<div\b[^>]*>/i', $product_review_section, $matches ) ) {
    672                 $wiser_open_tag = $matches[0];
     835                $wiserrw_open_tag = $matches[0];
    673836
    674837                // --- Ensure opid for wrapper ---
    675                 if ( preg_match( '/data-opid=/', $wiser_open_tag ) ) {
    676                         $wiser_open_tag = preg_replace( '/data-opid=("|\')(.*?)\1/', 'data-opid="' . $opid_value . '"', $wiser_open_tag );
     838                if ( preg_match( '/data-opid=/', $wiserrw_open_tag ) ) {
     839                        $wiserrw_open_tag = preg_replace( '/data-opid=("|\')(.*?)\1/', 'data-opid="' . $opid_value . '"', $wiserrw_open_tag );
    677840                } else {
    678                         $wiser_open_tag = str_replace( '<div', '<div data-opid="' . esc_attr( $opid_value ) . '"', $wiser_open_tag );
     841                        $wiserrw_open_tag = str_replace( '<div', '<div data-opid="' . esc_attr( $opid_value ) . '"', $wiserrw_open_tag );
    679842                }
    680843
    681844                // --- Ensure lang for wrapper ---
    682845                if ( ! empty( $current_lang ) ) {
    683                         if ( preg_match( '/data-lang=/', $wiser_open_tag ) ) {
    684                                 $wiser_open_tag = preg_replace( '/data-lang=("|\')(.*?)\1/', 'data-lang="' . $current_lang . '"', $wiser_open_tag );
     846                        if ( preg_match( '/data-lang=/', $wiserrw_open_tag ) ) {
     847                                $wiserrw_open_tag = preg_replace( '/data-lang=("|\')(.*?)\1/', 'data-lang="' . $current_lang . '"', $wiserrw_open_tag );
    685848                        } else {
    686                                 $wiser_open_tag = str_replace( '<div', '<div data-lang="' . esc_attr( $current_lang ) . '"', $wiser_open_tag );
     849                                $wiserrw_open_tag = str_replace( '<div', '<div data-lang="' . esc_attr( $current_lang ) . '"', $wiserrw_open_tag );
    687850                        }
    688851                }
    … …  
    712875        // Build output
    713876        $output = '';
    714         if ( $wiser_open_tag ) {
    715                 $output .= $wiser_open_tag;
     877        if ( $wiserrw_open_tag ) {
     878                $output .= $wiserrw_open_tag;
    716879        }
    717880        $output .= $inner_html;
    … …  
    9101073        }
    9111074
     1075        // Only fulfilled orders — an un-shipped / cancelled / refunded order
     1076        // shouldn't invite a review. Matches the inline order-item button gate.
    9121077        $orders = wc_get_orders( array(
    9131078                'customer_id' => $customer_id,
    914                 'status'      => array( 'completed', 'processing' ),
     1079                'status'      => array( 'completed' ),
    9151080                'limit'       => -1,
    9161081                'orderby'     => 'date',
    … …  
    11061271                text-decoration: none; opacity: 0.85; transition: opacity .15s; margin-top: 6px;
    11071272        }
    1108         .wiserrw-inline-review-link:hover { opacity: 1; text-decoration: none; }
    1109         .woocommerce-MyAccount-navigation ul li.woocommerce-MyAccount-navigation-link--leave-a-review a::before {
    1110                 content: "";
    1111                 display: inline-block;
    1112                 width: 16px; height: 16px;
    1113                 margin-right: 6px;
    1114                 vertical-align: middle;
    1115                 background: currentColor;
    1116                 -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'%3E%3Cpath d=\'M10 1.12l2.77 5.61 6.19.9-4.48 4.37 1.06 6.17L10 15.27l-5.54 2.9 1.06-6.17L1.04 7.63l6.19-.9L10 1.12z\'/%3E%3C/svg%3E") center/contain no-repeat;
    1117                 mask: url("data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'%3E%3Cpath d=\'M10 1.12l2.77 5.61 6.19.9-4.48 4.37 1.06 6.17L10 15.27l-5.54 2.9 1.06-6.17L1.04 7.63l6.19-.9L10 1.12z\'/%3E%3C/svg%3E") center/contain no-repeat;
    1118         }';
     1273        .wiserrw-inline-review-link:hover { opacity: 1; text-decoration: none; }';
     1274
     1275        // Star icon prefix on the "Leave a Review" menu item. Default: shown.
     1276        // Merchants on minimalist themes can opt out via the sub-checkbox in the
     1277        // settings page (key 'wiserrw_review_menu_icon_hidden'). Existing stores
     1278        // without the key set keep the icon — preserves the look they shipped with.
     1279        if ( empty( $settings['wiserrw_review_menu_icon_hidden'] ) ) {
     1280                $css .= '
     1281                .woocommerce-MyAccount-navigation ul li.woocommerce-MyAccount-navigation-link--leave-a-review a::before {
     1282                        content: "";
     1283                        display: inline-block;
     1284                        width: 16px; height: 16px;
     1285                        margin-right: 6px;
     1286                        vertical-align: middle;
     1287                        background: currentColor;
     1288                        -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'%3E%3Cpath d=\'M10 1.12l2.77 5.61 6.19.9-4.48 4.37 1.06 6.17L10 15.27l-5.54 2.9 1.06-6.17L1.04 7.63l6.19-.9L10 1.12z\'/%3E%3C/svg%3E") center/contain no-repeat;
     1289                        mask: url("data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'%3E%3Cpath d=\'M10 1.12l2.77 5.61 6.19.9-4.48 4.37 1.06 6.17L10 15.27l-5.54 2.9 1.06-6.17L1.04 7.63l6.19-.9L10 1.12z\'/%3E%3C/svg%3E") center/contain no-repeat;
     1290                }';
     1291        }
    11191292        wp_register_style( 'wiserrw-review-link', false );
    11201293        wp_enqueue_style( 'wiserrw-review-link' );
    … …  
    11291302        }
    11301303        if ( ! $item instanceof WC_Order_Item_Product ) {
     1304                return;
     1305        }
     1306        // Only show for fulfilled orders — an un-shipped / cancelled / refunded
     1307        // order shouldn't invite a review. Matches WooCommerce's convention that
     1308        // "Completed" means the customer has physically received the product.
     1309        if ( ! $order || 'completed' !== $order->get_status() ) {
    11311310                return;
    11321311        }
    … …  
    32303409
    32313410function wiserrw_clear_wp_cache() {
    3232     if( isset( $_GET['wiser_clear_cache'] ) && ( current_user_can( 'administrator' ) || current_user_can( 'editor' ) ) ) {
     3411    if( isset( $_GET['wiserrw_clear_cache'] ) && ( current_user_can( 'administrator' ) || current_user_can( 'editor' ) ) ) {
    32333412   
    32343413                global $wp_fastest_cache;
Note: See TracChangeset for help on using the changeset viewer.