Plugin Directory

Changeset 3730384


Ignore:
Timestamp:
10/06/2026 09:22:05 AM (3 weeks ago)
Author:
wpmatcha
Message:

Releasing 1.1.0

Location:
salespulse
Files:
61 added
13 edited

Legend:

Unmodified
Added
Removed
  • salespulse/trunk/admin/class-admin.php

    r3480727 r3730384  
    2828                add_action('admin_menu', array($this, 'register_menu'));
    2929                add_action('admin_enqueue_scripts', array($this, 'enqueue_assets'));
     30                add_action('wp_ajax_salespulse_dismiss_review', array($this, 'ajax_dismiss_review'));
    3031        }
    3132
    … …  
    175176                return apply_filters('salespulse_template_list', $templates);
    176177        }
     178
     179        /**
     180         * Check whether the review request notice should be displayed.
     181         *
     182         * Conditions:
     183         * 1. User has not permanently dismissed it.
     184         * 2. User has not snoozed it (or snooze time expired).
     185         * 3. Either installed >= 3 days ago OR total recorded impressions >= 15.
     186         *
     187         * @return bool
     188         */
     189        public static function should_show_review_notice()
     190        {
     191                if ('yes' === get_option('salespulse_review_dismissed')) {
     192                        return false;
     193                }
     194
     195                $snooze_until = (int) get_option('salespulse_review_snooze_until', 0);
     196                if ($snooze_until && time() < $snooze_until) {
     197                        return false;
     198                }
     199
     200                $install_time = (int) get_option('salespulse_install_time', 0);
     201                if (!$install_time) {
     202                        // For existing installs, backdate by 3 days so active users see the prompt.
     203                        $install_time = time() - (3 * DAY_IN_SECONDS);
     204                        update_option('salespulse_install_time', $install_time);
     205                }
     206
     207                if ((time() - $install_time) >= (3 * DAY_IN_SECONDS)) {
     208                        return true;
     209                }
     210
     211                // Or show if they have active impressions.
     212                global $wpdb;
     213                $table = $wpdb->prefix . 'salespulse_stats';
     214                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
     215                $has_impressions = (int) $wpdb->get_var("SELECT COUNT(id) FROM {$table} WHERE event_type = 'impression'");
     216                if ($has_impressions >= 15) {
     217                        return true;
     218                }
     219
     220                return false;
     221        }
     222
     223        /**
     224         * AJAX handler for dismissing or snoozing the review notice.
     225         */
     226        public function ajax_dismiss_review()
     227        {
     228                check_ajax_referer('salespulse_admin', '_ajax_nonce');
     229                if (!current_user_can('manage_options')) {
     230                        wp_send_json_error(array('message' => 'Unauthorized'), 403);
     231                }
     232
     233                $dismiss_type = sanitize_key($_POST['dismiss_type'] ?? 'permanent'); // phpcs:ignore WordPress.Security.NonceVerification.Missing
     234
     235                if ('snooze' === $dismiss_type) {
     236                        update_option('salespulse_review_snooze_until', time() + (7 * DAY_IN_SECONDS));
     237                } else {
     238                        update_option('salespulse_review_dismissed', 'yes');
     239                }
     240
     241                wp_send_json_success();
     242        }
    177243}
  • salespulse/trunk/admin/css/admin.css

    r3486898 r3730384  
    49784978  font-size: 18px;
    49794979}
     4980
     4981/* ── Review Request Banner ─────────────────────────────── */
     4982.sp-review-banner {
     4983  display: flex;
     4984  align-items: center;
     4985  gap: 16px;
     4986  margin: 16px 24px 0;
     4987  padding: 14px 20px;
     4988  background: linear-gradient(135deg, #f0f7f2 0%, #e8f2eb 100%);
     4989  border: 1px solid rgba(96, 125, 102, 0.3);
     4990  border-radius: var(--sp-radius-sm);
     4991  box-shadow: var(--sp-shadow);
     4992  transition: opacity 0.3s ease, transform 0.3s ease;
     4993}
     4994
     4995.sp-review-banner-badge {
     4996  width: 40px;
     4997  height: 40px;
     4998  border-radius: 10px;
     4999  background: var(--sp-primary);
     5000  display: flex;
     5001  align-items: center;
     5002  justify-content: center;
     5003  flex-shrink: 0;
     5004}
     5005
     5006.sp-review-banner-badge .material-symbols-outlined {
     5007  color: #fff;
     5008  font-size: 22px;
     5009}
     5010
     5011.sp-review-banner-text {
     5012  flex: 1;
     5013  min-width: 0;
     5014}
     5015
     5016.sp-review-banner-text strong {
     5017  display: block;
     5018  font-size: 14px;
     5019  font-weight: 600;
     5020  color: var(--sp-dark);
     5021  margin-bottom: 2px;
     5022}
     5023
     5024.sp-review-banner-text p {
     5025  margin: 0;
     5026  font-size: 13px;
     5027  color: var(--sp-text-light);
     5028  line-height: 1.4;
     5029}
     5030
     5031.sp-review-banner-actions {
     5032  display: flex;
     5033  align-items: center;
     5034  gap: 8px;
     5035  flex-shrink: 0;
     5036}
     5037
     5038.sp-review-btn-primary {
     5039  display: inline-flex;
     5040  align-items: center;
     5041  gap: 6px;
     5042  padding: 8px 16px;
     5043  background: var(--sp-primary);
     5044  color: #fff !important;
     5045  text-decoration: none;
     5046  border-radius: var(--sp-radius-sm);
     5047  font-size: 13px;
     5048  font-weight: 600;
     5049  border: none;
     5050  cursor: pointer;
     5051  transition: background var(--sp-transition);
     5052}
     5053
     5054.sp-review-btn-primary:hover {
     5055  background: var(--sp-primary-hover);
     5056}
     5057
     5058.sp-review-btn-primary .material-symbols-outlined {
     5059  font-size: 18px;
     5060}
     5061
     5062.sp-review-btn-secondary {
     5063  display: inline-flex;
     5064  align-items: center;
     5065  padding: 8px 14px;
     5066  background: transparent;
     5067  color: var(--sp-text-light);
     5068  border: 1px solid var(--sp-border);
     5069  border-radius: var(--sp-radius-sm);
     5070  font-size: 13px;
     5071  font-weight: 500;
     5072  cursor: pointer;
     5073  transition: all var(--sp-transition);
     5074}
     5075
     5076.sp-review-btn-secondary:hover {
     5077  background: #fff;
     5078  color: var(--sp-dark);
     5079  border-color: #cbd5e1;
     5080}
     5081
     5082.sp-review-btn-dismiss {
     5083  display: inline-flex;
     5084  align-items: center;
     5085  justify-content: center;
     5086  width: 32px;
     5087  height: 32px;
     5088  background: transparent;
     5089  border: none;
     5090  border-radius: var(--sp-radius-sm);
     5091  color: var(--sp-text-light);
     5092  cursor: pointer;
     5093  transition: all var(--sp-transition);
     5094}
     5095
     5096.sp-review-btn-dismiss:hover {
     5097  background: rgba(0, 0, 0, 0.06);
     5098  color: var(--sp-dark);
     5099}
     5100
     5101.sp-review-btn-dismiss .material-symbols-outlined {
     5102  font-size: 18px;
     5103}
  • salespulse/trunk/admin/js/admin.js

    r3486898 r3730384  
    18691869  }
    18701870
     1871  // ── Review Request Banner ────────────────────────────────
     1872  function initReviewBanner() {
     1873    const banner = $("#sp-review-banner");
     1874    if (!banner) return;
     1875
     1876    banner.querySelectorAll(".sp-btn-review-action").forEach((btn) => {
     1877      btn.addEventListener("click", () => {
     1878        const action = btn.dataset.action; // 'review', 'snooze', or 'permanent'
     1879
     1880        // Smoothly fade out and remove banner
     1881        banner.style.opacity = "0";
     1882        setTimeout(() => banner.remove(), 300);
     1883
     1884        // Send AJAX preference to backend
     1885        const formData = new FormData();
     1886        formData.append("action", "salespulse_dismiss_review");
     1887        formData.append("_ajax_nonce", config.ajaxNonce);
     1888        formData.append("dismiss_type", action === "snooze" ? "snooze" : "permanent");
     1889
     1890        fetch(config.ajaxUrl, {
     1891          method: "POST",
     1892          body: formData,
     1893        }).catch((err) => console.error("Review dismiss error:", err));
     1894      });
     1895    });
     1896  }
     1897
    18711898  // ── Init ─────────────────────────────────────────────────
    18721899  document.addEventListener("DOMContentLoaded", () => {
    … …  
    18761903    initWizard();
    18771904    initQuickActions();
     1905    initReviewBanner();
    18781906    loadSettings();
    18791907    loadNotifications();
  • salespulse/trunk/admin/views/admin-page.php

    r3486898 r3730384  
    7878                        <!-- Toast notification -->
    7979                        <div id="salespulse-toast" class="salespulse-toast" aria-live="polite"></div>
     80
     81                        <?php if ( \SalesPulse\Admin::should_show_review_notice() ) : ?>
     82                        <!-- Review Prompt Banner -->
     83                        <div class="sp-review-banner" id="sp-review-banner">
     84                                <div class="sp-review-banner-badge">
     85                                        <span class="material-symbols-outlined">star</span>
     86                                </div>
     87                                <div class="sp-review-banner-text">
     88                                        <strong><?php esc_html_e( 'Loving SalesPulse? Support our free plugin!', 'salespulse' ); ?></strong>
     89                                        <p><?php esc_html_e( 'If SalesPulse is helping your store build social proof and sales, please take 30 seconds to rate us 5 stars on WordPress.org. It helps independent WordPress developers keep building great free tools!', 'salespulse' ); ?></p>
     90                                </div>
     91                                <div class="sp-review-banner-actions">
     92                                        <a href="https://wordpress.org/support/plugin/salespulse/reviews/#new-post" target="_blank" rel="noopener noreferrer" class="sp-review-btn-primary sp-btn-review-action" data-action="review">
     93                                                <span class="material-symbols-outlined">rate_review</span>
     94                                                <?php esc_html_e( 'Leave a 5-Star Review', 'salespulse' ); ?>
     95                                        </a>
     96                                        <button type="button" class="sp-review-btn-secondary sp-btn-review-action" data-action="snooze">
     97                                                <?php esc_html_e( 'Maybe Later', 'salespulse' ); ?>
     98                                        </button>
     99                                        <button type="button" class="sp-review-btn-dismiss sp-btn-review-action" data-action="permanent" title="<?php esc_attr_e( 'Dismiss permanently', 'salespulse' ); ?>">
     100                                                <span class="material-symbols-outlined">close</span>
     101                                        </button>
     102                                </div>
     103                        </div>
     104                        <?php endif; ?>
    80105
    81106        <!-- ================================================================
  • salespulse/trunk/frontend/class-frontend.php

    r3486898 r3730384  
    197197                        'trackUrl' => esc_url_raw(rest_url('salespulse/v1/track')),
    198198                        'captureUrl' => esc_url_raw(rest_url('salespulse/v1/capture')),
     199                        'queueUrl' => esc_url_raw(rest_url('salespulse/v1/queue')),
    199200                        'nonce' => wp_create_nonce('wp_rest'),
    200201                        'pageId' => $page_id,
  • salespulse/trunk/frontend/js/salespulse.js

    r3486898 r3730384  
    6161                }
    6262
    63                 // Skip popup logic if no queue.
    64                 if ( ! queue.length ) return;
    65 
    6663                container = document.getElementById( 'salespulse-popup-container' );
    6764                if ( ! container ) return;
    … …  
    7774                initSmartCapture();
    7875
    79                 // Start displaying after initial delay.
    80                 setTimeout( showNext, settings.initialDelay || 3000 );
     76                if ( queue.length ) {
     77                        // Start displaying from pre-rendered queue.
     78                        setTimeout( showNext, settings.initialDelay || 3000 );
     79                } else if ( data.queueUrl ) {
     80                        // Page-cache fallback: Fetch fresh queue asynchronously
     81                        fetch( data.queueUrl + '?page_id=' + encodeURIComponent( data.pageId || 'global' ) )
     82                                .then( function ( r ) { return r.json(); } )
     83                                .then( function ( res ) {
     84                                        if ( res && res.success && Array.isArray( res.queue ) && res.queue.length ) {
     85                                                res.queue.forEach( function ( item ) {
     86                                                        if ( item.type === 'notification_bar' ) {
     87                                                                initIndividualBar( item );
     88                                                        } else {
     89                                                                queue.push( item );
     90                                                        }
     91                                                } );
     92                                                if ( queue.length ) {
     93                                                        setTimeout( showNext, settings.initialDelay || 3000 );
     94                                                }
     95                                        }
     96                                } )
     97                                .catch( function () {} );
     98                }
    8199        }
    82100
    … …  
    198216                        popup.addEventListener( 'click', function ( e ) {
    199217                                if ( e.target.classList.contains( 'sp-notification-close' ) ) return;
    200                                 trackEvent( notification.id, 'click', notification.type );
     218                                trackEvent( notification.id, 'click', notification.type, notification.ab_variant, notification.ab_test_id );
    201219                                window.open( notification.link, '_blank', 'noopener' );
    202220                        } );
    … …  
    275293
    276294                // Track impression.
    277                 trackEvent( notification.id, 'impression', notification.type );
     295                trackEvent( notification.id, 'impression', notification.type, notification.ab_variant, notification.ab_test_id );
    278296
    279297                // Initialize any countdown timer inside this popup.
    … …  
    366384
    367385        // ── Analytics Tracking ───────────────────────────────────
    368         function trackEvent( notificationId, eventType, notificationType ) {
     386        function trackEvent( notificationId, eventType, notificationType, abVariant, abTestId ) {
    369387                if ( ! data.trackUrl || ! notificationId ) return;
    370388
    … …  
    379397                                event: eventType,
    380398                                page_id: data.pageId || 'global',
     399                                ab_variant: abVariant || '',
     400                                ab_test_id: abTestId || 0,
    381401                        } ),
    382402                } ).catch( () => {} ); // Silently fail.
    … …  
    388408                                notification_type: notificationType || 'unknown',
    389409                                page_id: data.pageId || 'global',
     410                                ab_variant: abVariant || 'default',
    390411                        } );
    391412                }
  • salespulse/trunk/includes/class-analytics.php

    r3479316 r3730384  
    142142                );
    143143
     144                // Allow extensions (e.g. SalesPulse Pro A/B testing) to process tracking events.
     145                do_action('salespulse_track_event', $notification_id, $event, $page_id, $request);
     146
    144147                return new \WP_REST_Response(array('success' => true), 200);
    145148        }
  • salespulse/trunk/includes/class-data-collector.php

    r3486898 r3730384  
    117117        public static function get_recent_comments( $limit = 10 ) {
    118118                $comments = get_comments( array(
    119                         'status'  => 'approve',
    120                         'number'  => $limit,
    121                         'orderby' => 'comment_date',
    122                         'order'   => 'DESC',
     119                        'status'    => 'approve',
     120                        'post_type' => 'post',
     121                        'number'    => $limit,
     122                        'orderby'   => 'comment_date',
     123                        'order'     => 'DESC',
    123124                ) );
     125
     126                // Fallback if no blog post comments exist.
     127                if ( empty( $comments ) ) {
     128                        $comments = get_comments( array(
     129                                'status'  => 'approve',
     130                                'number'  => $limit,
     131                                'orderby' => 'comment_date',
     132                                'order'   => 'DESC',
     133                        ) );
     134                }
    124135
    125136                $comment_data = array();
    … …  
    137148
    138149                return $comment_data;
     150        }
     151
     152        /**
     153         * Get recent WooCommerce product reviews.
     154         *
     155         * Queries approved reviews attached to WooCommerce products.
     156         *
     157         * @param int $limit Max reviews to return.
     158         * @return array Array of review data arrays.
     159         */
     160        public static function get_recent_reviews( $limit = 10 ) {
     161                $args = array(
     162                        'status'    => 'approve',
     163                        'post_type' => 'product',
     164                        'number'    => $limit,
     165                        'orderby'   => 'comment_date',
     166                        'order'     => 'DESC',
     167                );
     168
     169                if ( class_exists( 'WooCommerce' ) ) {
     170                        $args['type'] = 'review';
     171                }
     172
     173                $reviews = get_comments( $args );
     174
     175                // Fallback without comment_type if none found.
     176                if ( empty( $reviews ) && ! empty( $args['type'] ) ) {
     177                        unset( $args['type'] );
     178                        $reviews = get_comments( $args );
     179                }
     180
     181                if ( empty( $reviews ) ) {
     182                        return array();
     183                }
     184
     185                $review_data = array();
     186
     187                foreach ( $reviews as $review ) {
     188                        $product_id = (int) $review->comment_post_ID;
     189                        $rating     = (int) get_comment_meta( $review->comment_ID, 'rating', true );
     190                        if ( ! $rating || $rating < 1 || $rating > 5 ) {
     191                                $rating = 5;
     192                        }
     193
     194                        $product_image = get_the_post_thumbnail_url( $product_id, 'thumbnail' );
     195                        if ( ! $product_image && function_exists( 'wc_placeholder_img_src' ) ) {
     196                                $product_image = wc_placeholder_img_src( 'thumbnail' );
     197                        }
     198
     199                        $product_title = get_the_title( $product_id );
     200                        $product_url   = get_permalink( $product_id );
     201
     202                        $review_data[] = array(
     203                                'customer_name' => $review->comment_author ?: esc_html__( 'Someone', 'salespulse' ),
     204                                'comment_text'  => wp_trim_words( $review->comment_content, 12, '...' ),
     205                                'review_text'   => wp_trim_words( $review->comment_content, 12, '...' ),
     206                                'product_name'  => $product_title ?: esc_html__( 'Product', 'salespulse' ),
     207                                'post_title'    => $product_title,
     208                                'product_url'   => $product_url,
     209                                'post_url'      => $product_url,
     210                                'product_image' => $product_image ?: '',
     211                                'rating'        => $rating,
     212                                'rating_stars'  => str_repeat( '★', $rating ),
     213                                'time_ago'      => self::human_time_diff( strtotime( $review->comment_date ) ),
     214                                'timestamp'     => strtotime( $review->comment_date ),
     215                        );
     216                }
     217
     218                return $review_data;
    139219        }
    140220
  • salespulse/trunk/includes/class-merge-tags.php

    r3486898 r3730384  
    3535                        '{post_title}'     => esc_html__( 'Post/page title', 'salespulse' ),
    3636                        '{comment_text}'   => esc_html__( 'Comment excerpt (10 words)', 'salespulse' ),
     37                        '{review_text}'    => esc_html__( 'Review excerpt (12 words)', 'salespulse' ),
     38                        '{rating}'         => esc_html__( 'Rating number (1-5)', 'salespulse' ),
     39                        '{rating_stars}'   => esc_html__( 'Rating stars (e.g. ★★★★★)', 'salespulse' ),
    3740                        '{on_fire_count}'  => esc_html__( 'Aggregate activity count (On Fire)', 'salespulse' ),
    3841                        '{on_fire_window}' => esc_html__( 'Time window label (e.g., "24 hours")', 'salespulse' ),
    … …  
    6164                        '{visitor_count}'  => absint( $data['visitor_count'] ?? Data_Collector::get_visitor_count() ),
    6265                        '{post_title}'     => esc_html( $data['post_title'] ?? '' ),
    63                         '{comment_text}'   => esc_html( $data['comment_text'] ?? '' ),
     66                        '{comment_text}'   => esc_html( $data['comment_text'] ?? $data['review_text'] ?? '' ),
     67                        '{review_text}'    => esc_html( $data['review_text'] ?? $data['comment_text'] ?? '' ),
     68                        '{rating}'         => absint( $data['rating'] ?? 5 ),
     69                        '{rating_stars}'   => esc_html( $data['rating_stars'] ?? '★★★★★' ),
    6470                        '{on_fire_count}'  => absint( $data['on_fire_count'] ?? 0 ),
    6571                        '{on_fire_window}' => esc_html( $data['on_fire_window'] ?? '' ),
  • salespulse/trunk/includes/class-notification-engine.php

    r3486898 r3730384  
    4444                $queue = array();
    4545
    46                 // Fetch visitor geo data once for all notifications.
    47                 $visitor_geo = Data_Collector::get_visitor_geo();
     46                // Fetch visitor geo data lazily only if geo-filtering is hooked or geo merge tags are used.
     47                $needs_geo = has_filter( 'salespulse_geo_filter_notification' );
     48                if ( ! $needs_geo ) {
     49                        foreach ( $notifications as $notif_item ) {
     50                                $cfg_str = is_string( $notif_item['config'] ?? '' ) ? $notif_item['config'] : wp_json_encode( $notif_item['config'] ?? '' );
     51                                if ( false !== strpos( $cfg_str, 'visitor_city' ) || false !== strpos( $cfg_str, 'visitor_country' ) ) {
     52                                        $needs_geo = true;
     53                                        break;
     54                                }
     55                        }
     56                }
     57
     58                $visitor_geo = $needs_geo ? Data_Collector::get_visitor_geo() : array();
    4859
    4960                foreach ( $notifications as $notification ) {
    … …  
    154165
    155166                        case 'review':
    156                                 $comments = Data_Collector::get_recent_comments( 1 );
    157                                 return $comments;
     167                                return Data_Collector::get_recent_reviews( 1 );
    158168
    159169                        case 'comment':
    … …  
    288298                        }
    289299
     300                        if ( 'review' === $type ) {
     301                                $item['product_name']  = esc_html__( 'Featured Product', 'salespulse' );
     302                                $item['post_title']    = $item['product_name'];
     303                                $item['comment_text']  = esc_html__( 'Amazing quality, exceeded my expectations!', 'salespulse' );
     304                                $item['review_text']   = $item['comment_text'];
     305                                $item['rating']        = 5;
     306                                $item['rating_stars']  = '★★★★★';
     307                                $item['product_url']   = home_url();
     308                                $item['post_url']      = home_url();
     309                                $item['product_image'] = '';
     310                        }
     311
    290312                        if ( 'comment' === $type ) {
    291313                                $post_titles = array( 'Getting Started Guide', 'Top 10 Tips', 'Product Review', 'How-To Tutorial', 'Latest Updates' );
  • salespulse/trunk/includes/class-salespulse.php

    r3486898 r3730384  
    213213                                'callback' => array($this, 'rest_smart_capture'),
    214214                                'permission_callback' => '__return_true',
     215                        ),
     216                ));
     217
     218                // Public endpoint: fetch fresh notification queue for page cache compatibility.
     219                register_rest_route('salespulse/v1', '/queue', array(
     220                                array(
     221                                'methods' => 'GET',
     222                                'callback' => array($this, 'rest_get_queue'),
     223                                'permission_callback' => '__return_true',
     224                                'args' => array(
     225                                        'page_id' => array(
     226                                                'default' => 'global',
     227                                                'sanitize_callback' => 'sanitize_text_field',
     228                                        ),
     229                                ),
    215230                        ),
    216231                ));
    … …  
    532547                Cookie_Consent::save_settings($data);
    533548                return rest_ensure_response(array('success' => true));
     549        }
     550
     551        /**
     552         * REST: Get fresh notification queue.
     553         *
     554         * Ensures notifications work dynamically on cached pages (LiteSpeed, WP Rocket, Cloudflare).
     555         *
     556         * @param \WP_REST_Request $request Request.
     557         * @return \WP_REST_Response
     558         */
     559        public function rest_get_queue($request)
     560        {
     561                $page_id = sanitize_text_field($request->get_param('page_id') ?: 'global');
     562                $queue = Notification_Engine::build_queue($page_id);
     563                return rest_ensure_response(array(
     564                        'success' => true,
     565                        'queue' => $queue,
     566                ));
    534567        }
    535568
  • salespulse/trunk/readme.txt

    r3486898 r3730384  
    1 === SalesPulse - Social Proof & FOMO Notifications ===
     1=== SalesPulse – Recent Sales Notification, Live Social Proof & FOMO Popups for WooCommerce ===
    22Contributors: wpmatcha
    33Donate link: https://wpmatcha.com/
    4 Tags: social proof, fomo, notifications, woocommerce, conversion
     4Tags: woocommerce, sales notification, recent sales, social proof, fomo popup
    55Requires at least: 5.8
    66Tested up to: 6.9
    77Requires PHP: 7.4
    8 Stable tag: 1.0.2
     8Stable tag: 1.1.0
    99License: GPLv2 or later
    1010License URI: https://www.gnu.org/licenses/gpl-2.0.html
    1111
    12 Boost conversions with real-time social proof & FOMO popups. Show purchases, signups, reviews, visitor counts & announcement bars.
     12Recent sales popups, live social proof, purchase notifications & FOMO alerts for WooCommerce. Boost sales, trust, and conversions in minutes.
    1313
    1414== Description ==
    … …  
    142142== Changelog ==
    143143
     144= 1.1.0 =
     145* Feature: Full page cache compatibility (LiteSpeed, WP Rocket, Cloudflare) via dynamic REST queue fallback.
     146* Fix: Resolved issue where product reviews fell back to general blog comments instead of WooCommerce product reviews.
     147* Improvement: Added {rating}, {rating_stars}, and {review_text} merge tags with automatic product link and image resolution for reviews.
     148* Improvement: Optimized SEO title and keywords for WooCommerce store discoverability.
     149* Improvement: Added extension hooks for Pro A/B testing and click-through attribution.
     150* Fix: Prevented stale notifications from displaying on cached product pages.
     151
    144152= 1.0.2 =
    145153* Bug Fixes
    … …  
    177185== Upgrade Notice ==
    178186
     187= 1.1.0 =
     188Recommended update: Adds full page caching compatibility for WooCommerce stores, performance optimizations, and bug fixes.
     189
    179190= 1.0.0 =
    180191First release of SalesPulse
     192
     193== External Services ==
     194
     195This plugin can optionally query an external service to resolve geo-location for merge tags:
     196* **ip-api.com** (https://ip-api.com/)
     197  - **Purpose:** Resolves visitor IP address to approximate city and country for localized merge tags ({visitor_city}, {visitor_country}).
     198  - **Invocation:** Only invoked if notifications explicitly include visitor geo merge tags. Otherwise, no external requests are made.
     199  - **Data Transmitted:** Visitor IP address. No personal information, names, or order details are ever sent.
     200  - **Terms & Privacy:** https://ip-api.com/docs/legal
  • salespulse/trunk/salespulse.php

    r3486898 r3730384  
    11<?php
    22/**
    3  * Plugin Name:       SalesPulse - Social Proof & FOMO Notifications
     3 * Plugin Name:       SalesPulse – Recent Sales Notification, Live Social Proof & FOMO Popups for WooCommerce
    44 * Description:       Boost conversions with real-time social proof notifications. Show recent purchases, signups, reviews & visitor activity to build trust and create urgency.
    5  * Version:           1.0.2
     5 * Version:           1.1.0
    66 * Author:            WPMatcha
    77 * Author URI:        https://wpmatcha.com/
    … …  
    3838 * Plugin constants.
    3939 */
    40 define( 'SALESPULSE_VERSION', '1.0.2' );
     40define( 'SALESPULSE_VERSION', '1.1.0' );
    4141define( 'SALESPULSE_PLUGIN_FILE', __FILE__ );
    4242define( 'SALESPULSE_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
Note: See TracChangeset for help on using the changeset viewer.