Changeset 3495969
- Timestamp:
- 03/31/2026 07:50:53 PM (5 days ago)
- Location:
- text-to-speech-tts/trunk
- Files:
-
- 2 deleted
- 9 edited
-
admin/partials/pages/advanced.php (modified) (2 diffs)
-
admin/partials/pages/player.php (modified) (2 diffs)
-
includes/class-mementor-tts-analytics.php (modified) (1 diff)
-
includes/class-mementor-tts-elevenlabs-api.php (modified) (6 diffs)
-
includes/class-mementor-tts-public.php (modified) (1 diff)
-
includes/class-mementor-tts-remote-telemetry.php (deleted)
-
includes/class-mementor-tts-telemetry.php (deleted)
-
includes/class-mementor-tts.php (modified) (1 diff)
-
public/js/mementor-tts-player-insert.js (modified) (1 diff)
-
readme.txt (modified) (3 diffs)
-
text-to-speech-tts.php (modified) (4 diffs)
Legend:
- Unmodified
- Added
- Removed
-
text-to-speech-tts/trunk/admin/partials/pages/advanced.php
r3493900 r3495969 23 23 // Data Management 24 24 update_option('mementor_tts_delete_data', isset($_POST['mementor_tts_delete_data']) ? '1' : '0'); 25 26 // Telemetry consent27 $telemetry_consent = isset($_POST['mementor_tts_telemetry_consent']) ? 'yes' : 'no';28 update_option('mementor_tts_telemetry_consent', $telemetry_consent);29 update_option('mementor_tts_telemetry_consent_time', time());30 25 31 26 // Custom CSS … … 114 109 <label class="tts-toggle"> 115 110 <input type="checkbox" name="mementor_tts_delete_data" value="1" <?php checked(get_option('mementor_tts_delete_data', '1'), '1'); ?>> 116 <span class="tts-toggle-slider"></span>117 </label>118 </div>119 120 <div class="tts-setting-row">121 <div>122 <div class="tts-setting-label"><?php esc_html_e('Usage Statistics', 'text-to-speech-tts'); ?></div>123 <div class="tts-setting-hint"><?php esc_html_e('Share anonymous usage data (feature counts, plugin version, WP version). No personal data collected.', 'text-to-speech-tts'); ?></div>124 </div>125 <label class="tts-toggle">126 <input type="checkbox" name="mementor_tts_telemetry_consent" value="1" <?php checked(get_option('mementor_tts_telemetry_consent', ''), 'yes'); ?>>127 111 <span class="tts-toggle-slider"></span> 128 112 </label> -
text-to-speech-tts/trunk/admin/partials/pages/player.php
r3493900 r3495969 30 30 $show_download = get_option('mementor_tts_show_download', false); 31 31 $show_info = get_option('mementor_tts_show_info', false); 32 33 // Check if show_info is forced by remote config34 $show_info_remote_override = Mementor_Text_To_Speech::get_remote_config_override('mementor_tts_show_info');35 $show_info_forced = ($show_info_remote_override !== null);36 if ($show_info_forced) {37 $show_info = $show_info_remote_override;38 }39 32 40 33 // Check if API key is set … … 291 284 <div class="tts-control-item"> 292 285 <span class="tts-control-label"><?php esc_html_e('Information', 'text-to-speech-tts'); ?></span> 293 <div class="mementor-tts-toggle"><input type="checkbox" id="mementor_tts_show_info" name="mementor_tts_show_info" value="1" <?php checked($show_info); ?> <?php echo $show_info_forced ? 'disabled' : ''; ?>><label for="mementor_tts_show_info"><span class="slider"></span></label></div>286 <div class="mementor-tts-toggle"><input type="checkbox" id="mementor_tts_show_info" name="mementor_tts_show_info" value="1" <?php checked($show_info); ?> ><label for="mementor_tts_show_info"><span class="slider"></span></label></div> 294 287 </div> 295 288 <div class="tts-control-item"> -
text-to-speech-tts/trunk/includes/class-mementor-tts-analytics.php
r3493900 r3495969 650 650 } 651 651 652 /**653 * Export analytics data for remote sync654 *655 * @param string $period Period to export656 * @return array657 */658 public function export_analytics_for_sync($period = 'day') {659 global $wpdb;660 661 $date_condition = $this->get_date_condition($period);662 663 // Get aggregated data664 $data = $wpdb->get_results(665 "SELECT666 domain_hash,667 user_type,668 COUNT(*) as requests,669 SUM(credits_used) as credits,670 plugin_version,671 pro_version,672 country_code,673 DATE(created_at) as date674 FROM {$this->table_name}675 WHERE {$date_condition}676 GROUP BY domain_hash, user_type, DATE(created_at)"677 );678 679 // Add metadata680 $export = array(681 'site_hash' => $this->get_domain_info()['hash'],682 'export_date' => current_time('mysql'),683 'period' => $period,684 'data' => $data,685 'plugin_version' => MEMENTOR_TTS_VERSION,686 'php_version' => PHP_VERSION,687 'wp_version' => get_bloginfo('version')688 );689 690 // Sign the export691 $export['signature'] = $this->integrity_helper->generate_signature($export, 'analytics_export');692 693 return $export;694 }695 696 /**697 * Send analytics to remote server698 * This runs periodically to sync data699 */700 public function send_analytics_to_remote() {701 // Check if remote analytics is enabled702 if (!get_option('mementor_tts_enable_remote_analytics', true)) {703 return;704 }705 706 // Get last sync time707 $last_sync = get_option('mementor_tts_last_analytics_sync', 0);708 $now = time();709 710 // Only sync once per day711 if ($now - $last_sync < DAY_IN_SECONDS) {712 return;713 }714 715 // Export data for the last day716 $export_data = $this->export_analytics_for_sync('day');717 718 // Send to remote endpoint719 $response = wp_remote_post('https://api.mementor.no/v1/analytics', array(720 'timeout' => 30,721 'headers' => array(722 'Content-Type' => 'application/json',723 'X-Plugin-Version' => MEMENTOR_TTS_VERSION724 ),725 'body' => wp_json_encode($export_data)726 ));727 728 if (!is_wp_error($response)) {729 $response_code = wp_remote_retrieve_response_code($response);730 if ($response_code === 200) {731 // Update last sync time732 update_option('mementor_tts_last_analytics_sync', $now, false);733 734 // Clean up local data after successful sync735 $this->cleanup_synced_data();736 }737 }738 }739 740 /**741 * Clean up synced data to save space742 */743 private function cleanup_synced_data() {744 global $wpdb;745 746 // Keep only last 7 days of detailed data locally747 $cutoff = date('Y-m-d H:i:s', strtotime('-7 days'));748 749 $wpdb->query($wpdb->prepare(750 "DELETE FROM {$this->table_name} WHERE created_at < %s",751 $cutoff752 ));753 }754 652 } -
text-to-speech-tts/trunk/includes/class-mementor-tts-elevenlabs-api.php
r3493900 r3495969 83 83 * Shared key manager instance 84 84 */ 85 private $shared_key_manager = null;86 85 87 /**88 * Remote telemetry instance89 */90 private $telemetry = null;91 86 92 87 /** … … 132 127 // Initialize debug mode 133 128 $this->debug_mode = get_option('mementor_tts_debug_mode', '0') === '1'; 134 135 // Shared key manager removed — synthesis now goes through SaaS API client136 $this->shared_key_manager = null;137 138 // Initialize telemetry139 if (!class_exists('Mementor_TTS_Remote_Telemetry')) {140 require_once plugin_dir_path(__FILE__) . 'class-mementor-tts-remote-telemetry.php';141 }142 $this->telemetry = Mementor_TTS_Remote_Telemetry::get_instance();143 129 144 130 // Get the API key from options … … 1048 1034 } 1049 1035 1050 // Track API error in telemetry1051 if ($this->telemetry) {1052 $this->telemetry->track_usage('error', array(1053 'error_code' => 'api_' . $status_code,1054 'error_message' => $friendly_message1055 ));1056 }1057 1058 1036 return array( 1059 1037 'success' => false, … … 1110 1088 $remaining_credits = $user_credits->get_remaining_credits(); 1111 1089 1112 // Log usage to shared key manager for analytics1113 if ($this->shared_key_manager) {1114 $this->shared_key_manager->log_shared_key_usage($credits_used, 'text_to_speech');1115 }1116 1090 } 1117 1091 … … 1134 1108 } 1135 1109 1136 // Track telemetry1137 if ($this->telemetry) {1138 $telemetry_data = array(1139 'text_length' => strlen($text),1140 'using_shared_key' => $used_shared_key1141 );1142 $this->telemetry->track_usage('text_to_speech', $telemetry_data);1143 }1144 1145 1110 return $result; 1146 1111 1147 1112 } catch (Exception $e) { 1148 1113 $this->log_message('Exception in text_to_speech: ' . $e->getMessage(), 'error'); 1149 1150 // Track error in telemetry1151 if ($this->telemetry) {1152 $this->telemetry->track_usage('error', array(1153 'error_code' => 'exception',1154 'error_message' => $e->getMessage()1155 ));1156 }1157 1114 1158 1115 return array( … … 1713 1670 /** 1714 1671 * Get decrypted shared API key 1715 * 1672 * 1716 1673 * @return string|null Decrypted API key or null 1717 1674 */ 1718 1675 private function get_decrypted_shared_key() { 1719 if (!$this->shared_key_manager) { 1720 return null; 1721 } 1722 1723 return $this->shared_key_manager->get_shared_key(); 1676 return null; 1724 1677 } 1725 1678 -
text-to-speech-tts/trunk/includes/class-mementor-tts-public.php
r3493900 r3495969 711 711 712 712 // Process content using CSS selectors if processor is available 713 if (!class_exists('Mementor_TTS_Processor')) { 714 require_once MEMENTOR_TTS_PLUGIN_DIR . 'includes/class-mementor-tts-processor.php'; 715 } 713 716 $processor = Mementor_TTS_Processor::get_instance(); 714 717 if ($processor && method_exists($processor, 'get_content_from_html')) { -
text-to-speech-tts/trunk/includes/class-mementor-tts.php
r3494985 r3495969 55 55 add_action('plugins_loaded', array($this, 'init_elementor_integration')); 56 56 57 // Initialize remote config for domain-specific setting overrides58 add_action('init', array($this, 'init_remote_config'));59 }60 61 /**62 * Initialize remote config system for domain-specific setting overrides.63 * Settings are fetched from the CRM and cached locally.64 */65 public function init_remote_config() {66 // Only apply overrides on frontend67 if (is_admin()) {68 return;69 }70 71 $overrides = $this->get_remote_config_overrides();72 73 if (!empty($overrides) && is_array($overrides)) {74 foreach ($overrides as $option_name => $value) {75 add_filter('pre_option_' . $option_name, function($pre_option) use ($value) {76 return $value;77 }, 10, 1);78 }79 }80 }81 82 /**83 * Get remote config overrides for the current domain.84 * Results are cached for 12 hours.85 *86 * @return array Associative array of option_name => forced_value87 */88 public function get_remote_config_overrides() {89 $cache_key = 'mementor_tts_remote_config';90 $cached = get_transient($cache_key);91 92 if (false !== $cached) {93 return $cached;94 }95 96 // Get current domain97 $domain = $this->get_current_domain();98 99 // Generate signature for authentication100 $timestamp = time();101 $secret = 'mementor_tts_config_v1';102 $signature = hash_hmac('sha256', $domain . $timestamp, $secret);103 104 // Fetch from CRM105 $response = wp_remote_get(106 'https://crm.mementor.no/plugin/api/telemetry/v1/settings.php?domain=' . urlencode($domain),107 array(108 'timeout' => 5,109 'headers' => array(110 'X-Plugin-Version' => MEMENTOR_TTS_VERSION,111 'X-TTS-Timestamp' => $timestamp,112 'X-TTS-Signature' => $signature,113 ),114 )115 );116 117 $overrides = array();118 119 if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) {120 $body = wp_remote_retrieve_body($response);121 $data = json_decode($body, true);122 123 if (isset($data['overrides']) && is_array($data['overrides'])) {124 $overrides = $data['overrides'];125 }126 }127 128 // Cache for 12 hours (even if empty, to avoid repeated requests)129 set_transient($cache_key, $overrides, 12 * HOUR_IN_SECONDS);130 131 return $overrides;132 }133 134 /**135 * Get the current domain without www prefix.136 *137 * @return string138 */139 public function get_current_domain() {140 $host = isset($_SERVER['HTTP_HOST']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_HOST'])) : '';141 return preg_replace('/^www\./', '', $host);142 }143 144 /**145 * Check if a specific option has a remote config override.146 *147 * @param string $option_name The option name to check.148 * @return mixed|null The override value if set, null otherwise.149 */150 public function get_remote_override($option_name) {151 $overrides = $this->get_remote_config_overrides();152 return isset($overrides[$option_name]) ? $overrides[$option_name] : null;153 }154 155 /**156 * Static helper to check for remote config override.157 * Can be called without a class instance.158 *159 * @param string $option_name The option name to check.160 * @return mixed|null The override value if set, null otherwise.161 */162 public static function get_remote_config_override($option_name) {163 $cache_key = 'mementor_tts_remote_config';164 $cached = get_transient($cache_key);165 166 // If not cached, we need to fetch - create temporary instance logic167 if (false === $cached) {168 // Get current domain169 $host = isset($_SERVER['HTTP_HOST']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_HOST'])) : '';170 $domain = preg_replace('/^www\./', '', $host);171 172 // Generate signature for authentication173 $timestamp = time();174 $secret = 'mementor_tts_config_v1';175 $signature = hash_hmac('sha256', $domain . $timestamp, $secret);176 177 // Fetch from CRM178 $response = wp_remote_get(179 'https://crm.mementor.no/plugin/api/telemetry/v1/settings.php?domain=' . urlencode($domain),180 array(181 'timeout' => 5,182 'headers' => array(183 'X-Plugin-Version' => MEMENTOR_TTS_VERSION,184 'X-TTS-Timestamp' => $timestamp,185 'X-TTS-Signature' => $signature,186 ),187 )188 );189 190 $overrides = array();191 192 if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) {193 $body = wp_remote_retrieve_body($response);194 $data = json_decode($body, true);195 196 if (isset($data['overrides']) && is_array($data['overrides'])) {197 $overrides = $data['overrides'];198 }199 }200 201 // Cache for 12 hours202 set_transient($cache_key, $overrides, 12 * HOUR_IN_SECONDS);203 $cached = $overrides;204 }205 206 return isset($cached[$option_name]) ? $cached[$option_name] : null;207 57 } 208 58 -
text-to-speech-tts/trunk/public/js/mementor-tts-player-insert.js
r3476321 r3495969 36 36 }; 37 37 38 // Log initialization if debug is enabled 39 safeDebug('Player insertion script initialized'); 40 safeDebug('Player insertion using PHP hooks - JavaScript insertion disabled'); 41 42 // IMPORTANT: Player insertion is now handled by PHP hooks for better compatibility 38 // Player insertion is handled by PHP hooks for better compatibility 43 39 // This script is kept for backward compatibility but does not insert players 44 return;45 46 // Don't skip auto-insertion just because there are shortcode players47 // Both types can coexist on the same page48 49 // Store references to existing shortcode players to ensure they're not affected50 const existingShortcodePlayers = $('.mementor-tts-player-container[data-player-type="shortcode"]');51 safeDebug('Found ' + existingShortcodePlayers.length + ' existing shortcode players to preserve');52 53 // Check if we have the required data54 if (typeof mementorTTSPlayerHTML === 'undefined' || !mementorTTSPlayerHTML.html) {55 // This is expected on pages with shortcodes, so just log debug info56 safeDebug('Player HTML data is not available - this is normal for shortcode usage');57 return;58 }59 60 // Get player position from localized data61 var position = mementorTTSPlayerPosition.position;62 safeDebug('Player position -', position);63 64 // Function to create the player label with proper classes65 function createPlayerLabel() {66 // Check if label settings exist and if the label should be shown67 if (!mementorTTSPlayerLabel || !mementorTTSPlayerLabel.show_label) {68 return '';69 }70 71 const labelClasses = ['mementor-tts-player-label'];72 73 // Add text alignment class74 if (mementorTTSPlayerLabel.text_align) {75 labelClasses.push(`text-${mementorTTSPlayerLabel.text_align}`);76 }77 78 // Add position class79 if (mementorTTSPlayerPosition) {80 labelClasses.push(`mementor-tts-${mementorTTSPlayerPosition}`);81 }82 83 // Add font size class84 const fontSize = parseInt(mementorTTSPlayerLabel.font_size);85 if (fontSize <= 12) {86 labelClasses.push('label-size-small');87 } else if (fontSize >= 16) {88 labelClasses.push('label-size-large');89 } else {90 labelClasses.push('label-size-default');91 }92 93 // Add font weight class94 const fontWeight = parseInt(mementorTTSPlayerLabel.font_weight);95 if (fontWeight >= 700) {96 labelClasses.push('label-weight-bold');97 } else if (fontWeight >= 500) {98 labelClasses.push('label-weight-medium');99 } else {100 labelClasses.push('label-weight-normal');101 }102 103 // Add color class104 if (mementorTTSPlayerLabel.color) {105 const color = mementorTTSPlayerLabel.color.toLowerCase();106 if (color === '#ffffff' || color === 'white') {107 labelClasses.push('label-color-white');108 } else if (color === '#1d2327' || color === 'dark') {109 labelClasses.push('label-color-dark');110 } else {111 // For custom colors, use a simpler approach with just 'custom' class112 labelClasses.push('label-color-custom');113 }114 } else {115 labelClasses.push('label-color-dark'); // Default color116 }117 118 // Use the labelText property from mementorTTSData for the label content119 const labelText = typeof mementorTTSData !== 'undefined' && mementorTTSData.labelText120 ? mementorTTSData.labelText121 : mementorTTSPlayerLabel.text || 'Listen to this article:';122 123 // For custom colors, include the color in the style attribute124 if (mementorTTSPlayerLabel.color &&125 mementorTTSPlayerLabel.color.toLowerCase() !== '#ffffff' &&126 mementorTTSPlayerLabel.color.toLowerCase() !== 'white' &&127 mementorTTSPlayerLabel.color.toLowerCase() !== '#1d2327' &&128 mementorTTSPlayerLabel.color.toLowerCase() !== 'dark') {129 return `<span class="${labelClasses.join(' ')}" style="--mementor-tts-custom-label-color: ${mementorTTSPlayerLabel.color}">${labelText}</span>`;130 } else {131 return `<span class="${labelClasses.join(' ')}">${labelText}</span>`;132 }133 }134 135 // Get player HTML from localized data136 var playerHTML = mementorTTSPlayerHTML.html;137 138 // Replace the default label with our dynamically styled one139 if (typeof mementorTTSPlayerLabel !== 'undefined' && mementorTTSPlayerLabel.show) {140 var labelHTML = createPlayerLabel();141 // Replace the existing label div with our new one142 var tempDiv = document.createElement('div');143 tempDiv.innerHTML = playerHTML;144 var existingLabel = tempDiv.querySelector('.mementor-tts-player-label');145 if (existingLabel) {146 existingLabel.outerHTML = labelHTML;147 } else {148 // If no label exists, insert before the player149 var player = tempDiv.querySelector('.mementor-tts-player');150 if (player) {151 player.insertAdjacentHTML('beforebegin', labelHTML);152 }153 }154 playerHTML = tempDiv.innerHTML;155 }156 157 // Create a position-specific class158 var positionClass = 'mementor-tts-' + position;159 160 // Use jQuery to create a DOM element we can modify161 var $tempContainer = $('<div>').html(playerHTML);162 var $playerContainer = $tempContainer.find('.mementor-tts-player-container');163 164 // Reset classes to the base container class to avoid duplications165 var baseClasses = ['mementor-tts-player-container'];166 167 // Add position class168 baseClasses.push(positionClass);169 170 // Add alignment class if specified171 if (typeof mementorTTSPlayerAlignment !== 'undefined') {172 var alignmentClass = 'mementor-tts-align-' + mementorTTSPlayerAlignment.alignment;173 baseClasses.push(alignmentClass);174 // Store alignment as data attribute175 $playerContainer.attr('data-alignment', mementorTTSPlayerAlignment.alignment);176 } else {177 // Use default alignment (left) if not specified178 baseClasses.push('mementor-tts-align-left');179 $playerContainer.attr('data-alignment', 'left');180 }181 182 // Add width class if specified183 if (typeof mementorTTSPlayerWidth !== 'undefined') {184 var widthClass = 'mementor-tts-width-' + mementorTTSPlayerWidth.width;185 baseClasses.push(widthClass);186 187 // Add max-width class if needed188 if (mementorTTSPlayerWidth.maxWidth) {189 baseClasses.push('mementor-tts-max-width-custom');190 $playerContainer.attr('data-max-width', mementorTTSPlayerWidth.maxWidth);191 $playerContainer.attr('data-max-width-unit', mementorTTSPlayerWidth.maxWidthUnit || '%');192 }193 } else {194 // Use default width (content_width) if not specified195 baseClasses.push('mementor-tts-width-content_width');196 }197 198 // Set the classes instead of adding to existing ones to avoid duplications199 $playerContainer.attr('class', baseClasses.join(' '));200 201 // Add data attribute to identify this as an auto-inserted player202 $playerContainer.attr('data-player-type', 'auto-inserted');203 204 // Add post ID if available205 if (typeof mementorTTS !== 'undefined' && mementorTTS.postId) {206 $playerContainer.attr('data-post-id', mementorTTS.postId);207 } else if (typeof mementorTTSStats !== 'undefined' && mementorTTSStats.postId) {208 $playerContainer.attr('data-post-id', mementorTTSStats.postId);209 }210 211 // Get the updated HTML212 playerHTML = $tempContainer.html();213 214 // Function to initialize the newly inserted player215 function initializeInsertedPlayer($player) {216 var audio = $player.find('audio')[0];217 var durationDisplay = $player.find('.mementor-tts-duration-display')[0];218 219 if (audio && durationDisplay) {220 // Force preload metadata221 audio.preload = "metadata";222 223 // Load the audio224 audio.load();225 226 // Add metadata loaded listener227 audio.addEventListener('loadedmetadata', function() {228 if (audio.duration && !isNaN(audio.duration)) {229 var minutes = Math.floor(audio.duration / 60);230 var seconds = Math.floor(audio.duration % 60);231 durationDisplay.textContent = minutes + ':' + (seconds < 10 ? '0' : '') + seconds;232 safeDebug('Player duration loaded:', durationDisplay.textContent);233 }234 });235 236 // Fallback: Check duration periodically237 var attempts = 0;238 var maxAttempts = 10;239 var checkInterval = setInterval(function() {240 if (attempts >= maxAttempts) {241 clearInterval(checkInterval);242 return;243 }244 245 if (audio.duration && !isNaN(audio.duration)) {246 var minutes = Math.floor(audio.duration / 60);247 var seconds = Math.floor(audio.duration % 60);248 durationDisplay.textContent = minutes + ':' + (seconds < 10 ? '0' : '') + seconds;249 clearInterval(checkInterval);250 }251 252 attempts++;253 }, 500);254 }255 }256 257 // Find the appropriate insertion point based on position258 var inserted = false;259 260 switch (position) {261 case 'before_title':262 var titleSelectors = [263 'h1.entry-title',264 '.entry-header h1',265 '.post-title',266 'article h1:first-of-type',267 '.elementor-widget-theme-post-title h1',268 '.page-title',269 '.site-content h1:first-of-type',270 '.wp-block-post-title',271 'h1.header-title' // Added for Uncode theme support272 ];273 274 // Try each selector275 $.each(titleSelectors, function(index, selector) {276 var titleElement = $(selector).first();277 if (titleElement.length) {278 // Add specific classes for position and context279 var $player = $(playerHTML)280 .addClass('mementor-tts-before-title-js')281 .addClass('mementor-tts-adjacent-to-' + selector.replace(/[^a-zA-Z0-9]/g, '-'));282 titleElement.before($player);283 initializeInsertedPlayer($player);284 inserted = true;285 safeDebug('Player inserted before title using selector:', selector);286 return false; // Break the loop287 }288 });289 break;290 291 case 'after_title':292 var titleSelectors = [293 'h1.entry-title',294 '.entry-header h1',295 '.post-title',296 'article h1:first-of-type',297 '.elementor-widget-theme-post-title h1',298 '.page-title',299 '.site-content h1:first-of-type',300 '.wp-block-post-title',301 'h1.header-title' // Added for Uncode theme support302 ];303 304 // Try each selector305 $.each(titleSelectors, function(index, selector) {306 var titleElement = $(selector).first();307 if (titleElement.length) {308 var $player = $(playerHTML)309 .addClass('mementor-tts-after-title-js')310 .addClass('mementor-tts-adjacent-to-' + selector.replace(/[^a-zA-Z0-9]/g, '-'));311 titleElement.after($player);312 initializeInsertedPlayer($player);313 inserted = true;314 safeDebug('Player inserted after title using selector:', selector);315 return false; // Break the loop316 }317 });318 break;319 320 case 'after_title_before_excerpt':321 var titleSelectors = [322 '.entry-header',323 'h1.entry-title',324 '.entry-header h1',325 '.post-title',326 'article h1:first-of-type',327 '.elementor-widget-theme-post-title h1',328 '.page-title',329 '.site-content h1:first-of-type',330 '.wp-block-post-title',331 'h1.header-title' // Added for Uncode theme support332 ];333 334 // Try each selector335 $.each(titleSelectors, function(index, selector) {336 var titleElement = $(selector).first();337 if (titleElement.length) {338 // Find the excerpt or content element339 var excerptElement = $('.entry-content, .post-content, .excerpt').first();340 341 // Create player with specific classes342 var $player = $(playerHTML)343 .addClass('mementor-tts-after-title-before-excerpt')344 .addClass('mementor-tts-adjacent-to-' + selector.replace(/[^a-zA-Z0-9]/g, '-'));345 346 if (excerptElement.length) {347 // Add class for excerpt context348 $player.addClass('mementor-tts-before-' + excerptElement.attr('class').split(' ')[0]);349 excerptElement.before($player);350 } else {351 // If no excerpt found, insert after title352 $player.addClass('mementor-tts-no-excerpt');353 titleElement.after($player);354 }355 356 initializeInsertedPlayer($player);357 inserted = true;358 safeDebug('Player inserted after title before excerpt using selector:', selector);359 return false; // Break the loop360 }361 });362 break;363 364 case 'after_title_after_excerpt':365 var titleSelectors = [366 '.entry-header',367 'h1.entry-title',368 '.entry-header h1',369 '.post-title',370 'article h1:first-of-type',371 '.elementor-widget-theme-post-title h1',372 '.page-title',373 '.site-content h1:first-of-type',374 '.wp-block-post-title',375 'h1.header-title' // Added for Uncode theme support376 ];377 378 // Try each selector379 $.each(titleSelectors, function(index, selector) {380 var titleElement = $(selector).first();381 if (titleElement.length) {382 // Find the excerpt element383 var excerptElement = $('.entry-excerpt, .excerpt, .post-excerpt').first();384 385 // Create player with specific classes386 var $player = $(playerHTML)387 .addClass('mementor-tts-after-title-after-excerpt')388 .addClass('mementor-tts-adjacent-to-' + selector.replace(/[^a-zA-Z0-9]/g, '-'));389 390 if (excerptElement.length) {391 // Add class for excerpt context392 $player.addClass('mementor-tts-after-' + excerptElement.attr('class').split(' ')[0]);393 excerptElement.after($player);394 } else {395 // If no excerpt found, try to find the content396 var contentElement = $('.entry-content, .post-content, article .content').first();397 if (contentElement.length) {398 $player.addClass('mementor-tts-before-' + contentElement.attr('class').split(' ')[0]);399 contentElement.before($player);400 } else {401 // If no content element found either402 $player.addClass('mementor-tts-no-excerpt-no-content');403 titleElement.after($player);404 }405 }406 407 initializeInsertedPlayer($player);408 inserted = true;409 safeDebug('Player inserted after title and excerpt using selector:', selector);410 return false; // Break the loop411 }412 });413 break;414 415 case 'after_content':416 var contentSelectors = [417 '.entry-content',418 '.post-content',419 'article .content',420 '.site-content',421 '.elementor-widget-theme-post-content',422 '.post-entry',423 '.content-area',424 'article'425 ];426 427 // Try each selector428 $.each(contentSelectors, function(index, selector) {429 var contentElement = $(selector).first();430 if (contentElement.length) {431 var $player = $(playerHTML)432 .addClass('mementor-tts-after-content')433 .addClass('mementor-tts-adjacent-to-' + selector.replace(/[^a-zA-Z0-9]/g, '-'));434 contentElement.append($player);435 initializeInsertedPlayer($player);436 inserted = true;437 safeDebug('Player inserted after content using selector:', selector);438 return false; // Break the loop439 }440 });441 break;442 }443 444 if (!inserted) {445 safeDebug('Player could not find appropriate insertion point');446 }447 448 // Add max-width CSS variable to the player container element after insertion449 function applyMaxWidthStyles() {450 // Return early if max width data is not defined451 if (typeof mementorTTSPlayerWidth === 'undefined') {452 safeDebug('Max width data not available');453 return;454 }455 456 // Find all player containers457 const playerContainers = document.querySelectorAll('.mementor-tts-player-container.mementor-tts-max-width-custom');458 459 // Get the max-width and unit from localized data460 const maxWidth = mementorTTSPlayerWidth.maxWidth || 100;461 const maxWidthUnit = mementorTTSPlayerWidth.maxWidthUnit || '%';462 463 // Apply the max-width as data attributes on each container464 playerContainers.forEach(container => {465 // Set the data attributes if not already set466 if (!container.hasAttribute('data-max-width')) {467 container.setAttribute('data-max-width', maxWidth);468 }469 if (!container.hasAttribute('data-max-width-unit')) {470 container.setAttribute('data-max-width-unit', maxWidthUnit);471 }472 473 // Remove any inline styles - we'll use CSS custom properties in the stylesheet474 if (container.hasAttribute('style') &&475 container.getAttribute('style').includes('--mementor-tts-max-width')) {476 container.removeAttribute('style');477 }478 });479 }480 481 // Call the function after the player is inserted482 document.addEventListener('DOMContentLoaded', function() {483 // Set a small delay to ensure the player is inserted484 setTimeout(applyMaxWidthStyles, 100);485 });486 487 // Also set up a mutation observer to catch dynamically inserted players488 const observer = new MutationObserver(function(mutations) {489 mutations.forEach(function(mutation) {490 if (mutation.addedNodes.length) {491 applyMaxWidthStyles();492 }493 });494 });495 496 // Start observing the document body for player insertion497 observer.observe(document.body, { childList: true, subtree: true });498 499 // Add a final initialization function to cleanup any duplicated classes and ensure proper data attributes500 document.addEventListener('DOMContentLoaded', function() {501 setTimeout(function() {502 // Find all player containers503 const playerContainers = document.querySelectorAll('.mementor-tts-player-container');504 505 // Process each container to clean up duplicated classes506 playerContainers.forEach(container => {507 // Remove any duplicate classes508 const classNames = [];509 const uniqueClasses = [];510 511 container.classList.forEach(className => {512 if (!classNames.includes(className)) {513 classNames.push(className);514 uniqueClasses.push(className);515 }516 });517 518 // Apply the unique classes519 if (classNames.length !== uniqueClasses.length) {520 container.setAttribute('class', uniqueClasses.join(' '));521 }522 523 // Remove any inline styles - we'll handle through CSS only524 if (container.hasAttribute('style')) {525 container.removeAttribute('style');526 }527 528 // Clean up label styling if needed - convert to data attribute529 const label = container.querySelector('.mementor-tts-player-label.label-color-custom');530 if (label && label.hasAttribute('style')) {531 const style = label.getAttribute('style');532 const colorMatch = style.match(/--mementor-tts-custom-label-color:\s*([^;]+)/);533 if (colorMatch && colorMatch[1]) {534 // Move from style to data attribute535 label.setAttribute('data-custom-color', colorMatch[1].trim());536 label.removeAttribute('style');537 }538 }539 });540 541 safeDebug('Player classes and styles cleaned up');542 }, 500); // Delay to ensure everything is loaded543 });544 40 }); // End jQuery(document).ready 545 41 } // End initializePlayerInsert -
text-to-speech-tts/trunk/readme.txt
r3495753 r3495969 6 6 Tested up to: 6.9 7 7 Requires PHP: 7.2 8 Stable tag: 3.1. 38 Stable tag: 3.1.4 9 9 License: GPLv3 or later 10 10 License URI: [https://www.gnu.org/licenses/gpl-3.0.txt](https://www.gnu.org/licenses/gpl-3.0.txt) … … 221 221 == Screenshots == 222 222 1. Dashboard with an overview of your text-to-speech settings, ready to generate audio with one click 223 2. Audio player added to posts automatically with no manual placement needed224 223 3. Voice and language selector. Choose from natural male and female voices in multiple languages 225 4. Mobile-ready player with responsive design that works on any device and screen size 224 3. Audio player added to posts automatically with no manual placement needed 225 4. Word Replacement & Pronunciation Controls 226 226 5. Engagement analytics. Track how many visitors listen to your audio content 227 227 6. Shortcode and block generator. Place the audio player anywhere on your site … … 236 236 237 237 == Changelog == 238 239 = 3.1.4 - 2026-03-31 = 240 241 * Removed: Remote telemetry collection and usage tracking to external servers 242 * Fixed: License page no longer blocked for free plan users 243 * Fixed: Some posts causing a server error when auto-generation is enabled 244 * Fixed: Browser console warning about unreachable code in player script 238 245 239 246 = 3.1.3 - 2026-03-31 = -
text-to-speech-tts/trunk/text-to-speech-tts.php
r3495753 r3495969 9 9 * Plugin URI: https://mementor.no/en/wordpress-plugins/text-to-speech/ 10 10 * Description: The easiest Text-to-Speech plugin for WordPress. Add natural voices, boost accessibility, and engage visitors with an instant audio player. 11 * Version: 3.1. 311 * Version: 3.1.4 12 12 * Author: Mementor AS 13 13 * Author URI: https://mementor.no/en/ … … 26 26 27 27 // Define plugin constants 28 define('MEMENTOR_TTS_VERSION', '3.1. 3');28 define('MEMENTOR_TTS_VERSION', '3.1.4'); 29 29 define('MEMENTOR_TTS_PLUGIN_DIR', plugin_dir_path(__FILE__)); 30 30 define('MEMENTOR_TTS_PLUGIN_URL', plugin_dir_url(__FILE__)); … … 150 150 } 151 151 152 // Clean up removed telemetry system (added in 3.1.3) 153 wp_clear_scheduled_hook('mementor_tts_send_remote_telemetry'); 154 delete_transient('mementor_tts_daily_usage'); 155 delete_option('mementor_tts_telemetry_consent'); 156 delete_option('mementor_tts_telemetry_consent_time'); 157 152 158 // Flush rewrite rules 153 159 flush_rewrite_rules(); 154 155 // Send Reddit conversion event on plugin activation156 mementor_tts_send_reddit_conversion();157 }158 159 /**160 * Send Reddit conversion event on plugin activation161 */162 function mementor_tts_send_reddit_conversion() {163 $site_url = get_site_url();164 $admin_email = get_option('admin_email');165 $wp_version = get_bloginfo('version');166 $plugin_version = defined('MEMENTOR_TTS_VERSION') ? MEMENTOR_TTS_VERSION : '1.0.0';167 168 // Generate a unique conversion ID169 $conversion_id = 'wp_tts_' . wp_generate_password(12, false);170 171 // Prepare the data to send172 $data = array(173 'event_type' => 'Installed',174 'conversion_id' => $conversion_id,175 'test_mode' => false,176 'event_at' => gmdate('c'),177 'metadata' => array(178 'site_url' => $site_url,179 'admin_email' => $admin_email,180 'wp_version' => $wp_version,181 'plugin_version' => $plugin_version,182 'plugin_name' => 'text-to-speech-tts'183 )184 );185 186 // Send the POST request187 $response = wp_remote_post('https://mementor.no/api/reddit-conversion/', array(188 'method' => 'POST',189 'timeout' => 30,190 'headers' => array(191 'Content-Type' => 'application/json'192 ),193 'body' => wp_json_encode($data)194 ));195 196 // Log the response for debugging (optional)197 if (is_wp_error($response)) {198 error_log('Reddit conversion error: ' . $response->get_error_message());199 } else {200 $response_code = wp_remote_retrieve_response_code($response);201 $response_body = wp_remote_retrieve_body($response);202 203 if ($response_code !== 200) {204 error_log('Reddit conversion failed with code ' . $response_code . ': ' . $response_body);205 } else {206 // Store the conversion ID for potential future use207 update_option('mementor_tts_reddit_conversion_id', $conversion_id, false);208 }209 }210 160 } 211 161 … … 219 169 wp_clear_scheduled_hook('mementor_tts_aggregate_player_stats'); 220 170 wp_clear_scheduled_hook('mementor_tts_aggregate_analytics'); 171 wp_clear_scheduled_hook('mementor_tts_send_remote_telemetry'); 172 173 // Clean up telemetry data 174 delete_transient('mementor_tts_daily_usage'); 175 delete_option('mementor_tts_telemetry_consent'); 176 delete_option('mementor_tts_telemetry_consent_time'); 221 177 222 178 // Flush rewrite rules
Note: See TracChangeset
for help on using the changeset viewer.