Plugin Directory

Changeset 3381510


Ignore:
Timestamp:
10/20/2025 07:43:21 PM (5 months ago)
Author:
synoveo
Message:

Deploy synoveo v1.2.1

Location:
synoveo/trunk
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • synoveo/trunk/assets/js/google-business-connector.js

    r3378651 r3381510  
    144144   
    145145    /**
    146      * Get or generate stable Lite user identifier
    147      * Priority: WordPress option > localStorage > generate new
    148      */
    149     getOrCreateUserIdentifier() {
    150         // First, check if WordPress has it (source of truth)
    151         let identifier = window.synoveo_ajax?.user_identifier;
    152        
    153         // If WordPress has synoveo_lite_ key, use it
    154         if (identifier && identifier.startsWith('synoveo_lite_')) {
    155             localStorage.setItem('synoveo_lite_identifier', identifier);
    156             return identifier;
    157         }
    158        
    159         // Check localStorage
    160         identifier = localStorage.getItem('synoveo_lite_identifier');
    161         if (identifier && identifier.startsWith('synoveo_lite_')) {
    162             return identifier;
    163         }
    164        
    165         // Generate new stable identifier (deterministic based on domain)
    166         const domain = window.location.hostname
    167             .replace(/[^a-z0-9]/g, '')
    168             .substring(0, 8);
    169        
    170         // Use deterministic hash based on domain + fixed salt
     146     * Compute canonical Lite identifier based on hostname.
     147     *
     148     * Mirrors PHP and Node implementations to avoid divergence.
     149     *
     150     * @param {string} hostname
     151     * @returns {string}
     152     */
     153    computeLiteIdentifier(hostname) {
     154        const source = typeof hostname === 'string' && hostname.length > 0 ? hostname : window.location.hostname;
     155        let normalized = (source || '').toLowerCase().replace(/[^a-z0-9]/g, '');
     156
     157        if (!normalized) {
     158            normalized = 'synoveo';
     159        }
     160
     161        const prefix = (normalized + '00000000').substring(0, 8);
    171162        const salt = 'synoveo_stable_2025';
    172         const hashInput = domain + salt;
    173        
    174         // Simple deterministic hash (same input = same output)
     163        const hashInput = prefix + salt;
     164
    175165        let hash = 0;
    176166        for (let i = 0; i < hashInput.length; i++) {
    177             const char = hashInput.charCodeAt(i);
    178             hash = ((hash << 5) - hash) + char;
    179             hash = hash & hash; // Convert to 32-bit integer
    180         }
    181        
    182         // Convert to alphanumeric string
    183         const hashStr = Math.abs(hash).toString(36).substring(0, 15).padEnd(15, '0');
    184        
    185         identifier = `synoveo_lite_${domain}${hashStr}`;
    186         localStorage.setItem('synoveo_lite_identifier', identifier);
    187        
    188         SynoveoLogger.info(`Generated stable identifier: ${identifier}`, 'GoogleBusiness');
    189         return identifier;
     167            hash = ((hash << 5) - hash) + hashInput.charCodeAt(i);
     168            hash |= 0; // Force 32-bit signed integer
     169        }
     170
     171        let hashStr = Math.abs(hash).toString(36).toLowerCase();
     172        hashStr = (hashStr + '000000000000000').substring(0, 15);
     173
     174        return `synoveo_lite_${prefix}${hashStr}`;
     175    }
     176   
     177    /**
     178     * Get or generate stable Lite user identifier.
     179     * Always returns the canonical hostname-derived identifier.
     180     */
     181    getOrCreateUserIdentifier() {
     182        const canonical = this.computeLiteIdentifier(window.location.hostname);
     183
     184        try {
     185            const optionIdentifier = window.synoveo_ajax?.user_identifier;
     186            if (optionIdentifier && !optionIdentifier.startsWith('synoveo_lite_')) {
     187                return optionIdentifier;
     188            }
     189
     190            if (optionIdentifier && optionIdentifier.startsWith('synoveo_lite_')) {
     191                if (optionIdentifier !== canonical) {
     192                    localStorage.setItem('synoveo_lite_identifier', canonical);
     193                    return canonical;
     194                }
     195
     196                localStorage.setItem('synoveo_lite_identifier', optionIdentifier);
     197                return optionIdentifier;
     198            }
     199        } catch (error) {
     200            SynoveoLogger.warn(`Failed to sync WordPress option identifier: ${error.message}`, 'GoogleBusiness');
     201        }
     202
     203        try {
     204            const stored = localStorage.getItem('synoveo_lite_identifier');
     205            if (stored && stored.startsWith('synoveo_lite_')) {
     206                if (stored !== canonical) {
     207                    localStorage.setItem('synoveo_lite_identifier', canonical);
     208                    SynoveoLogger.info(`Normalized Lite identifier to canonical value: ${canonical}`, 'GoogleBusiness');
     209                    return canonical;
     210                }
     211
     212                return stored;
     213            }
     214        } catch (error) {
     215            SynoveoLogger.warn(`Unable to access localStorage for Lite identifier: ${error.message}`, 'GoogleBusiness');
     216        }
     217       
     218        try {
     219            localStorage.setItem('synoveo_lite_identifier', canonical);
     220        } catch (error) {
     221            SynoveoLogger.warn(`Failed to persist canonical Lite identifier: ${error.message}`, 'GoogleBusiness');
     222        }
     223       
     224        SynoveoLogger.info(`Generated canonical identifier: ${canonical}`, 'GoogleBusiness');
     225        return canonical;
    190226    }
    191227
     
    12301266        const sendBtn = document.getElementById('wizard-send-code-btn');
    12311267       
    1232         // Get or generate user identifier
     1268        // Get or generate canonical Lite identifier
    12331269        let userIdentifier = window.synoveo_ajax?.user_identifier;
    1234         if (!userIdentifier || userIdentifier === '') {
    1235             userIdentifier = localStorage.getItem('synoveo_lite_identifier');
    1236             if (!userIdentifier) {
    1237                 const siteIdentifier = window.location.hostname.replace(/[^a-z0-9]/g, '').substring(0, 8);
    1238                 const randomSuffix = Math.random().toString(36).substring(2, 15);
    1239                 userIdentifier = `synoveo_lite_${siteIdentifier}${randomSuffix}`;
    1240                 localStorage.setItem('synoveo_lite_identifier', userIdentifier);
    1241                 SynoveoLogger.debug(`Generated new Lite identifier: ${userIdentifier}`, 'GoogleBusiness');
    1242             } else {
    1243                 SynoveoLogger.debug(`Using existing identifier: ${userIdentifier}`, 'GoogleBusiness');
    1244             }
    1245         } else {
    1246             SynoveoLogger.debug(`Using API key: ${userIdentifier}`, 'GoogleBusiness');
    1247         }
     1270        if (!userIdentifier || userIdentifier === '' || userIdentifier.startsWith('synoveo_lite_')) {
     1271            userIdentifier = this.getOrCreateUserIdentifier();
     1272        }
     1273        SynoveoLogger.debug(`Using identifier for verification: ${userIdentifier}`, 'GoogleBusiness');
    12481274       
    12491275        if (!email) {
     
    15771603        const sendBtn = document.getElementById('send-code-btn');
    15781604       
    1579         // Get or generate user identifier
     1605        // Get or generate canonical identifier
    15801606        let userIdentifier = window.synoveo_ajax?.user_identifier;
    1581        
    1582         // If no API key exists (new Lite user), check localStorage or generate one
    1583         if (!userIdentifier || userIdentifier === '') {
    1584             // Check if we already generated one in this session
    1585             userIdentifier = localStorage.getItem('synoveo_lite_identifier');
    1586            
    1587             if (!userIdentifier) {
    1588                 // Generate a new Lite user identifier based on site/domain
    1589                 const siteIdentifier = window.location.hostname.replace(/[^a-z0-9]/g, '').substring(0, 8);
    1590                 const randomSuffix = Math.random().toString(36).substring(2, 15);
    1591                 userIdentifier = `synoveo_lite_${siteIdentifier}${randomSuffix}`;
    1592                
    1593                 // Store it in localStorage so it persists
    1594                 localStorage.setItem('synoveo_lite_identifier', userIdentifier);
    1595                 SynoveoLogger.debug(`Generated Lite user identifier: ${userIdentifier}`, 'GoogleBusiness');
    1596             } else {
    1597                 SynoveoLogger.debug(`Reusing Lite user identifier from localStorage: ${userIdentifier}`, 'GoogleBusiness');
    1598             }
    1599         }
     1607        if (!userIdentifier || userIdentifier === '' || userIdentifier.startsWith('synoveo_lite_')) {
     1608            userIdentifier = this.getOrCreateUserIdentifier();
     1609        }
     1610        SynoveoLogger.debug(`Using identifier for legacy verification flow: ${userIdentifier}`, 'GoogleBusiness');
    16001611       
    16011612        if (!email) {
  • synoveo/trunk/includes/services/class-synoveo-api-service.php

    r3378651 r3381510  
    218218     * @return string The Lite user identifier in format synoveo_lite_<hash>
    219219     */
    220     private function getLiteApiKey(): string {
    221         // PRIORITY: Use registered user_identifier from WordPress option (source of truth)
    222         $stored_identifier = get_option( 'synoveo_user_identifier', '' );
    223         if ( ! empty( $stored_identifier ) && str_starts_with( $stored_identifier, 'synoveo_lite_' ) ) {
     220    /**
     221     * Public accessor for Lite API key generation.
     222     * Called by bootstrap to self-heal user identifier on plugin load.
     223     */
     224    public function getLiteApiKey(): string {
     225        $stored_identifier   = get_option( 'synoveo_user_identifier', '' );
     226        $canonical_identifier = $this->compute_lite_identifier();
     227
     228        if ( is_string( $stored_identifier ) && str_starts_with( $stored_identifier, 'synoveo_lite_' ) ) {
     229            if ( $stored_identifier !== $canonical_identifier ) {
     230                update_option( 'synoveo_user_identifier', $canonical_identifier, false );
     231                return $canonical_identifier;
     232            }
     233
    224234            return $stored_identifier;
    225235        }
    226        
    227         // Fallback: Generate deterministic key based on domain (same logic as backend)
    228         $domain = wp_parse_url( home_url(), PHP_URL_HOST );
    229         $domain = strtolower( trim( $domain ) );
    230         $domain = preg_replace( '/[^a-z0-9.\-]/i', '', $domain );
    231         // Align with server hash logic (sha256(domain + salt) → 16 hex chars)
    232         $hash = hash( 'sha256', $domain . 'synoveo_lite_salt_2025' );
    233         return 'synoveo_lite_' . substr( $hash, 0, 16 );
     236
     237        update_option( 'synoveo_user_identifier', $canonical_identifier, false );
     238        return $canonical_identifier;
    234239    }
    235240
     
    643648     */
    644649    private function get_or_generate_lite_key(): string {
    645         // Generate deterministic key based on domain
    646         $domain = isset( $_SERVER['HTTP_HOST'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) : home_url();
    647         $domain = preg_replace( '/^https?:\/\//', '', $domain ); // Clean domain
    648         $domain = preg_replace( '/[^a-zA-Z0-9.-]/', '', $domain ); // Sanitize
    649 
    650         // Create deterministic hash
    651         $hash       = hash( 'sha256', $domain . 'synoveo_lite_salt_2025' );
    652         $short_hash = substr( $hash, 0, 16 ); // 16 chars
    653 
    654         return 'synoveo_lite_' . $short_hash;
     650        return $this->compute_lite_identifier();
    655651    }
    656652
     
    759755        );
    760756    }
     757
     758    /**
     759     * Compute canonical Lite user identifier based on the current site hostname.
     760     *
     761     * Matches frontend and backend logic to prevent divergent identifiers.
     762     *
     763     * @return string Lite identifier in the format synoveo_lite_<prefix><suffix>
     764     */
     765    private function compute_lite_identifier(): string {
     766        $host = wp_parse_url( home_url(), PHP_URL_HOST );
     767        if ( empty( $host ) ) {
     768            $host = home_url();
     769        }
     770
     771        $normalized = strtolower( $host );
     772        $normalized = preg_replace( '/[^a-z0-9]/', '', $normalized ?? '' );
     773        if ( empty( $normalized ) ) {
     774            $normalized = 'synoveo';
     775        }
     776
     777        $prefix = substr( $normalized . '00000000', 0, 8 );
     778        $salt   = 'synoveo_stable_2025';
     779
     780        $hash_input = $prefix . $salt;
     781        $hash       = 0;
     782
     783        foreach ( str_split( $hash_input ) as $char ) {
     784            $hash = ( ( $hash << 5 ) - $hash ) + ord( $char );
     785            $hash &= 0xFFFFFFFF;
     786
     787            if ( $hash & 0x80000000 ) {
     788                $hash -= 0x100000000;
     789            }
     790        }
     791
     792        $hash_value = abs( $hash );
     793        $hash_str   = strtolower( base_convert( (string) $hash_value, 10, 36 ) );
     794        $hash_str   = substr( $hash_str . '000000000000000', 0, 15 );
     795
     796        return 'synoveo_lite_' . $prefix . $hash_str;
     797    }
    761798}
  • synoveo/trunk/readme.txt

    r3378651 r3381510  
    55Tested up to: 6.8
    66Requires PHP: 7.4
    7 Stable tag: 1.2.0
     7Stable tag: 1.2.1
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    252252
    253253== Changelog ==
     254
     255= 1.2.1 =
     256* Fixed Lite user desynchronization issue between PHP and JS key generators.
     257* Added automatic self-healing of Lite identifier on plugin load.
     258* Improved bootstrap stability and idempotent registration flow.
     259* Minor logging improvements and internal API normalization.
    254260
    255261= 1.2.0 (2024-03-15) =
     
    344350== Upgrade Notice ==
    345351
     352= 1.2.1 =
     353Lite identifier alignment and self-healing. Recommended for all Lite installations to prevent registration errors.
     354
    346355= 1.2.0 =
    347356Major stability improvements. This update eliminates quota duplication issues and rate limiting errors. Recommended for all users.
  • synoveo/trunk/synoveo.php

    r3378651 r3381510  
    44 * Plugin URI: https://www.synoveo.com
    55 * Description: Grow revenue by improving your online business presence. Synoveo keeps your Google Business Profile accurate and up-to-date.
    6  * Version: 1.2.0
     6 * Version: 1.2.1
    77 * Author: Synoveo (CODE75)
    88 * Author URI: https://www.synoveo.com
     
    111111// Plugin constants
    112112// Single source of truth for version number
    113 $base_version = '1.2.0'; // ← Change only this when bumping version
     113$base_version = '1.2.1'; // ← Change only this when bumping version
    114114define( 'SYNOVEO_VERSION', defined( 'WP_DEBUG' ) && WP_DEBUG ? $base_version . '-dev-' . time() : $base_version );
    115115define( 'SYNOVEO_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
     
    203203        $this->admin_controller = $this->container->get( 'admin_controller' );
    204204        $this->frontend_service = $this->container->get( 'frontend_service' );
     205        $api_service            = $this->container->get( 'api_service' );
     206
     207        // Automatic Lite identifier self-healing on plugin load
     208        update_option( 'synoveo_user_identifier', $api_service->getLiteApiKey(), true );
    205209
    206210        // Configure Admin Controller
Note: See TracChangeset for help on using the changeset viewer.