Changeset 3339444
- Timestamp:
- 08/05/2025 07:31:08 AM (8 months ago)
- Location:
- cryptx
- Files:
-
- 4 added
- 19 edited
- 11 copied
-
tags/4.0.0 (copied) (copied from cryptx/trunk)
-
tags/4.0.0/classes/Admin (copied) (copied from cryptx/trunk/classes/Admin)
-
tags/4.0.0/classes/Admin/ChangelogSettingsTab.php (modified) (5 diffs)
-
tags/4.0.0/classes/Admin/GeneralSettingsTab.php (modified) (3 diffs)
-
tags/4.0.0/classes/Admin/PresentationSettingsTab.php (modified) (9 diffs)
-
tags/4.0.0/classes/Config.php (copied) (copied from cryptx/trunk/classes/Config.php) (4 diffs)
-
tags/4.0.0/classes/CryptX.php (copied) (copied from cryptx/trunk/classes/CryptX.php) (20 diffs)
-
tags/4.0.0/classes/CryptXSettingsTabs.php (copied) (copied from cryptx/trunk/classes/CryptXSettingsTabs.php) (7 diffs)
-
tags/4.0.0/classes/SecureEncryption.php (added)
-
tags/4.0.0/classes/Util (copied) (copied from cryptx/trunk/classes/Util)
-
tags/4.0.0/cryptx.php (copied) (copied from cryptx/trunk/cryptx.php) (1 diff)
-
tags/4.0.0/js/cryptx.js (copied) (copied from cryptx/trunk/js/cryptx.js) (1 diff)
-
tags/4.0.0/js/cryptx.min.js (copied) (copied from cryptx/trunk/js/cryptx.min.js) (1 diff)
-
tags/4.0.0/readme.txt (copied) (copied from cryptx/trunk/readme.txt) (3 diffs)
-
tags/4.0.0/templates (copied) (copied from cryptx/trunk/templates)
-
tags/4.0.0/templates/admin/tabs/changelog.php (added)
-
tags/4.0.0/templates/admin/tabs/general.php (modified) (1 diff)
-
tags/4.0.0/templates/admin/tabs/howto.php (modified) (3 diffs)
-
tags/4.0.0/templates/admin/tabs/presentation.php (modified) (1 diff)
-
trunk/classes/Admin/ChangelogSettingsTab.php (modified) (5 diffs)
-
trunk/classes/Admin/GeneralSettingsTab.php (modified) (3 diffs)
-
trunk/classes/Admin/PresentationSettingsTab.php (modified) (9 diffs)
-
trunk/classes/Config.php (modified) (4 diffs)
-
trunk/classes/CryptX.php (modified) (20 diffs)
-
trunk/classes/CryptXSettingsTabs.php (modified) (7 diffs)
-
trunk/classes/SecureEncryption.php (added)
-
trunk/cryptx.php (modified) (1 diff)
-
trunk/js/cryptx.js (modified) (1 diff)
-
trunk/js/cryptx.min.js (modified) (1 diff)
-
trunk/readme.txt (modified) (3 diffs)
-
trunk/templates/admin/tabs/changelog.php (added)
-
trunk/templates/admin/tabs/general.php (modified) (1 diff)
-
trunk/templates/admin/tabs/howto.php (modified) (3 diffs)
-
trunk/templates/admin/tabs/presentation.php (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
cryptx/tags/4.0.0/classes/Admin/ChangelogSettingsTab.php
r3323475 r3339444 5 5 use CryptX\Config; 6 6 7 /**8 * Class ChangelogSettingsTab9 * Handles the changelog tab functionality in the CryptX plugin admin interface10 */11 7 class ChangelogSettingsTab 12 8 { … … 15 11 */ 16 12 private Config $config; 13 14 /** 15 * Template path 16 */ 17 private const TEMPLATE_PATH = CRYPTX_DIR_PATH . 'templates/admin/tabs/changelog.php'; 17 18 18 19 /** … … 31 32 public function render(): void 32 33 { 33 echo '<h4>' . esc_html__('Changelog', 'cryptx') . '</h4>'; 34 $this->renderChangelogContent(); 34 if ('changelog' !== $this->getActiveTab()) { 35 return; 36 } 37 38 $changelogs = $this->getChangelogData(); 39 $this->renderTemplate(self::TEMPLATE_PATH, ['changelogs' => $changelogs]); 35 40 } 36 41 37 42 /** 38 * Parse and render changelog content from readme.txt43 * Get changelog data 39 44 */ 40 private function renderChangelogContent(): void45 private function getChangelogData(): array 41 46 { 42 47 $readmePath = CRYPTX_DIR_PATH . '/readme.txt'; 43 48 if (!file_exists($readmePath)) { 44 return ;49 return []; 45 50 } 46 51 47 52 $fileContents = file_get_contents($readmePath); 48 53 if ($fileContents === false) { 49 return ;54 return []; 50 55 } 51 56 52 $changelogs = $this->parseChangelog($fileContents); 53 foreach ($changelogs as $log) { 54 echo wp_kses_post("<dl>" . implode("", $log) . "</dl>"); 57 return $this->parseChangelog($fileContents); 58 } 59 60 /** 61 * Render template with data 62 */ 63 private function renderTemplate(string $path, array $data): void 64 { 65 if (!file_exists($path)) { 66 throw new \RuntimeException(sprintf('Template file not found: %s', $path)); 55 67 } 68 69 extract($data); 70 include $path; 71 } 72 73 /** 74 * Get active tab 75 */ 76 private function getActiveTab(): string 77 { 78 return sanitize_text_field($_GET['tab'] ?? 'general'); 56 79 } 57 80 … … 89 112 90 113 for ($i = 1; $i <= count($_sections); $i += 2) { 114 if (!isset($_sections[$i - 1]) || !isset($_sections[$i])) { 115 continue; 116 } 117 91 118 $title = $_sections[$i - 1]; 92 119 $sections[str_replace(' ', '_', strtolower($title))] = [ … … 111 138 112 139 for ($i = 1; $i <= count($_changelogs); $i += 2) { 140 if (!isset($_changelogs[$i - 1]) || !isset($_changelogs[$i])) { 141 continue; 142 } 143 113 144 $version = $_changelogs[$i - 1]; 114 145 $content = ltrim($_changelogs[$i], "\n"); 115 $content = str_replace("* ", "<li>", $content);116 $content = str_replace("\n", " </li>\n", $content);117 146 118 $changelogs[] = [ 119 'version' => "<dt>" . esc_html($version) . "</dt>", 120 'content' => "<dd><ul>" . wp_kses_post($content) . "</ul></dd>" 121 ]; 147 // Parse changelog items 148 $items = $this->parseChangelogItems($content); 149 150 if (!empty($items)) { 151 $changelogs[] = [ 152 'version' => $version, 153 'items' => $items 154 ]; 155 } 122 156 } 123 157 124 158 return $changelogs; 125 159 } 160 161 /** 162 * Parse individual changelog items 163 * 164 * @param string $content 165 * @return array 166 */ 167 private function parseChangelogItems(string $content): array 168 { 169 $lines = explode("\n", $content); 170 $items = []; 171 172 foreach ($lines as $line) { 173 $line = trim($line); 174 if (empty($line)) { 175 continue; 176 } 177 178 // Remove leading asterisk and clean up 179 if (strpos($line, '* ') === 0) { 180 $line = substr($line, 2); 181 } 182 183 if (!empty($line)) { 184 $items[] = trim($line); 185 } 186 } 187 188 return $items; 189 } 126 190 } -
cryptx/tags/4.0.0/classes/Admin/GeneralSettingsTab.php
r3323475 r3339444 18 18 'disable_rss' => 0, 19 19 'java' => 1, 20 'load_java' => 1 20 'load_java' => 1, 21 'use_secure_encryption' => 0, 22 'encryption_mode' => 'legacy' 21 23 ]; 24 22 25 public function __construct(Config $config) { 23 26 $this->config = $config; … … 33 36 $settings = [ 34 37 'options' => $options, // Pass all options 38 'securitySettings' => [ 39 'use_secure_encryption' => [ 40 'label' => __('Use Secure Encryption', 'cryptx'), 41 'description' => __('Enable AES-256-GCM encryption for enhanced security (recommended for new installations)', 'cryptx'), 42 'value' => $options['use_secure_encryption'] ?? 0 43 ], 44 'encryption_mode' => [ 45 'label' => __('Encryption Mode', 'cryptx'), 46 'description' => __('Choose encryption method. Legacy mode maintains backward compatibility with existing encrypted emails.', 'cryptx'), 47 'value' => $options['encryption_mode'] ?? 'legacy', 48 'options' => [ 49 'legacy' => __('Legacy (Compatible)', 'cryptx'), 50 'secure' => __('Secure (AES-256-GCM)', 'cryptx') 51 ] 52 ] 53 ], 35 54 'applyTo' => [ 36 55 'the_content' => [ … … 50 69 'widget_text' => [ 51 70 'label' => __('Widgets', 'cryptx'), 52 'description' => __('( works only on all widgets, not on a single widget!)', 'cryptx')71 'description' => __('(applies to all text and HTML widgets)', 'cryptx') 53 72 ] 54 73 ], -
cryptx/tags/4.0.0/classes/Admin/PresentationSettingsTab.php
r3323475 r3339444 13 13 } 14 14 15 /** 16 * Renders the template for the active tab. 17 * 18 * The function checks if the 'presentation' tab is active. If not, it returns without further 19 * execution. When active, it retrieves configuration options, organizes them into a structured 20 * settings array, and renders the template with the specified settings. 21 * 22 * @return void 23 */ 15 24 public function render(): void { 16 25 if ('presentation' !== $this->getActiveTab()) { … … 108 117 } 109 118 119 /** 120 * Renders a template file by including it and extracting the provided data for use within the template scope. 121 * 122 * @param string $path The file path to the template to be rendered. Must be a valid, existing file. 123 * @param array $data An associative array of data to be extracted and made available to the template. 124 * 125 * @return void 126 * 127 * @throws \RuntimeException If the specified template file does not exist. 128 */ 110 129 private function renderTemplate(string $path, array $data): void { 111 130 if (!file_exists($path)) { … … 117 136 } 118 137 138 /** 139 * Retrieves the active tab from the request, defaulting to 'general' if not specified. 140 * 141 * @return string The sanitized active tab value from the request or 'general' if no tab is provided. 142 */ 119 143 private function getActiveTab(): string { 120 144 return sanitize_text_field($_GET['tab'] ?? 'general'); 121 145 } 122 146 147 /** 148 * Retrieves the available font options by scanning a directory for font files. 149 * 150 * @return array An associative array where the keys are the font file names and the values are the font names without the '.ttf' extension. 151 */ 123 152 private function getFontOptions(): array { 124 153 $fonts = []; … … 132 161 } 133 162 163 /** 164 * Retrieves a list of files in a specified directory that match the given file extensions. 165 * 166 * @param string $path The path to the directory to scan for files. 167 * @param array $extensions An array of file extensions to filter the files by. 168 * Only files matching these extensions will be included in the results. 169 * 170 * @return array An array of filenames from the specified directory that match the provided extensions. 171 * If the directory does not exist, an empty array is returned. 172 */ 134 173 private function getFilesInDirectory(string $path, array $extensions): array { 135 174 if (!is_dir($path)) { … … 149 188 } 150 189 190 /** 191 * Saves and processes the settings for the application. Handles security checks, 192 * optional reset functionality, and updates sanitized settings to the configuration. 193 * 194 * @param array $data The input array containing settings data to be saved. 195 * The data is sanitized before being saved into the configuration. 196 * 197 * @return void This method does not return a value. 198 */ 151 199 public function saveSettings(array $data): void { 152 200 if (!current_user_can('manage_options')) { … … 156 204 check_admin_referer('cryptX'); 157 205 158 $sanitized = $this->sanitizeSettings($data); 159 206 // Handle reset button 160 207 if (!empty($_POST['cryptX_var_reset'])) { 161 208 $this->config->reset(); 209 add_settings_error( 210 'cryptx_messages', 211 'settings_reset', 212 __('Presentation settings have been reset to defaults.', 'cryptx'), 213 'updated' 214 ); 162 215 return; 163 216 } 164 217 218 $sanitized = $this->sanitizeSettings($data); 165 219 $this->config->update($sanitized); 166 220 … … 173 227 } 174 228 229 /** 230 * Sanitizes an array of settings data to ensure secure and valid formatting. 231 * 232 * @param array $data The input array containing settings data to be sanitized. 233 * Expected keys include CSS settings, text replacements, link text options, 234 * and font settings, each of which is sanitized according to its respective type. 235 * 236 * @return array The sanitized array with cleaned or validated values for all specified settings. 237 */ 175 238 private function sanitizeSettings(array $data): array { 176 239 $sanitized = []; … … 180 243 $sanitized['css_class'] = sanitize_html_class($data['css_class'] ?? ''); 181 244 182 // Text replacements 183 $sanitized['at'] = sanitize_text_field($data['at'] ?? ' [at] ');184 $sanitized['dot'] = sanitize_text_field($data['dot'] ?? ' [dot] ');245 // Text replacements - preserve whitespace for 'at' and 'dot' fields 246 $sanitized['at'] = wp_kses_post($data['at'] ?? ' [at] '); 247 $sanitized['dot'] = wp_kses_post($data['dot'] ?? ' [dot] '); 185 248 186 249 // Link text options … … 199 262 return $sanitized; 200 263 } 264 201 265 } -
cryptx/tags/4.0.0/classes/Config.php
r3323475 r3339444 33 33 'whiteList' => 'jpeg,jpg,png,gif', 34 34 'disable_rss' => 1, // Disable CryptX in RSS feeds by default 35 'encryption_mode' => 'secure', // Changed to 'secure' by default 36 'encryption_password' => null, // Will be auto-generated if null 37 'use_secure_encryption' => 1, // Enable secure encryption by default 38 ]; 39 40 // Define the actual widget filters that will be used when widget_text is enabled 41 private const WIDGET_FILTERS = [ 42 'widget_text', // Legacy text widget (pre-4.9) 43 'widget_text_content', // Modern text widget (4.9+) 44 'widget_custom_html_content' // Custom HTML widget (4.8.1+) 35 45 ]; 36 46 … … 99 109 public function getVersion(): ?string { 100 110 return $this->options['version']; 111 } 112 113 /** 114 * Get the actual widget filters to be applied 115 * 116 * @return array 117 */ 118 public function getWidgetFilters(): array { 119 return self::WIDGET_FILTERS; 101 120 } 102 121 … … 123 142 // Convert checkbox values to integers 124 143 foreach (['the_content', 'the_meta_key', 'the_excerpt', 'comment_text', 125 'widget_text', 'autolink', 'metaBox', 'disable_rss' ] as $key) {144 'widget_text', 'autolink', 'metaBox', 'disable_rss', 'use_secure_encryption'] as $key) { 126 145 if (isset($newOptions[$key])) { 127 146 $newOptions[$key] = (int)$newOptions[$key]; … … 165 184 $this->save(); 166 185 } 186 187 /** 188 * Retrieves the encryption mode configured in the options. 189 * 190 * @return string Returns the encryption mode as a string. Defaults to 'secure' if not set. 191 */ 192 public function getEncryptionMode(): string 193 { 194 return $this->options['encryption_mode'] ?? 'secure'; 195 } 196 197 /** 198 * Checks if secure encryption is enabled in the options. 199 * 200 * @return bool Returns true if secure encryption is enabled, false otherwise. 201 */ 202 public function isSecureEncryptionEnabled(): bool 203 { 204 return (bool) ($this->options['use_secure_encryption'] ?? true); 205 } 206 207 /** 208 * Retrieves the encryption password configured in the options or generates a secure password if not set. 209 * 210 * @return string Returns the encryption password as a string. 211 */ 212 public function getEncryptionPassword(): string 213 { 214 if (empty($this->options['encryption_password'])) { 215 // Generate a secure password based on WordPress keys 216 $this->options['encryption_password'] = hash('sha256', 217 (defined('AUTH_KEY') ? AUTH_KEY : '') . 218 (defined('SECURE_AUTH_KEY') ? SECURE_AUTH_KEY : '') . 219 get_site_url() 220 ); 221 $this->save(); 222 } 223 return $this->options['encryption_password']; 224 } 167 225 } -
cryptx/tags/4.0.0/classes/CryptX.php
r3335910 r3339444 25 25 { 26 26 $this->settingsTabs = new CryptXSettingsTabs($this); 27 $this->config = new Config( get_option('cryptX', []));27 $this->config = new Config(get_option('cryptX', [])); 28 28 self::$cryptXOptions = $this->loadCryptXOptionsWithDefaults(); 29 29 } … … 62 62 { 63 63 $this->checkAndUpdateVersion(); 64 $this->addUniversalWidgetFilters(); // Add this line 64 65 $this->initializePluginFilters(); 65 66 $this->registerCoreHooks(); … … 86 87 * @return void 87 88 */ 88 private function initializePluginFilters(): void 89 { 90 foreach (self::$cryptXOptions['filter'] as $filter) { 91 if (isset(self::$cryptXOptions[$filter]) && self::$cryptXOptions[$filter]) { 92 $this->addPluginFilters($filter); 89 public function initializePluginFilters(): void 90 { 91 if (empty($this->config)) { 92 return; 93 } 94 95 $activeFilters = $this->config->getActiveFilters(); 96 97 foreach ($activeFilters as $filter) { 98 if ($filter === 'widget_text') { 99 $this->addWidgetFilters(); 100 } else { 101 // Add autolink filters for non-widget filters if autolink is enabled 102 if ($this->config->isAutolinkEnabled()) { 103 $this->addAutoLinkFilters($filter, 10); 104 } 105 $this->addOtherFilters($filter); 93 106 } 94 107 } … … 236 249 } 237 250 238 // Process content 251 // Process content (inline the encryptAndLinkContent logic) 239 252 if (self::$cryptXOptions['autolink'] ?? false) { 240 253 $content = $this->addLinkToEmailAddresses($content, true); 241 254 } 242 255 243 $processedContent = $this->encryptAndLinkContent($content, true); 256 $content = $this->findEmailAddressesInContent($content, true); 257 $processedContent = $this->replaceEmailInContent($content, true); 244 258 245 259 // Reset options to defaults … … 263 277 } 264 278 265 private function processAndEncryptEmails(EmailProcessingConfig $config): string 266 { 267 $content = $this->encryptMailtoLinks($config); 268 return $this->encryptPlainEmails($content, $config); 269 } 270 271 private function encryptMailtoLinks(EmailProcessingConfig $config): ?string 272 { 273 $content = $config->getContent(); 274 if ($content === null) { 275 return null; 276 } 277 278 $postId = $config->getPostId() ?? $this->getCurrentPostId(); 279 280 if (!$this->isIdExcluded($postId) || $config->isShortcode()) { 281 return preg_replace_callback( 282 self::MAILTO_PATTERN, 283 [$this, 'encryptEmailAddress'], 284 $content 285 ); 286 } 287 288 return $content; 289 } 290 291 private function encryptPlainEmails(string $content, EmailProcessingConfig $config): string 292 { 293 $postId = $config->getPostId() ?? $this->getCurrentPostId(); 294 295 if ((!$this->isIdExcluded($postId) || $config->isShortcode()) && !empty($content)) { 296 return preg_replace_callback( 297 self::EMAIL_PATTERN, 298 [$this, 'encodeEmailToLinkText'], 299 $content 300 ); 301 } 302 303 return $content; 304 } 305 279 /** 280 * Retrieves the ID of the current post. 281 * 282 * @return int The current post ID if available, or -1 if no post object is present. 283 */ 306 284 private function getCurrentPostId(): int 307 285 { … … 362 340 363 341 /** 364 * Add plugin filters.365 *366 * This function adds the specified plugin filter if the 'autolink' key is present and its value is true in the global $cryptXOptions variable.367 * It also adds the 'autolink' function as a filter to the $filterName if the global $shortcode_tags variable is not empty.368 * Additionally, this function calls the addCommonFilters() and addOtherFilters() functions at specific points.369 *370 * @param string $filterName The name of the filter to add.371 *372 * @return void373 */374 private function addPluginFilters(string $filterName): void375 {376 global $shortcode_tags;377 378 if (array_key_exists('autolink', self::$cryptXOptions) && self::$cryptXOptions['autolink']) {379 $this->addAutoLinkFilters($filterName);380 if (!empty($shortcode_tags)) {381 $this->addAutoLinkFilters($filterName, 11);382 }383 }384 $this->addOtherFilters($filterName);385 }386 387 /**388 342 * Adds common filters to a given filter name. 389 343 * … … 412 366 private function addOtherFilters(string $filterName): void 413 367 { 414 add_filter($filterName, [$this, 'findEmailAddressesInContent'], 12); 415 add_filter($filterName, [$this, 'replaceEmailInContent'], 13); 368 // Check if this is a widget filter 369 $widgetFilters = $this->config->getWidgetFilters(); 370 $isWidgetFilter = in_array($filterName, $widgetFilters); 371 372 if ($isWidgetFilter) { 373 // Use higher priority for widget filters (after autolink at priority 10) 374 add_filter($filterName, [$this, 'findEmailAddressesInContent'], 15); 375 add_filter($filterName, [$this, 'replaceEmailInContent'], 16); 376 } else { 377 // Standard priorities for other filters 378 add_filter($filterName, [$this, 'findEmailAddressesInContent'], 12); 379 add_filter($filterName, [$this, 'replaceEmailInContent'], 13); 380 } 381 } 382 383 384 /** 385 * Adds and applies widget filters from the configuration. 386 * 387 * @return void 388 */ 389 private function addWidgetFilters(): void 390 { 391 $widgetFilters = $this->config->getWidgetFilters(); 392 393 foreach ($widgetFilters as $widgetFilter) { 394 $this->addAutoLinkFilters($widgetFilter, 10); 395 $this->addOtherFilters($widgetFilter); 396 } 416 397 } 417 398 … … 442 423 global $post; 443 424 444 if (self::$cryptXOptions['disable_rss'] && $this->isRssFeed()) return $content; 425 if (self::$cryptXOptions['disable_rss'] && $this->isRssFeed()) return $content; 426 427 // Check if current filter is a widget filter 428 $widgetFilters = $this->config->getWidgetFilters(); 429 $isWidgetContext = in_array(current_filter(), $widgetFilters); 445 430 446 431 $postId = (is_object($post)) ? $post->ID : -1; 447 if ((!$this->isIdExcluded($postId) || $isShortcode) && !empty($content)) { 432 433 // For widgets, always process; for other content, check exclusion rules 434 if (($isWidgetContext || !$this->isIdExcluded($postId) || $isShortcode) && !empty($content)) { 448 435 $content = $this->replaceEmailWithLinkText($content); 449 436 } … … 451 438 return $content; 452 439 } 440 453 441 454 442 /** … … 618 606 global $post; 619 607 620 if (self::$cryptXOptions['disable_rss'] && $this->isRssFeed()) return $content;608 if (self::$cryptXOptions['disable_rss'] && $this->isRssFeed()) return $content; 621 609 622 610 if ($content === null) { … … 624 612 } 625 613 614 // Check if current filter is a widget filter 615 $widgetFilters = $this->config->getWidgetFilters(); 616 $isWidgetContext = in_array(current_filter(), $widgetFilters); 617 626 618 $postId = (is_object($post)) ? $post->ID : -1; 627 619 $isIdExcluded = $this->isIdExcluded($postId); 628 620 629 // FIXED: Added 's' modifier to handle multiline HTML (like Elementor buttons)630 621 $mailtoRegex = '/<a\s+[^>]*href=(["\'])mailto:([^"\']+)\1[^>]*>(.*?)<\/a>/is'; 631 622 632 if ((!$isIdExcluded || $shortcode !== null)) { 633 $content = preg_replace_callback($mailtoRegex, [$this, 'encryptEmailAddressNew'], $content); 623 // For widgets, always process since there's no specific post context 624 // For other content, check exclusion rules 625 if ($isWidgetContext || !$isIdExcluded || $shortcode) { 626 // $content = preg_replace_callback($mailtoRegex, [$this, 'encryptEmailAddressNew'], $content); 627 $content = preg_replace_callback($mailtoRegex, [$this, 'encryptEmailAddressSecure'], $content); 634 628 } 635 629 636 630 return $content; 637 631 } 632 638 633 639 634 /** … … 659 654 660 655 $return = $originalValue; 661 656 662 657 // Apply JavaScript handler if enabled 663 658 if (!empty(self::$cryptXOptions['java'])) { … … 682 677 683 678 /** 684 * Encrypts an email address found in the search results and modifies it to safeguard against email harvesting. 685 * 686 * @param array $searchResults Array containing search result data, where: 687 * - Index 0 contains the full match. 688 * - Index 2 contains the email address. 689 * - Index 3 contains the link text for the email. 690 * @return string The encrypted or modified email link. 679 * Encrypts an email address within the provided search results and generates a secure or obfuscated link. 680 * If secure encryption is enabled, the function uses secure encryption. Otherwise, it falls back to legacy methods 681 * or antispambot obfuscation if JavaScript is not enabled. Additional CSS attributes can be added if specified. 682 * 683 * @param array $searchResults The array containing match results: 684 * - Index 0: The full match value (original string), 685 * - Index 2: The email address to encrypt, 686 * - Index 3: The link text for the email link. 687 * @return string Returns the modified string where the email address is encrypted or obfuscated based on the configuration. 691 688 */ 692 689 private function encryptEmailAddressNew(array $searchResults): string … … 708 705 // Apply JavaScript handler if enabled 709 706 if (!empty(self::$cryptXOptions['java'])) { 710 $javaHandler = "javascript:DeCryptX('" . $this->generateHashFromString($emailAddress) . "')"; 707 // Check if secure encryption is enabled and working 708 if ($this->config->isSecureEncryptionEnabled()) { 709 try { 710 // Use secure encryption - encrypt the full mailto URL 711 $password = $this->config->getEncryptionPassword(); 712 $mailtoUrl = 'mailto:' . $emailAddress; 713 $encryptedEmail = SecureEncryption::encrypt($mailtoUrl, $password); 714 715 $javaHandler = "javascript:secureDecryptAndNavigate('" . 716 $this->escapeJavaScript($encryptedEmail) . "', '" . 717 $this->escapeJavaScript($password) . "')"; 718 } catch (\Exception $e) { 719 // Fallback to legacy encryption if secure encryption fails 720 error_log('CryptX Secure Encryption failed: ' . $e->getMessage()); 721 $encryptedEmail = $this->generateHashFromString($emailAddress); 722 $javaHandler = "javascript:DeCryptX('" . $this->escapeJavaScript($encryptedEmail) . "')"; 723 } 724 } else { 725 // Use legacy encryption 726 $encryptedEmail = $this->generateHashFromString($emailAddress); 727 $javaHandler = "javascript:DeCryptX('" . $this->escapeJavaScript($encryptedEmail) . "')"; 728 } 729 711 730 $return = str_replace('mailto:' . $emailAddress, $javaHandler, $originalValue); 712 731 } else { 713 // Only apply antispambot if JavaScript is not enabled 714 $return = str_replace('mailto:' . $emailAddress, antispambot('mailto:' . $emailAddress), $return); 732 // Fallback to antispambot if JavaScript is not enabled 733 $return = str_replace('mailto:' . $emailAddress, 734 antispambot('mailto:' . $emailAddress), $return); 715 735 } 716 736 717 737 // Add CSS attributes if specified 718 738 if (!empty(self::$cryptXOptions['css_id'])) { 719 $return = preg_replace('/(<a\s+[^>]*)(>)/i', '$1 id="' . self::$cryptXOptions['css_id'] . '"$2', $return); 739 $return = preg_replace('/(<a\s+[^>]*)(>)/i', 740 '$1 id="' . self::$cryptXOptions['css_id'] . '"$2', $return); 720 741 } 721 742 722 743 if (!empty(self::$cryptXOptions['css_class'])) { 723 $return = preg_replace('/(<a\s+[^>]*)(>)/i', '$1 class="' . self::$cryptXOptions['css_class'] . '"$2', $return); 744 $return = preg_replace('/(<a\s+[^>]*)(>)/i', 745 '$1 class="' . self::$cryptXOptions['css_class'] . '"$2', $return); 724 746 } 725 747 … … 768 790 { 769 791 global $post; 792 793 // Check if current filter is a widget filter 794 $widgetFilters = $this->config->getWidgetFilters(); 795 $isWidgetContext = in_array(current_filter(), $widgetFilters); 796 770 797 $postID = is_object($post) ? $post->ID : -1; 771 798 772 if ($this->isIdExcluded($postID) && !$shortcode) { 799 // For widgets, always process; for other content, check exclusion rules 800 if (!$isWidgetContext && $this->isIdExcluded($postID) && !$shortcode) { 773 801 return $content; 774 802 } 775 803 776 $emailPattern = "[_a-zA-Z0-9-+]+(\ .[_a-zA-Z0-9-+]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(\.[a-zA-Z]{2,})";804 $emailPattern = "[_a-zA-Z0-9-+]+(\\.[_a-zA-Z0-9-+]+)*@[a-zA-Z0-9-]+(\\.[a-zA-Z0-9-]+)*(\\.[a-zA-Z]{2,})"; 777 805 $linkPattern = "<a href=\"mailto:\\2\">\\2</a>"; 778 806 $src = [ 779 "/([\ s])($emailPattern)/si",807 "/([\\s])($emailPattern)/si", 780 808 "/(>)($emailPattern)(<)/si", 781 "/(\ ()($emailPattern)(\))/si",782 "/(>)($emailPattern)([\ s])/si",783 "/([\ s])($emailPattern)(<)/si",809 "/(\\()($emailPattern)(\\))/si", 810 "/(>)($emailPattern)([\\s])/si", 811 "/([\\s])($emailPattern)(<)/si", 784 812 "/^($emailPattern)/si", 785 813 "/(<a[^>]*>)<a[^>]*>/", 786 "/(<\ /A>)<\/A>/i"814 "/(<\\/A>)<\\/A>/i" 787 815 ]; 788 816 $tar = [ … … 1114 1142 } 1115 1143 1144 /** 1145 * Converts an associative array into an argument string. 1146 * 1147 * @param array $args An optional associative array where keys represent argument names and values represent argument values. 1148 * @return string A formatted string of arguments where each key-value pair is encoded and concatenated. 1149 */ 1116 1150 public function convertArrayToArgumentString(array $args = []): string 1117 1151 { … … 1140 1174 * Adds plugin action links to the WordPress plugin row 1141 1175 * 1142 * @param array $links Existing plugin row links1143 * @param string $file Plugin file path1176 * @param array $links Existing plugin row links 1177 * @param string $file Plugin file path 1144 1178 * @return array Modified plugin row links 1145 1179 */ … … 1159 1193 1160 1194 /** 1161 * Creates the settings link for the plugin 1195 * Creates and returns a settings link for the options page. 1196 * 1197 * @return string The HTML link to the settings page. 1162 1198 */ 1163 1199 private function create_settings_link(): string … … 1171 1207 1172 1208 /** 1173 * Creates the donation link for the plugin 1209 * Creates and returns a donation link in HTML format. 1210 * 1211 * @return string The HTML string for the donation link. 1174 1212 */ 1175 1213 private function create_donation_link(): string … … 1181 1219 ); 1182 1220 } 1221 1222 /** 1223 * Adds a universal filter for all widget types by hooking into the widget display process. 1224 * 1225 * @return void 1226 */ 1227 private function addUniversalWidgetFilters(): void 1228 { 1229 // Hook into the widget display process to catch all widget types 1230 add_filter('widget_display_callback', [$this, 'processWidgetContent'], 10, 3); 1231 } 1232 1233 /** 1234 * Processes the widget content to detect and modify email addresses. 1235 * 1236 * @param array $instance The current widget instance settings. 1237 * @param object $widget The widget object being processed. 1238 * @param array $args Additional arguments passed by the widget function. 1239 * 1240 * @return array The modified widget instance with updated content. 1241 */ 1242 public function processWidgetContent($instance, $widget, $args) 1243 { 1244 // Only process if widget_text option is enabled 1245 if (!(self::$cryptXOptions['widget_text'] ?? false)) { 1246 return $instance; 1247 } 1248 1249 // Check if instance has text content (traditional text widgets) 1250 if (isset($instance['text']) && stripos($instance['text'], '@') !== false) { 1251 $instance['text'] = $this->addLinkToEmailAddresses($instance['text']); 1252 $instance['text'] = $this->findEmailAddressesInContent($instance['text']); 1253 $instance['text'] = $this->replaceEmailInContent($instance['text']); 1254 } 1255 1256 // Check if instance has content field (block widgets) 1257 if (isset($instance['content']) && stripos($instance['content'], '@') !== false) { 1258 $instance['content'] = $this->addLinkToEmailAddresses($instance['content']); 1259 $instance['content'] = $this->findEmailAddressesInContent($instance['content']); 1260 $instance['content'] = $this->replaceEmailInContent($instance['content']); 1261 } 1262 1263 return $instance; 1264 } 1265 1266 /** 1267 * Generates hash using secure or legacy encryption based on settings 1268 * 1269 * @param string $inputString 1270 * @return string 1271 */ 1272 private function generateSecureHashFromString(string $inputString): string 1273 { 1274 if ($this->config->isSecureEncryptionEnabled()) { 1275 try { 1276 $password = $this->config->getEncryptionPassword(); 1277 return SecureEncryption::encrypt($inputString, $password); 1278 } catch (\Exception $e) { 1279 error_log('CryptX Secure Encryption failed: ' . $e->getMessage()); 1280 // Fallback to legacy encryption 1281 return $this->generateHashFromString($inputString); 1282 } 1283 } 1284 1285 return $this->generateHashFromString($inputString); 1286 } 1287 1288 /** 1289 * Enhanced email encryption with security validation 1290 * 1291 * @param array $searchResults 1292 * @return string 1293 */ 1294 private function encryptEmailAddressSecure(array $searchResults): string 1295 { 1296 $originalValue = $searchResults[0]; // Full match 1297 $emailAddress = $searchResults[2]; // Email address 1298 $linkText = $searchResults[3]; // Link text 1299 1300 if (strpos($emailAddress, '@') === self::NOT_FOUND) { 1301 return $originalValue; 1302 } 1303 1304 if (str_starts_with($emailAddress, self::SUBJECT_IDENTIFIER)) { 1305 return $originalValue; 1306 } 1307 1308 $return = $originalValue; 1309 1310 // Apply JavaScript handler if enabled 1311 if (!empty(self::$cryptXOptions['java'])) { 1312 $encryptionMode = $this->config->getEncryptionMode(); 1313 1314 // Determine which encryption method to use 1315 if ($encryptionMode === 'secure' && 1316 $this->config->isSecureEncryptionEnabled() && 1317 class_exists('CryptX\SecureEncryption')) { 1318 1319 // Use modern AES-256-GCM encryption 1320 try { 1321 $password = $this->config->getEncryptionPassword(); 1322 $mailtoUrl = 'mailto:' . $emailAddress; 1323 $encryptedEmail = SecureEncryption::encrypt($mailtoUrl, $password); 1324 1325 $javaHandler = "javascript:secureDecryptAndNavigate('" . 1326 $this->escapeJavaScript($encryptedEmail) . "', '" . 1327 $this->escapeJavaScript($password) . "')"; 1328 } catch (\Exception $e) { 1329 // Fallback to legacy if secure encryption fails 1330 error_log('CryptX Secure Encryption failed, falling back to legacy: ' . $e->getMessage()); 1331 $encryptedEmail = $this->generateHashFromString($emailAddress); 1332 $javaHandler = "javascript:DeCryptX('" . $this->escapeJavaScript($encryptedEmail) . "')"; 1333 } 1334 } else { 1335 // Use legacy encryption (original algorithm) 1336 $encryptedEmail = $this->generateHashFromString($emailAddress); 1337 $javaHandler = "javascript:DeCryptX('" . $this->escapeJavaScript($encryptedEmail) . "')"; 1338 } 1339 1340 $return = str_replace('mailto:' . $emailAddress, $javaHandler, $originalValue); 1341 } else { 1342 // Fallback to antispambot if JavaScript is not enabled 1343 $return = str_replace('mailto:' . $emailAddress, 1344 antispambot('mailto:' . $emailAddress), $return); 1345 } 1346 1347 // Add CSS attributes if specified 1348 if (!empty(self::$cryptXOptions['css_id'])) { 1349 $return = preg_replace('/(<a\s+[^>]*)(>)/i', 1350 '$1 id="' . self::$cryptXOptions['css_id'] . '"$2', $return); 1351 } 1352 1353 if (!empty(self::$cryptXOptions['css_class'])) { 1354 $return = preg_replace('/(<a\s+[^>]*)(>)/i', 1355 '$1 class="' . self::$cryptXOptions['css_class'] . '"$2', $return); 1356 } 1357 1358 return $return; 1359 } 1360 1361 /** 1362 * Escapes string for safe JavaScript usage 1363 * 1364 * @param string $string 1365 * @return string 1366 */ 1367 private function escapeJavaScript(string $string): string 1368 { 1369 return str_replace( 1370 ['\\', "'", '"', "\n", "\r", "\t"], 1371 ['\\\\', "\\'", '\\"', '\\n', '\\r', '\\t'], 1372 $string 1373 ); 1374 } 1375 1376 /** 1377 * Secure URL validation 1378 * 1379 * @param string $url 1380 * @return bool 1381 */ 1382 private function isValidUrl(string $url): bool 1383 { 1384 return SecureEncryption::validateUrl($url); 1385 } 1386 1183 1387 } -
cryptx/tags/4.0.0/classes/CryptXSettingsTabs.php
r3323475 r3339444 79 79 ); 80 80 81 // Enqueue WordPress color picker assets 81 82 wp_enqueue_style('wp-color-picker'); 82 83 // Enqueue JavaScript files 84 wp_enqueue_script( 85 'cryptx-admin-js', 86 CRYPTX_DIR_URL . 'js/cryptx-admin.min.js', 87 ['jquery', 'wp-color-picker'], 88 CRYPTX_VERSION, 89 true 90 ); 91 83 wp_enqueue_script('wp-color-picker'); 84 85 // Enqueue media uploader 92 86 wp_enqueue_media(); 93 87 } … … 133 127 $saveOptions = DataSanitizer::sanitize($_POST['cryptX_var']); 134 128 129 // Handle reset for any tab 135 130 if (isset($_POST['cryptX_var_reset'])) { 136 $saveOptions = $this->cryptX->getCryptXOptionsDefaults(); 131 $this->cryptX->getCryptXOptionsDefaults(); 132 $this->cryptX->getConfig()->reset(); 133 $this->displayResetMessage(); 134 return; 137 135 } 138 136 … … 145 143 } 146 144 } 147 148 145 149 146 /** … … 173 170 'cryptx_messages', 174 171 'cryptx_message', 175 __('Settings saved.'), 172 __('Settings saved.', 'cryptx'), 173 'updated' 174 ); 175 } 176 177 /** 178 * Display reset message 179 */ 180 private function displayResetMessage(): void 181 { 182 add_settings_error( 183 'cryptx_messages', 184 'cryptx_reset', 185 __('Settings have been reset to defaults.', 'cryptx'), 176 186 'updated' 177 187 ); … … 294 304 295 305 // Handle form submission if needed 296 if (isset($_POST['cryptX_save_general_settings']) ) {297 if (!empty($_POST['cryptX_var']) ) {298 $generalTab->saveSettings($_POST['cryptX_var'] );306 if (isset($_POST['cryptX_save_general_settings']) || isset($_POST['cryptX_var_reset'])) { 307 if (!empty($_POST['cryptX_var']) || isset($_POST['cryptX_var_reset'])) { 308 $generalTab->saveSettings($_POST['cryptX_var'] ?? []); 299 309 } 300 310 } … … 328 338 329 339 // Handle form submission if needed 330 if (isset($_POST['cryptX_save_presentation_settings']) ) {331 if (!empty($_POST['cryptX_var']) ) {332 $presentationTab->saveSettings($_POST['cryptX_var'] );340 if (isset($_POST['cryptX_save_presentation_settings']) || isset($_POST['cryptX_var_reset'])) { 341 if (!empty($_POST['cryptX_var']) || isset($_POST['cryptX_var_reset'])) { 342 $presentationTab->saveSettings($_POST['cryptX_var'] ?? []); 333 343 } 334 344 } … … 375 385 } 376 386 } 377 378 379 /**380 * Parse and render changelog content from readme.txt381 */382 private function renderChangelogContent(): void383 {384 $readmePath = CRYPTX_DIR_PATH . '/readme.txt';385 if (!file_exists($readmePath)) {386 return;387 }388 389 $fileContents = file_get_contents($readmePath);390 if ($fileContents === false) {391 return;392 }393 394 $changelogs = $this->parseChangelog($fileContents);395 foreach ($changelogs as $log) {396 echo wp_kses_post("<dl>" . implode("", $log) . "</dl>");397 }398 }399 400 /**401 * Parse changelog content from readme.txt402 *403 * @param string $content404 * @return array405 */406 private function parseChangelog(string $content): array407 {408 $content = str_replace(["\r\n", "\r"], "\n", $content);409 $content = trim($content);410 411 // Split into sections412 $sections = $this->parseSections($content);413 if (!isset($sections['changelog'])) {414 return [];415 }416 417 // Parse changelog entries418 return $this->parseChangelogEntries($sections['changelog']['content']);419 }420 421 /**422 * Parse sections from readme content423 *424 * @param string $content425 * @return array426 */427 private function parseSections(string $content): array428 {429 $_sections = preg_split('/^[\s]*==[\s]*(.+?)[\s]*==/m', $content, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);430 $sections = [];431 432 for ($i = 1; $i <= count($_sections); $i += 2) {433 $title = $_sections[$i - 1];434 $sections[str_replace(' ', '_', strtolower($title))] = [435 'title' => $title,436 'content' => $_sections[$i]437 ];438 }439 440 return $sections;441 }442 443 /**444 * Parse changelog entries445 *446 * @param string $content447 * @return array448 */449 private function parseChangelogEntries(string $content): array450 {451 $_changelogs = preg_split('/^[\s]*=[\s]*(.+?)[\s]*=/m', $content, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);452 $changelogs = [];453 454 for ($i = 1; $i <= count($_changelogs); $i += 2) {455 $version = $_changelogs[$i - 1];456 $content = ltrim($_changelogs[$i], "\n");457 $content = str_replace("* ", "<li>", $content);458 $content = str_replace("\n", " </li>\n", $content);459 460 $changelogs[] = [461 'version' => "<dt>" . esc_html($version) . "</dt>",462 'content' => "<dd><ul>" . wp_kses_post($content) . "</ul></dd>"463 ];464 }465 466 return $changelogs;467 }468 387 } -
cryptx/tags/4.0.0/cryptx.php
r3335910 r3339444 1 1 <?php 2 /* 3 * Plugin Name: CryptX4 * Plugin URI: http://weber-nrw.de/wordpress/cryptx/5 * Description: No more SPAM by spiders scanning you site for email addresses. With CryptX you can hide all your email addresses, with and without a mailto-link, by converting them using javascript or UNICODE.6 * Version: 3.5.22 /** 3 * Plugin Name: CryptX 4 * Plugin URI: https://wordpress.org/plugins/cryptx/ 5 * Description: CryptX encrypts email addresses in your posts, pages, comments, and text widgets to protect them from spam bots while keeping them readable for your visitors. 6 * Version: 4.0.0 7 7 * Requires at least: 6.7 8 * Author: Ralf Weber 9 * Author URI: http://weber-nrw.de/ 8 * Tested up to: 6.8 9 * Requires PHP: 8.1 10 * Author: Ralf Weber 11 * Author URI: https://weber-nrw.de/ 10 12 * License: GPL v2 or later 11 13 * License URI: https://www.gnu.org/licenses/gpl-2.0.html 12 * Text Domain: cryptx 13 */ 14 * Text Domain: cryptx 15 * Domain Path: /languages 16 * Network: false 17 * Update URI: https://wordpress.org/plugins/cryptx/ 18 * 19 * CryptX is free software: you can redistribute it and/or modify 20 * it under the terms of the GNU General Public License as published by 21 * the Free Software Foundation, either version 2 of the License, or 22 * any later version. 23 * 24 * CryptX is distributed in the hope that it will be useful, 25 * but WITHOUT ANY WARRANTY; without even the implied warranty of 26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 27 * GNU General Public License for more details. 28 * 29 * You should have received a copy of the GNU General Public License 30 * along with CryptX. If not, see https://www.gnu.org/licenses/gpl-2.0.html. 31 * 32 * @package CryptX 33 * @since 1.0.0 34 */ 14 35 15 //avoid direct calls to this file, because now WP core and framework has been used 16 if ( ! function_exists( 'add_action' ) ) { 17 header( 'Status: 403 Forbidden' ); 18 header( 'HTTP/1.1 403 Forbidden' ); 19 exit(); 36 // Prevent direct access 37 if (!defined('ABSPATH')) { 38 exit; 20 39 } 21 40 22 /** @const CryptX version */ 23 define( 'CRYPTX_VERSION', "3.5.2" ); 24 define( 'CRYPTX_BASENAME', plugin_basename( __FILE__ ) ); 25 define( 'CRYPTX_BASEFOLDER', plugin_basename( dirname( __FILE__ ) ) ); 26 define( 'CRYPTX_DIR_URL', rtrim( plugin_dir_url( __FILE__ ), "/" ) . "/" ); 27 define( 'CRYPTX_DIR_PATH', plugin_dir_path( __FILE__ ) ); 28 define( 'CRYPTX_FILENAME', str_replace( CRYPTX_BASEFOLDER . '/', '', plugin_basename( __FILE__ ) ) ); 41 // Plugin constants 42 define('CRYPTX_VERSION', '4.0.0'); 43 define('CRYPTX_PLUGIN_FILE', __FILE__); 44 define('CRYPTX_PLUGIN_BASENAME', plugin_basename(__FILE__)); 45 define('CRYPTX_BASENAME', plugin_basename(__FILE__)); // Add this missing constant 46 define('CRYPTX_DIR_PATH', plugin_dir_path(__FILE__)); 47 define('CRYPTX_DIR_URL', plugin_dir_url(__FILE__)); 48 define('CRYPTX_BASEFOLDER', dirname(CRYPTX_PLUGIN_BASENAME)); 29 49 30 spl_autoload_register(function ($class_name) { 31 // Handle classes in the CryptX namespace 32 if (strpos($class_name, 'CryptX\\') === 0) { 33 // Convert namespace separators to directory separators 34 $file_path = str_replace('\\', DIRECTORY_SEPARATOR, $class_name); 35 $file_path = str_replace('CryptX' . DIRECTORY_SEPARATOR, '', $file_path); 50 // Minimum requirements check 51 if (version_compare(PHP_VERSION, '8.1.0', '<')) { 52 add_action('admin_notices', function() { 53 echo '<div class="notice notice-error"><p>'; 54 printf( 55 /* translators: %1$s: Required PHP version, %2$s: Current PHP version */ 56 __('CryptX requires PHP version %1$s or higher. You are running version %2$s. Please update PHP.', 'cryptx'), 57 '8.1.0', 58 PHP_VERSION 59 ); 60 echo '</p></div>'; 61 }); 62 return; 63 } 36 64 37 // Construct the full file path 38 $file = CRYPTX_DIR_PATH . 'classes' . DIRECTORY_SEPARATOR . $file_path . '.php'; 65 // WordPress version check 66 global $wp_version; 67 if (version_compare($wp_version, '6.7', '<')) { 68 add_action('admin_notices', function() { 69 echo '<div class="notice notice-error"><p>'; 70 printf( 71 /* translators: %1$s: Required WordPress version, %2$s: Current WordPress version */ 72 __('CryptX requires WordPress version %1$s or higher. You are running version %2$s. Please update WordPress.', 'cryptx'), 73 '5.0', 74 $GLOBALS['wp_version'] 75 ); 76 echo '</p></div>'; 77 }); 78 return; 79 } 39 80 40 if (file_exists($file)) { 41 require_once $file; 42 } 81 // Autoloader for plugin classes 82 spl_autoload_register(function ($class) { 83 // Check if the class belongs to our namespace 84 if (strpos($class, 'CryptX\\') !== 0) { 85 return; 86 } 87 88 // Remove namespace prefix 89 $class = substr($class, 7); 90 91 // Convert namespace separators to directory separators 92 $class = str_replace('\\', DIRECTORY_SEPARATOR, $class); 93 94 // Build the full path 95 $file = CRYPTX_DIR_PATH . 'classes' . DIRECTORY_SEPARATOR . $class . '.php'; 96 97 // Include the file if it exists 98 if (file_exists($file)) { 99 require_once $file; 43 100 } 44 101 }); 45 102 103 // Initialize the plugin 104 add_action('plugins_loaded', function() { 105 // Check if all required classes can be loaded 106 $requiredClasses = [ 107 'CryptX\\CryptX', 108 'CryptX\\Config', 109 'CryptX\\CryptXSettingsTabs', 110 'CryptX\\SecureEncryption', 111 'CryptX\\Admin\\ChangelogSettingsTab', 112 'CryptX\\Admin\\GeneralSettingsTab', 113 'CryptX\\Admin\\PresentationSettingsTab', 114 'CryptX\\Util\\DataSanitizer', 115 ]; 46 116 47 //require_once( CRYPTX_DIR_PATH . 'classes/CryptX.php' ); 48 //require_once( CRYPTX_DIR_PATH . 'include/admin_option_page.php' ); 117 $missingClasses = []; 118 foreach ($requiredClasses as $class) { 119 if (!class_exists($class)) { 120 $missingClasses[] = $class; 121 } 122 } 49 123 50 $CryptX_instance = CryptX\CryptX::get_instance(); 51 $CryptX_instance->startCryptX(); 124 if (!empty($missingClasses)) { 125 add_action('admin_notices', function() use ($missingClasses) { 126 echo '<div class="notice notice-error"><p>'; 127 echo esc_html__('CryptX: Missing required classes: ', 'cryptx') . implode(', ', $missingClasses); 128 echo '</p></div>'; 129 }); 130 return; 131 } 52 132 53 /** 54 * Encrypts the given content using the CryptX WordPress plugin. 55 * 56 * @param string|null $content The content to encrypt. 57 * @param array|null $args The optional arguments for the encryption. Default is an empty array. 58 * 59 * @return string The encrypted content wrapped in the '[cryptx]' shortcode. 60 */ 61 function encryptx( ?string $content, ?array $args = [] ): string { 62 $CryptX_instance = Cryptx\CryptX::get_instance(); 63 return do_shortcode( '[cryptx'. $CryptX_instance->convertArrayToArgumentString( $args ).']' . $content . '[/cryptx]' ); 64 } 133 // Initialize the main plugin class 134 try { 135 $cryptx_instance = CryptX\CryptX::get_instance(); 136 $cryptx_instance->startCryptX(); 137 } catch (Exception $e) { 138 add_action('admin_notices', function() use ($e) { 139 echo '<div class="notice notice-error"><p>'; 140 echo esc_html__('CryptX initialization failed: ', 'cryptx') . esc_html($e->getMessage()); 141 echo '</p></div>'; 142 }); 143 } 144 }); 145 146 // Remove the strict activation requirements - let the plugin handle fallbacks 147 register_activation_hook(__FILE__, function() { 148 // Just flush rewrite rules 149 flush_rewrite_rules(); 150 }); 151 152 // Plugin deactivation hook 153 register_deactivation_hook(__FILE__, function() { 154 // Clean up any transients or cached data 155 global $wpdb; 156 $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_cryptx_%'"); 157 $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_timeout_cryptx_%'"); 158 }); 159 160 // Add plugin action links - updated to match the settings page slug 161 add_filter('plugin_action_links_' . plugin_basename(__FILE__), function($links) { 162 $settings_link = '<a href="' . admin_url('options-general.php?page=cryptx') . '">' . 163 esc_html__('Settings', 'cryptx') . '</a>'; 164 array_unshift($links, $settings_link); 165 return $links; 166 }); -
cryptx/tags/4.0.0/js/cryptx.js
r3323475 r3339444 1 const UPPER_LIMIT = 8364; 2 const DEFAULT_VALUE = 128; 3 4 /** 5 * Decrypts an encrypted string using a specific encryption algorithm. 6 * 7 * @param {string} encryptedString - The encrypted string to be decrypted. 8 * @returns {string} The decrypted string. 1 /** 2 * Secure CryptX Library - Fixed for backward compatibility 3 */ 4 5 // Configuration constants 6 const CONFIG = { 7 ALLOWED_PROTOCOLS: ['http:', 'https:', 'mailto:'], 8 MAX_URL_LENGTH: 2048, 9 ENCRYPTION_KEY_SIZE: 32, 10 IV_SIZE: 16 11 }; 12 13 /** 14 * Utility functions for secure operations 15 */ 16 class SecureUtils { 17 static getSecureRandomBytes(length) { 18 if (typeof crypto === 'undefined' || !crypto.getRandomValues) { 19 throw new Error('Secure random number generation not available'); 20 } 21 return crypto.getRandomValues(new Uint8Array(length)); 22 } 23 24 static arrayBufferToBase64(buffer) { 25 const bytes = new Uint8Array(buffer); 26 let binary = ''; 27 for (let i = 0; i < bytes.byteLength; i++) { 28 binary += String.fromCharCode(bytes[i]); 29 } 30 return btoa(binary); 31 } 32 33 static base64ToArrayBuffer(base64) { 34 const binary = atob(base64); 35 const buffer = new ArrayBuffer(binary.length); 36 const bytes = new Uint8Array(buffer); 37 for (let i = 0; i < binary.length; i++) { 38 bytes[i] = binary.charCodeAt(i); 39 } 40 return buffer; 41 } 42 43 static validateUrl(url) { 44 if (typeof url !== 'string' || url.length === 0) { 45 return null; 46 } 47 48 if (url.length > CONFIG.MAX_URL_LENGTH) { 49 console.error('URL exceeds maximum length'); 50 return null; 51 } 52 53 try { 54 const urlObj = new URL(url); 55 56 if (!CONFIG.ALLOWED_PROTOCOLS.includes(urlObj.protocol)) { 57 console.error('Protocol not allowed:', urlObj.protocol); 58 return null; 59 } 60 61 if (urlObj.protocol === 'mailto:') { 62 const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; 63 if (!emailRegex.test(urlObj.pathname)) { 64 console.error('Invalid email format in mailto URL'); 65 return null; 66 } 67 } 68 69 return url; 70 } catch (error) { 71 console.error('Invalid URL format:', error.message); 72 return null; 73 } 74 } 75 76 static escapeJavaScript(str) { 77 if (typeof str !== 'string') { 78 return ''; 79 } 80 return str.replace(/\\/g, '\\\\') 81 .replace(/'/g, "\\'") 82 .replace(/"/g, '\\"') 83 .replace(/\n/g, '\\n') 84 .replace(/\r/g, '\\r') 85 .replace(/\t/g, '\\t'); 86 } 87 } 88 89 /** 90 * Legacy encryption class - Fixed to match original PHP algorithm 91 */ 92 class LegacyEncryption { 93 /** 94 * Decrypts using the original CryptX algorithm (matches PHP version) 95 * @param {string} encryptedString 96 * @returns {string} 97 */ 98 static originalDecrypt(encryptedString) { 99 if (typeof encryptedString !== 'string' || encryptedString.length === 0) { 100 throw new Error('Invalid encrypted string'); 101 } 102 103 // Constants from original algorithm 104 const UPPER_LIMIT = 8364; 105 const DEFAULT_VALUE = 128; 106 107 let charCode = 0; 108 let decryptedString = "mailto:"; 109 let encryptionKey = 0; 110 111 try { 112 for (let i = 0; i < encryptedString.length; i += 2) { 113 if (i + 1 >= encryptedString.length) { 114 break; 115 } 116 117 // Get the salt (encryption key) from current position 118 encryptionKey = parseInt(encryptedString.charAt(i), 10); 119 120 // Handle invalid salt values 121 if (isNaN(encryptionKey)) { 122 encryptionKey = 0; 123 } 124 125 // Get the character code from next position 126 charCode = encryptedString.charCodeAt(i + 1); 127 128 // Apply the same logic as original 129 if (charCode >= UPPER_LIMIT) { 130 charCode = DEFAULT_VALUE; 131 } 132 133 // Decrypt by subtracting the salt 134 const decryptedCharCode = charCode - encryptionKey; 135 136 // Validate the result 137 if (decryptedCharCode < 0 || decryptedCharCode > 1114111) { 138 throw new Error('Invalid character code during decryption'); 139 } 140 141 decryptedString += String.fromCharCode(decryptedCharCode); 142 } 143 144 return decryptedString; 145 } catch (error) { 146 throw new Error('Original decryption failed: ' + error.message); 147 } 148 } 149 150 /** 151 * Encrypts using the original CryptX algorithm (matches PHP version) 152 * @param {string} inputString 153 * @returns {string} 154 */ 155 static originalEncrypt(inputString) { 156 if (typeof inputString !== 'string' || inputString.length === 0) { 157 throw new Error('Invalid input string'); 158 } 159 160 // Remove "mailto:" prefix if present for encryption 161 const cleanInput = inputString.replace(/^mailto:/, ''); 162 let crypt = ''; 163 164 // ASCII values blacklist (from PHP constant) 165 const ASCII_VALUES_BLACKLIST = ['32', '34', '39', '60', '62', '63', '92', '94', '96', '127']; 166 167 try { 168 for (let i = 0; i < cleanInput.length; i++) { 169 let salt, asciiValue; 170 let attempts = 0; 171 const maxAttempts = 20; // Prevent infinite loops 172 173 do { 174 if (attempts >= maxAttempts) { 175 // Fallback to a safe salt if we can't find a valid one 176 salt = 1; 177 asciiValue = cleanInput.charCodeAt(i) + salt; 178 break; 179 } 180 181 // Generate random number between 0 and 3 (matching PHP rand(0,3)) 182 const randomValues = SecureUtils.getSecureRandomBytes(1); 183 salt = randomValues[0] % 4; 184 185 // Get ASCII value and add salt 186 asciiValue = cleanInput.charCodeAt(i) + salt; 187 188 // Check if value exceeds limit (matching PHP logic) 189 if (asciiValue >= 8364) { 190 asciiValue = 128; 191 } 192 193 attempts++; 194 } while (ASCII_VALUES_BLACKLIST.includes(asciiValue.toString()) && attempts < maxAttempts); 195 196 // Append salt and character to result 197 crypt += salt.toString() + String.fromCharCode(asciiValue); 198 } 199 200 return crypt; 201 } catch (error) { 202 throw new Error('Original encryption failed: ' + error.message); 203 } 204 } 205 } 206 207 /** 208 * Modern encryption class using Web Crypto API - PHP Compatible 209 */ 210 class SecureEncryption { 211 static async deriveKey(password, salt) { 212 const encoder = new TextEncoder(); 213 const keyMaterial = await crypto.subtle.importKey( 214 'raw', 215 encoder.encode(password), 216 { name: 'PBKDF2' }, 217 false, 218 ['deriveKey'] 219 ); 220 221 return crypto.subtle.deriveKey( 222 { 223 name: 'PBKDF2', 224 salt: salt, 225 iterations: 100000, 226 hash: 'SHA-256' 227 }, 228 keyMaterial, 229 { name: 'AES-GCM', length: 256 }, 230 false, 231 ['encrypt', 'decrypt'] 232 ); 233 } 234 235 static async encrypt(plaintext, password) { 236 if (typeof plaintext !== 'string' || typeof password !== 'string') { 237 throw new Error('Both plaintext and password must be strings'); 238 } 239 240 const encoder = new TextEncoder(); 241 const salt = SecureUtils.getSecureRandomBytes(16); 242 const iv = SecureUtils.getSecureRandomBytes(CONFIG.IV_SIZE); 243 244 const key = await this.deriveKey(password, salt); 245 246 const encrypted = await crypto.subtle.encrypt( 247 { name: 'AES-GCM', iv: iv }, 248 key, 249 encoder.encode(plaintext) 250 ); 251 252 // Match PHP format: salt(16) + iv(16) + encrypted_data + tag(16) 253 const encryptedArray = new Uint8Array(encrypted); 254 const encryptedData = encryptedArray.slice(0, -16); // Remove tag from encrypted data 255 const tag = encryptedArray.slice(-16); // Get the tag 256 257 const combined = new Uint8Array(salt.length + iv.length + encryptedData.length + tag.length); 258 combined.set(salt, 0); 259 combined.set(iv, salt.length); 260 combined.set(encryptedData, salt.length + iv.length); 261 combined.set(tag, salt.length + iv.length + encryptedData.length); 262 263 return SecureUtils.arrayBufferToBase64(combined.buffer); 264 } 265 266 static async decrypt(encryptedData, password) { 267 if (typeof encryptedData !== 'string' || typeof password !== 'string') { 268 throw new Error('Both encryptedData and password must be strings'); 269 } 270 271 try { 272 const combined = SecureUtils.base64ToArrayBuffer(encryptedData); 273 const totalLength = combined.byteLength; 274 275 // PHP format: salt(16) + iv(16) + encrypted_data + tag(16) 276 const saltLength = 16; 277 const ivLength = 16; 278 const tagLength = 16; 279 const encryptedDataLength = totalLength - saltLength - ivLength - tagLength; 280 281 if (totalLength < saltLength + ivLength + tagLength) { 282 throw new Error('Encrypted data too short'); 283 } 284 285 const salt = combined.slice(0, saltLength); 286 const iv = combined.slice(saltLength, saltLength + ivLength); 287 const encryptedDataOnly = combined.slice(saltLength + ivLength, saltLength + ivLength + encryptedDataLength); 288 const tag = combined.slice(-tagLength); // Last 16 bytes 289 290 const key = await this.deriveKey(password, new Uint8Array(salt)); 291 292 // Reconstruct the encrypted data with tag for Web Crypto API 293 const encryptedWithTag = new Uint8Array(encryptedDataOnly.byteLength + tag.byteLength); 294 encryptedWithTag.set(new Uint8Array(encryptedDataOnly), 0); 295 encryptedWithTag.set(new Uint8Array(tag), encryptedDataOnly.byteLength); 296 297 const decrypted = await crypto.subtle.decrypt( 298 { name: 'AES-GCM', iv: new Uint8Array(iv) }, 299 key, 300 encryptedWithTag 301 ); 302 303 const decoder = new TextDecoder(); 304 return decoder.decode(decrypted); 305 } catch (error) { 306 throw new Error('Decryption failed: ' + error.message); 307 } 308 } 309 } 310 311 /** 312 * Main CryptX functions with backward compatibility 313 */ 314 315 /** 316 * Securely decrypts and validates a URL before navigation 317 * @param {string} encryptedUrl 318 * @param {string} password 319 */ 320 async function secureDecryptAndNavigate(encryptedUrl, password = 'default_key') { 321 if (typeof encryptedUrl !== 'string' || encryptedUrl.length === 0) { 322 console.error('Invalid encrypted URL provided'); 323 return; 324 } 325 326 try { 327 let decryptedUrl; 328 329 // Try modern decryption first, then fall back to original algorithm 330 try { 331 decryptedUrl = await SecureEncryption.decrypt(encryptedUrl, password); 332 } catch (modernError) { 333 console.warn('Modern decryption failed, trying original algorithm'); 334 decryptedUrl = LegacyEncryption.originalDecrypt(encryptedUrl); 335 } 336 337 const validatedUrl = SecureUtils.validateUrl(decryptedUrl); 338 if (!validatedUrl) { 339 console.error('Invalid or unsafe URL detected'); 340 return; 341 } 342 343 window.location.href = validatedUrl; 344 345 } catch (error) { 346 console.error('Error during URL decryption and navigation:', error.message); 347 } 348 } 349 350 /** 351 * Legacy function for backward compatibility - using original algorithm 352 * @param {string} encryptedString 353 * @returns {string|null} 9 354 */ 10 355 function DeCryptString(encryptedString) { 11 let charCode = 0; 12 let decryptedString = "mailto:"; 13 let encryptionKey = 0; 14 15 for (let i = 0; i < encryptedString.length; i += 2) { 16 encryptionKey = encryptedString.substr(i, 1); 17 charCode = encryptedString.charCodeAt(i + 1); 18 19 if (charCode >= UPPER_LIMIT) { 20 charCode = DEFAULT_VALUE; 21 } 22 23 decryptedString += String.fromCharCode(charCode - encryptionKey); 24 } 25 26 return decryptedString; 27 } 28 29 /** 30 * Redirects the current page to the decrypted URL. 31 * 32 * @param {string} encryptedUrl - The encrypted URL to be decrypted and redirected to. 33 * @return {void} 34 */ 35 function DeCryptX( encryptedUrl ) 36 { 37 location.href=DeCryptString( encryptedUrl ); 38 } 39 40 /** 41 * Generates a hashed string from the input string using a custom algorithm. 42 * The method applies a randomized salt to the ASCII values of the characters 43 * in the input string while avoiding certain blacklisted ASCII values. 44 * 45 * @param {string} inputString - The input string to be hashed. 46 * @return {string} The generated hash string. 356 try { 357 return LegacyEncryption.originalDecrypt(encryptedString); 358 } catch (error) { 359 console.error('Legacy decryption failed:', error.message); 360 return null; 361 } 362 } 363 364 /** 365 * Legacy function for backward compatibility - secured 366 * @param {string} encryptedUrl 367 */ 368 function DeCryptX(encryptedUrl) { 369 const decryptedUrl = DeCryptString(encryptedUrl); 370 if (!decryptedUrl) { 371 console.error('Failed to decrypt URL'); 372 return; 373 } 374 375 const validatedUrl = SecureUtils.validateUrl(decryptedUrl); 376 if (!validatedUrl) { 377 console.error('Invalid or unsafe URL detected'); 378 return; 379 } 380 381 window.location.href = validatedUrl; 382 } 383 384 /** 385 * Generates encrypted email link with proper security 386 * @param {string} emailAddress 387 * @param {string} password 388 * @returns {Promise<string>} 389 */ 390 async function generateSecureEmailLink(emailAddress, password = 'default_key') { 391 if (typeof emailAddress !== 'string' || emailAddress.length === 0) { 392 throw new Error('Valid email address required'); 393 } 394 395 const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; 396 if (!emailRegex.test(emailAddress)) { 397 throw new Error('Invalid email format'); 398 } 399 400 const mailtoUrl = `mailto:${emailAddress}`; 401 const encryptedData = await SecureEncryption.encrypt(mailtoUrl, password); 402 const escapedData = SecureUtils.escapeJavaScript(encryptedData); 403 404 return `javascript:secureDecryptAndNavigate('${escapedData}', '${SecureUtils.escapeJavaScript(password)}')`; 405 } 406 407 /** 408 * Legacy function for backward compatibility - using original algorithm 409 * @param {string} emailAddress 410 * @returns {string} 411 */ 412 function generateDeCryptXHandler(emailAddress) { 413 if (typeof emailAddress !== 'string' || emailAddress.length === 0) { 414 console.error('Valid email address required'); 415 return 'javascript:void(0)'; 416 } 417 418 try { 419 const encrypted = LegacyEncryption.originalEncrypt(emailAddress); 420 const escaped = SecureUtils.escapeJavaScript(encrypted); 421 return `javascript:DeCryptX('${escaped}')`; 422 } catch (error) { 423 console.error('Error generating handler:', error.message); 424 return 'javascript:void(0)'; 425 } 426 } 427 428 /** 429 * Legacy function - matches original PHP generateHashFromString 430 * @param {string} inputString 431 * @returns {string} 47 432 */ 48 433 function generateHashFromString(inputString) { 49 // Replace & with itself (this line seems redundant in the original PHP code) 50 inputString = inputString.replace("&", "&"); 51 let crypt = ''; 52 53 // ASCII values blacklist (taken from the PHP constant) 54 const ASCII_VALUES_BLACKLIST = ['32', '34', '39', '60', '62', '63', '92', '94', '96', '127']; 55 56 for (let i = 0; i < inputString.length; i++) { 57 let salt, asciiValue; 58 do { 59 // Generate random number between 0 and 3 60 salt = Math.floor(Math.random() * 4); 61 // Get ASCII value and add salt 62 asciiValue = inputString.charCodeAt(i) + salt; 63 64 // Check if value exceeds limit 65 if (8364 <= asciiValue) { 66 asciiValue = 128; 67 } 68 } while (ASCII_VALUES_BLACKLIST.includes(asciiValue.toString())); 69 70 // Append salt and character to result 71 crypt += salt + String.fromCharCode(asciiValue); 72 } 73 74 return crypt; 75 } 76 77 /** 78 * Generates a DeCryptX handler URL with a hashed email address. 79 * 80 * @param {string} emailAddress - The email address to be hashed and included in the handler URL. 81 * @return {string} A string representing the JavaScript DeCryptX handler with the hashed email address. 82 */ 83 function generateDeCryptXHandler(emailAddress) { 84 return `javascript:DeCryptX('${generateHashFromString(emailAddress)}')`; 85 } 434 try { 435 return LegacyEncryption.originalEncrypt(inputString); 436 } catch (error) { 437 console.error('Error generating hash:', error.message); 438 return ''; 439 } 440 } 441 442 // Export functions for module usage 443 if (typeof module !== 'undefined' && module.exports) { 444 module.exports = { 445 secureDecryptAndNavigate, 446 generateSecureEmailLink, 447 DeCryptX, 448 DeCryptString, 449 generateDeCryptXHandler, 450 generateHashFromString, 451 SecureEncryption, 452 LegacyEncryption, 453 SecureUtils 454 }; 455 } -
cryptx/tags/4.0.0/js/cryptx.min.js
r3323475 r3339444 1 const UPPER_LIMIT=8364,DEFAULT_VALUE=128;function DeCryptString(t){let r=0,e="mailto:",n=0;for(let o=0;o<t.length;o+=2)n=t.substr(o,1),r=t.charCodeAt(o+1),r>=8364&&(r=128),e+=String.fromCharCode(r-n);return e}function DeCryptX(t){location.href=DeCryptString(t)}function generateHashFromString(t){t=t.replace("&","&");let r="";const e=["32","34","39","60","62","63","92","94","96","127"];for(let n=0;n<t.length;n++){let o,a;do{o=Math.floor(4*Math.random()),a=t.charCodeAt(n)+o,8364<=a&&(a=128)}while(e.includes(a.toString()));r+=o+String.fromCharCode(a)}return r}function generateDeCryptXHandler(t){return`javascript:DeCryptX('${generateHashFromString(t)}')`}1 const CONFIG={ALLOWED_PROTOCOLS:["http:","https:","mailto:"],MAX_URL_LENGTH:2048,ENCRYPTION_KEY_SIZE:32,IV_SIZE:16};class SecureUtils{static getSecureRandomBytes(e){if("undefined"==typeof crypto||!crypto.getRandomValues)throw new Error("Secure random number generation not available");return crypto.getRandomValues(new Uint8Array(e))}static arrayBufferToBase64(e){const r=new Uint8Array(e);let t="";for(let e=0;e<r.byteLength;e++)t+=String.fromCharCode(r[e]);return btoa(t)}static base64ToArrayBuffer(e){const r=atob(e),t=new ArrayBuffer(r.length),n=new Uint8Array(t);for(let e=0;e<r.length;e++)n[e]=r.charCodeAt(e);return t}static validateUrl(e){if("string"!=typeof e||0===e.length)return null;if(e.length>CONFIG.MAX_URL_LENGTH)return console.error("URL exceeds maximum length"),null;try{const r=new URL(e);if(!CONFIG.ALLOWED_PROTOCOLS.includes(r.protocol))return console.error("Protocol not allowed:",r.protocol),null;if("mailto:"===r.protocol){if(!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(r.pathname))return console.error("Invalid email format in mailto URL"),null}return e}catch(e){return console.error("Invalid URL format:",e.message),null}}static escapeJavaScript(e){return"string"!=typeof e?"":e.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(/"/g,'\\"').replace(/\n/g,"\\n").replace(/\r/g,"\\r").replace(/\t/g,"\\t")}}class LegacyEncryption{static originalDecrypt(e){if("string"!=typeof e||0===e.length)throw new Error("Invalid encrypted string");let r=0,t="mailto:",n=0;try{for(let a=0;a<e.length&&!(a+1>=e.length);a+=2){n=parseInt(e.charAt(a),10),isNaN(n)&&(n=0),r=e.charCodeAt(a+1),r>=8364&&(r=128);const o=r-n;if(o<0||o>1114111)throw new Error("Invalid character code during decryption");t+=String.fromCharCode(o)}return t}catch(e){throw new Error("Original decryption failed: "+e.message)}}static originalEncrypt(e){if("string"!=typeof e||0===e.length)throw new Error("Invalid input string");const r=e.replace(/^mailto:/,"");let t="";const n=["32","34","39","60","62","63","92","94","96","127"];try{for(let e=0;e<r.length;e++){let a,o,i=0;const c=20;do{if(i>=c){a=1,o=r.charCodeAt(e)+a;break}a=SecureUtils.getSecureRandomBytes(1)[0]%4,o=r.charCodeAt(e)+a,o>=8364&&(o=128),i++}while(n.includes(o.toString())&&i<c);t+=a.toString()+String.fromCharCode(o)}return t}catch(e){throw new Error("Original encryption failed: "+e.message)}}}class SecureEncryption{static async deriveKey(e,r){const t=new TextEncoder,n=await crypto.subtle.importKey("raw",t.encode(e),{name:"PBKDF2"},!1,["deriveKey"]);return crypto.subtle.deriveKey({name:"PBKDF2",salt:r,iterations:1e5,hash:"SHA-256"},n,{name:"AES-GCM",length:256},!1,["encrypt","decrypt"])}static async encrypt(e,r){if("string"!=typeof e||"string"!=typeof r)throw new Error("Both plaintext and password must be strings");const t=new TextEncoder,n=SecureUtils.getSecureRandomBytes(16),a=SecureUtils.getSecureRandomBytes(CONFIG.IV_SIZE),o=await this.deriveKey(r,n),i=await crypto.subtle.encrypt({name:"AES-GCM",iv:a},o,t.encode(e)),c=new Uint8Array(i),s=c.slice(0,-16),l=c.slice(-16),y=new Uint8Array(n.length+a.length+s.length+l.length);return y.set(n,0),y.set(a,n.length),y.set(s,n.length+a.length),y.set(l,n.length+a.length+s.length),SecureUtils.arrayBufferToBase64(y.buffer)}static async decrypt(e,r){if("string"!=typeof e||"string"!=typeof r)throw new Error("Both encryptedData and password must be strings");try{const t=SecureUtils.base64ToArrayBuffer(e),n=t.byteLength,a=16,o=16,i=16,c=n-a-o-i;if(n<a+o+i)throw new Error("Encrypted data too short");const s=t.slice(0,a),l=t.slice(a,a+o),y=t.slice(a+o,a+o+c),g=t.slice(-i),d=await this.deriveKey(r,new Uint8Array(s)),p=new Uint8Array(y.byteLength+g.byteLength);p.set(new Uint8Array(y),0),p.set(new Uint8Array(g),y.byteLength);const u=await crypto.subtle.decrypt({name:"AES-GCM",iv:new Uint8Array(l)},d,p);return(new TextDecoder).decode(u)}catch(e){throw new Error("Decryption failed: "+e.message)}}}async function secureDecryptAndNavigate(e,r="default_key"){if("string"==typeof e&&0!==e.length)try{let t;try{t=await SecureEncryption.decrypt(e,r)}catch(r){console.warn("Modern decryption failed, trying original algorithm"),t=LegacyEncryption.originalDecrypt(e)}const n=SecureUtils.validateUrl(t);if(!n)return void console.error("Invalid or unsafe URL detected");window.location.href=n}catch(e){console.error("Error during URL decryption and navigation:",e.message)}else console.error("Invalid encrypted URL provided")}function DeCryptString(e){try{return LegacyEncryption.originalDecrypt(e)}catch(e){return console.error("Legacy decryption failed:",e.message),null}}function DeCryptX(e){const r=DeCryptString(e);if(!r)return void console.error("Failed to decrypt URL");const t=SecureUtils.validateUrl(r);t?window.location.href=t:console.error("Invalid or unsafe URL detected")}async function generateSecureEmailLink(e,r="default_key"){if("string"!=typeof e||0===e.length)throw new Error("Valid email address required");if(!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e))throw new Error("Invalid email format");const t=`mailto:${e}`,n=await SecureEncryption.encrypt(t,r);return`javascript:secureDecryptAndNavigate('${SecureUtils.escapeJavaScript(n)}', '${SecureUtils.escapeJavaScript(r)}')`}function generateDeCryptXHandler(e){if("string"!=typeof e||0===e.length)return console.error("Valid email address required"),"javascript:void(0)";try{const r=LegacyEncryption.originalEncrypt(e);return`javascript:DeCryptX('${SecureUtils.escapeJavaScript(r)}')`}catch(e){return console.error("Error generating handler:",e.message),"javascript:void(0)"}}function generateHashFromString(e){try{return LegacyEncryption.originalEncrypt(e)}catch(e){return console.error("Error generating hash:",e.message),""}}"undefined"!=typeof module&&module.exports&&(module.exports={secureDecryptAndNavigate:secureDecryptAndNavigate,generateSecureEmailLink:generateSecureEmailLink,DeCryptX:DeCryptX,DeCryptString:DeCryptString,generateDeCryptXHandler:generateDeCryptXHandler,generateHashFromString:generateHashFromString,SecureEncryption:SecureEncryption,LegacyEncryption:LegacyEncryption,SecureUtils:SecureUtils}); -
cryptx/tags/4.0.0/readme.txt
r3335910 r3339444 2 2 Contributors: d3395 3 3 Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=4026696 4 Tags: antispam, email, mail, addresses 5 Requires at least: 6. 04 Tags: antispam, email, mail, addresses, spam protection, email encryption, privacy 5 Requires at least: 6.7 6 6 Tested up to: 6.8 7 Stable tag: 3.5.28 Requires PHP: 8. 07 Stable tag: 4.0.0 8 Requires PHP: 8.1 9 9 License: GPLv2 or later 10 10 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 16 16 No more SPAM by spiders scanning you site for email addresses. With CryptX you can hide all your email addresses, with and without a mailto-link, by converting them using javascript or UNICODE. 17 17 18 CryptX protects your email addresses from spam bots while keeping them readable and functional for your visitors. The plugin automatically detects email addresses in your content and encrypts them using various methods including JavaScript encryption, Unicode conversion, and image replacement. 19 20 **Key Features:** 21 22 * **Automatic Email Detection** - Finds and encrypts email addresses in posts, pages, comments, and widgets 23 * **Multiple Encryption Methods** - JavaScript, Unicode, image replacement, and custom text options 24 * **Widget Support** - Works with text widgets and other widget content 25 * **RSS Feed Control** - Option to disable encryption in RSS feeds 26 * **Whitelist Support** - Exclude specific domains from encryption 27 * **Per-Post Control** - Enable/disable encryption on individual posts and pages 28 * **Shortcode Support** - Use `[cryptx][email protected][/cryptx]` for manual encryption 29 * **Template Functions** - Developer-friendly functions for theme integration 30 18 31 [Plugin Homepage](http://weber-nrw.de/wordpress/cryptx/ "Plugin Homepage") 19 32 20 33 == Screenshots == 21 34 22 1. Plugin settings 23 2. Template functions 35 1. Plugin settings - General configuration options 36 2. Email encryption methods and display options 37 3. Advanced settings and whitelist configuration 38 39 == Installation == 40 41 1. Upload the CryptX folder to the `/wp-content/plugins/` directory 42 2. Activate the plugin through the 'Plugins' menu in WordPress 43 3. Configure the plugin settings under Settings > CryptX 44 4. Your email addresses will now be automatically protected! 45 46 == Frequently Asked Questions == 47 48 = How does CryptX protect my email addresses? = 49 50 CryptX uses various methods to hide email addresses from spam bots while keeping them functional for visitors. Methods include JavaScript encryption, Unicode conversion, and replacing emails with images or custom text. 51 52 = Will this affect my website's performance? = 53 54 CryptX is designed to be lightweight and only loads JavaScript when needed. The performance impact is minimal. 55 56 = Can I exclude certain email addresses from encryption? = 57 58 Yes, you can use the whitelist feature to exclude specific domains or email addresses from encryption. 59 60 = Does it work with contact forms? = 61 62 CryptX primarily works with email addresses displayed in content. It doesn't interfere with contact forms or other form functionality. 63 64 = Can I disable encryption on specific posts? = 65 66 Yes, you can enable the meta box feature to control encryption on individual posts and pages. 67 68 For more information, visit the [Plugin Homepage](http://weber-nrw.de/wordpress/cryptx/ "Plugin Homepage") 24 69 25 70 == Changelog == 71 72 = 4.0.0 = 73 * **Major Update**: Complete code refactoring and modernization 74 * Improved PHP 8.1+ compatibility and performance 75 * Enhanced plugin architecture with better separation of concerns 76 * Improved widget filtering and universal widget support 77 * Better error handling and debugging capabilities 78 * Updated minimum requirements: WordPress 6.7+ and PHP 8.1+ 79 * Improved security and code quality 80 * Enhanced admin interface and settings organization 81 * Better handling of complex HTML structures and multiline content 26 82 = 3.5.2 = 27 83 * Fixed a bug where activating CryptX for the first time caused a PHP Fatal error … … 165 221 * Add Option to disable CryptX on single post/page 166 222 167 == Installation ==168 169 1. Upload "cryptX folder" to the `/wp-content/plugins/` directory170 2. Activate the plugin through the 'Plugins' menu in WordPress171 3. Edit the Options under the Options Page.172 4. Look at your Blog and be happy.173 174 223 == Upgrade Notice == 175 Nothing special to do. 176 177 == Frequently Asked Questions == 178 179 [Plugin Homepage](http://weber-nrw.de/wordpress/cryptx/ "Plugin Homepage") 224 225 = 4.0.0 = 226 Major update with improved PHP 8.1+ compatibility, enhanced performance, and modernized codebase. Please test on a staging site first. Minimum requirements: WordPress 6.7+ and PHP 8.1+. 227 228 = 3.5.2 = 229 Bug fixes for activation errors and Elementor compatibility issues. 230 -
cryptx/tags/4.0.0/templates/admin/tabs/general.php
r3323475 r3339444 1 <?php 2 /** 3 * CryptX General Settings Tab Template 4 * 5 * @var array $options 6 * @var array $applyTo 7 * @var array $decryptionType 8 * @var array $javascriptLocation 9 * @var string $excludedIds 10 * @var bool $metaBox 11 * @var bool $autolink 12 * @var string $whiteList 13 */ 14 15 defined('ABSPATH') || exit; 16 ?> 17 18 <h4><?php esc_html_e("General", 'cryptx'); ?></h4> 19 <table class="form-table" role="presentation"> 20 <!-- Apply CryptX Section --> 21 <tr> 22 <th scope="row"><?php _e("Apply CryptX to...", 'cryptx'); ?></th> 23 <td> 24 <?php foreach ($applyTo as $key => $setting): ?> 25 <label> 26 <input type="checkbox" 27 name="cryptX_var[<?php echo esc_attr($key); ?>]" 28 value="1" 29 <?php checked($options[$key] ?? 0, 1); ?> /> 30 <?php echo esc_html($setting['label']); ?> 31 <?php if (isset($setting['description'])): ?> 32 <small><?php echo esc_html($setting['description']); ?></small> 33 <?php endif; ?> 34 </label><br/> 35 <?php endforeach; ?> 36 </td> 37 </tr> 38 39 <tr class="spacer"> 40 <td colspan="2"><hr></td> 41 </tr> 42 43 <!-- RSS Feed Section --> 44 <tr> 45 <th scope="row" colspan="2"> 46 <label> 47 <input name="cryptX_var[disable_rss]" 48 type="checkbox" 49 value="1" 50 <?php checked($options['disable_rss'] ?? true, 1); ?> /> 51 <?php esc_html_e("Disable CryptX in RSS feeds", 'cryptx'); ?> 52 </label> 53 <p class="description"> 54 <?php esc_html_e("When enabled, email addresses in RSS feeds will not be encrypted.", 'cryptx'); ?> 55 </p> 56 </th> 57 </tr> 58 59 <tr class="spacer"> 60 <td colspan="2"><hr></td> 61 </tr> 62 63 <!-- Excluded IDs Section --> 64 <tr> 65 <th scope="row"><?php esc_html_e("Excluded ID's...", 'cryptx'); ?></th> 66 <td> 67 <input name="cryptX_var[excludedIDs]" 68 type="text" 69 value="<?php echo esc_attr($excludedIds); ?>" 70 class="regular-text" /> 71 <p class="description"> 72 <?php esc_html_e("Enter all Page/Post ID's to exclude from CryptX as comma separated list.", 'cryptx'); ?> 73 </p> 74 <label> 75 <input name="cryptX_var[metaBox]" 76 type="checkbox" 77 value="1" 78 <?php checked($metaBox, 1); ?> /> 79 <?php esc_html_e("Enable the CryptX Widget on editing a post or page.", 'cryptx'); ?> 80 </label> 81 </td> 82 </tr> 83 84 <tr class="spacer"> 85 <td colspan="2"><hr></td> 86 </tr> 87 88 <!-- Decryption Type Section --> 89 <tr> 90 <th scope="row"><?php esc_html_e("Type of decryption", 'cryptx'); ?></th> 91 <td> 92 <?php foreach ($decryptionType as $type): ?> 93 <label> 94 <input name="cryptX_var[java]" 95 type="radio" 96 value="<?php echo esc_attr($type['value']); ?>" 97 <?php checked($options['java'], $type['value']); ?> /> 98 <?php echo esc_html($type['label']); ?> 99 </label><br /> 100 <?php endforeach; ?> 101 </td> 102 </tr> 103 104 <tr class="spacer"> 105 <td colspan="2"><hr></td> 106 </tr> 107 108 <!-- JavaScript Location Section --> 109 <tr> 110 <th scope="row"><?php esc_html_e("Where to load the needed javascript...", 'cryptx'); ?></th> 111 <td> 112 <?php foreach ($javascriptLocation as $location): ?> 113 <label> 114 <input name="cryptX_var[load_java]" 115 type="radio" 116 value="<?php echo esc_attr($location['value']); ?>" 117 <?php //checked(get_option('load_java'), $location['value']); ?> 118 <?php checked($options['load_java'], $location['value']); ?> /> 119 <?php echo wp_kses($location['label'], ['b' => []]); ?> 120 </label><br /> 121 <?php endforeach; ?> 122 </td> 123 </tr> 124 125 <tr class="spacer"> 126 <td colspan="2"><hr></td> 127 </tr> 128 129 <!-- Autolink Section --> 130 <tr> 131 <th scope="row" colspan="2"> 132 <label> 133 <input name="cryptX_var[autolink]" 134 type="checkbox" 135 value="1" 136 <?php checked($autolink, 1); ?> /> 137 <?php esc_html_e("Add mailto to all unlinked email addresses", 'cryptx'); ?> 138 </label> 139 </th> 140 </tr> 141 142 <tr class="spacer"> 143 <td colspan="2"><hr></td> 144 </tr> 145 146 <!-- Whitelist Section --> 147 <tr> 148 <th scope="row"><?php esc_html_e("Whitelist of extensions", 'cryptx'); ?></th> 149 <td> 150 <input name="cryptX_var[whiteList]" 151 type="text" 152 value="<?php echo esc_attr($whiteList); ?>" 153 class="regular-text" /> 154 <p class="description"> 155 <?php echo wp_kses( 156 __("<strong>This is a workaround for the 'retina issue'.</strong><br/>You can provide a comma separated list of extensions like 'jpeg,jpg,png,gif' which will be ignored by CryptX.", 'cryptx'), 157 ['strong' => [], 'br' => []] 158 ); ?> 159 </p> 160 </td> 161 </tr> 162 163 <tr class="spacer"> 164 <td colspan="2"><hr></td> 165 </tr> 166 167 <!-- Reset Options Section --> 168 <tr> 169 <th scope="row" colspan="2" class="warning"> 170 <label> 171 <input name="cryptX_var_reset" 172 type="checkbox" 173 value="1" /> 174 <?php esc_html_e("Reset CryptX options to defaults. Use it carefully and at your own risk. All changes will be deleted!", 'cryptx'); ?> 175 </label> 176 </th> 177 </tr> 178 </table> 179 180 <input type="hidden" name="cryptX_save_general_settings" value="true"> 181 <?php submit_button(__('Save General Settings', 'cryptx'), 'primary', 'cryptX_save_general_settings'); ?> 182 1 <?php if (!defined('ABSPATH')) exit; ?> 2 3 <div class="cryptx-tab-content cryptx-general-settings"> 4 <table class="form-table"> 5 6 <!-- Security Settings Section --> 7 <tr> 8 <th colspan="2"> 9 <h3 style="margin: 20px 0 10px 0; padding: 10px 0; border-bottom: 1px solid #ddd;"> 10 <?php _e('Encryption Mode', 'cryptx'); ?> 11 </h3> 12 </th> 13 </tr> 14 <tr> 15 <th scope="row"><?php echo $securitySettings['encryption_mode']['label']; ?></th> 16 <td> 17 <select name="cryptX_var[encryption_mode]" id="encryption_mode"> 18 <?php foreach ($securitySettings['encryption_mode']['options'] as $value => $label): ?> 19 <option value="<?php echo esc_attr($value); ?>" <?php selected($securitySettings['encryption_mode']['value'], $value); ?>> 20 <?php echo esc_html($label); ?> 21 </option> 22 <?php endforeach; ?> 23 </select> 24 <p class="description"> 25 <?php echo $securitySettings['encryption_mode']['description']; ?> 26 <br/> 27 <strong><?php _e('Legacy:', 'cryptx'); ?></strong> <?php _e('Uses the original CryptX encryption algorithm for backward compatibility.', 'cryptx'); ?><br/> 28 <strong><?php _e('Secure:', 'cryptx'); ?></strong> <?php _e('Uses modern AES-256-GCM encryption with PBKDF2 key derivation for enhanced security.', 'cryptx'); ?> 29 </p> 30 31 <!-- Hidden checkbox that gets set based on the dropdown --> 32 <input type="hidden" name="cryptX_var[use_secure_encryption]" id="use_secure_encryption" 33 value="<?php echo $securitySettings['use_secure_encryption']['value']; ?>" /> 34 </td> 35 </tr> 36 37 <!-- Separator --> 38 <tr> 39 <th colspan="2"> 40 <h3 style="margin: 20px 0 10px 0; padding: 10px 0; border-bottom: 1px solid #ddd;"> 41 <?php _e('General Settings', 'cryptx'); ?> 42 </h3> 43 </th> 44 </tr> 45 46 <!-- Apply CryptX to Section --> 47 <tr> 48 <th scope="row"><?php _e('Apply CryptX to', 'cryptx'); ?></th> 49 <td> 50 <fieldset> 51 <?php foreach ($applyTo as $key => $setting): ?> 52 <label for="<?php echo esc_attr($key); ?>"> 53 <input type="hidden" name="cryptX_var[<?php echo esc_attr($key); ?>]" value="0" /> 54 <input type="checkbox" 55 id="<?php echo esc_attr($key); ?>" 56 name="cryptX_var[<?php echo esc_attr($key); ?>]" 57 value="1" 58 <?php checked($options[$key] ?? 0, 1); ?> /> 59 <?php echo esc_html($setting['label']); ?> 60 <?php if (isset($setting['description'])): ?> 61 <span class="description"><?php echo esc_html($setting['description']); ?></span> 62 <?php endif; ?> 63 </label><br /> 64 <?php endforeach; ?> 65 </fieldset> 66 </td> 67 </tr> 68 69 <!-- Decryption Type --> 70 <tr> 71 <th scope="row"><?php _e('Decryption type', 'cryptx'); ?></th> 72 <td> 73 <fieldset> 74 <?php foreach ($decryptionType as $key => $setting): ?> 75 <label for="java_<?php echo esc_attr($key); ?>"> 76 <input type="radio" 77 id="java_<?php echo esc_attr($key); ?>" 78 name="cryptX_var[java]" 79 value="<?php echo esc_attr($setting['value']); ?>" 80 <?php checked($options['java'] ?? 1, $setting['value']); ?> /> 81 <?php echo $setting['label']; ?> 82 </label><br /> 83 <?php endforeach; ?> 84 </fieldset> 85 </td> 86 </tr> 87 88 <!-- JavaScript Location --> 89 <tr> 90 <th scope="row"><?php _e('Javascript location', 'cryptx'); ?></th> 91 <td> 92 <fieldset> 93 <?php foreach ($javascriptLocation as $key => $setting): ?> 94 <label for="load_java_<?php echo esc_attr($key); ?>"> 95 <input type="radio" 96 id="load_java_<?php echo esc_attr($key); ?>" 97 name="cryptX_var[load_java]" 98 value="<?php echo esc_attr($setting['value']); ?>" 99 <?php checked($options['load_java'] ?? 1, $setting['value']); ?> /> 100 <?php echo $setting['label']; ?> 101 </label><br /> 102 <?php endforeach; ?> 103 </fieldset> 104 </td> 105 </tr> 106 107 <!-- Additional Options --> 108 <tr> 109 <th scope="row"><?php _e('Additional options', 'cryptx'); ?></th> 110 <td> 111 <fieldset> 112 <label for="autolink"> 113 <input type="hidden" name="cryptX_var[autolink]" value="0" /> 114 <input type="checkbox" 115 id="autolink" 116 name="cryptX_var[autolink]" 117 value="1" 118 <?php checked($options['autolink'] ?? 0, 1); ?> /> 119 <?php _e('Automatically add a link to non-linked email addresses.', 'cryptx'); ?> 120 </label><br /> 121 122 <label for="metaBox"> 123 <input type="hidden" name="cryptX_var[metaBox]" value="0" /> 124 <input type="checkbox" 125 id="metaBox" 126 name="cryptX_var[metaBox]" 127 value="1" 128 <?php checked($options['metaBox'] ?? 0, 1); ?> /> 129 <?php _e('Show the "Disable CryptX" checkbox in the post editor.', 'cryptx'); ?> 130 </label><br /> 131 132 <label for="disable_rss"> 133 <input type="hidden" name="cryptX_var[disable_rss]" value="0" /> 134 <input type="checkbox" 135 id="disable_rss" 136 name="cryptX_var[disable_rss]" 137 value="1" 138 <?php checked($options['disable_rss'] ?? 1, 1); ?> /> 139 <?php _e('Disable CryptX in RSS feeds.', 'cryptx'); ?> 140 </label> 141 </fieldset> 142 </td> 143 </tr> 144 145 <!-- Excluded Post IDs --> 146 <tr> 147 <th scope="row"> 148 <label for="excludedIDs"><?php _e('Excluded posts/pages IDs', 'cryptx'); ?></label> 149 </th> 150 <td> 151 <input type="text" 152 id="excludedIDs" 153 name="cryptX_var[excludedIDs]" 154 value="<?php echo esc_attr($options['excludedIDs'] ?? ''); ?>" 155 class="regular-text" /> 156 <p class="description"> 157 <?php _e('Comma-separated list of post/page IDs where CryptX should be disabled.', 'cryptx'); ?> 158 </p> 159 </td> 160 </tr> 161 162 <!-- Whitelist --> 163 <tr> 164 <th scope="row"> 165 <label for="whiteList"><?php _e('Whitelist', 'cryptx'); ?></label> 166 </th> 167 <td> 168 <input type="text" 169 id="whiteList" 170 name="cryptX_var[whiteList]" 171 value="<?php echo esc_attr($options['whiteList'] ?? 'jpeg,jpg,png,gif'); ?>" 172 class="regular-text" /> 173 <p class="description"> 174 <?php _e('Comma-separated list of file extensions that should not be encrypted when found in email addresses.', 'cryptx'); ?> 175 </p> 176 </td> 177 </tr> 178 </table> 179 180 <!-- Submit Button --> 181 <p class="submit"> 182 <input type="submit" 183 name="cryptX_save_general_settings" 184 class="button-primary" 185 value="<?php _e('Save Changes', 'cryptx'); ?>" /> 186 <input type="submit" 187 name="cryptX_var_reset" 188 class="button-secondary" 189 value="<?php _e('Reset to Defaults', 'cryptx'); ?>" 190 onclick="return confirm('<?php _e('Are you sure you want to reset all settings to defaults?', 'cryptx'); ?>');" /> 191 </p> 192 </div> 193 194 <script> 195 // Auto-sync the dropdown with the hidden checkbox 196 document.addEventListener('DOMContentLoaded', function() { 197 const encryptionModeSelect = document.getElementById('encryption_mode'); 198 const useSecureEncryption = document.getElementById('use_secure_encryption'); 199 200 if (encryptionModeSelect && useSecureEncryption) { 201 // Set initial value 202 useSecureEncryption.value = (encryptionModeSelect.value === 'secure') ? '1' : '0'; 203 204 // Update on change 205 encryptionModeSelect.addEventListener('change', function() { 206 useSecureEncryption.value = (this.value === 'secure') ? '1' : '0'; 207 }); 208 } 209 }); 210 </script> -
cryptx/tags/4.0.0/templates/admin/tabs/howto.php
r3323475 r3339444 11 11 ?> 12 12 13 <h4><?php esc_html_e("How to use CryptX in your Template", 'cryptx'); ?></h4> 14 <div class="cryptx-documentation"> 15 <p>The <code>[cryptx]</code> shortcode allows you to protect email addresses from spam bots in your WordPress posts 16 and pages, even when CryptX is disabled globally for that content.</p> 17 18 <h3>Basic Usage</h3> 19 <pre><code>[cryptx][email protected][/cryptx]</code></pre> 20 21 <h3>Advanced Usage</h3> 22 <pre><code>[cryptx linktext="Contact Us" subject="Website Inquiry"][email protected][/cryptx]</code></pre> 23 24 <h4>Available Attributes</h4> 25 <table class="cryptx-attributes"> 26 <thead> 27 <tr> 28 <th>Attribute</th> 29 <th>Description</th> 30 <th>Default</th> 31 <th>Example</th> 32 </tr> 33 </thead> 34 <tbody> 35 <tr> 36 <td><code>linktext</code></td> 37 <td>Custom text to display instead of the email address</td> 38 <td>Email address</td> 39 <td><code>linktext="Contact Us"</code></td> 40 </tr> 41 <tr> 42 <td><code>subject</code></td> 43 <td>Pre-defined subject line for the email</td> 44 <td>None</td> 45 <td><code>subject="Website Inquiry"</code></td> 46 </tr> 47 </tbody> 48 </table> 49 50 <h3>Examples</h3> 51 52 <h4>1. Basic Email Protection</h4> 53 <pre><code>[cryptx][email protected][/cryptx]</code></pre> 54 <p>Displays: A protected version of [email protected]</p> 55 56 <h4>2. Custom Link Text</h4> 57 <pre><code>[cryptx linktext="Send us an email"][email protected][/cryptx]</code></pre> 58 <p>Displays: "Send us an email" as a protected link</p> 59 60 <h4>3. With Subject Line</h4> 61 <pre><code>[cryptx subject="Product Inquiry"][email protected][/cryptx]</code></pre> 62 <p>Creates a link that opens the email client with a pre-filled subject line</p> 63 64 <h3>Best Practices</h3> 65 <ul> 66 <li>Use the shortcode when you need to protect individual email addresses in content where CryptX is disabled 67 globally 68 </li> 69 <li>Consider using custom link text for better user experience</li> 70 <li>Use meaningful subject lines when applicable</li> 71 <li>Don't nest shortcodes within the email address</li> 72 </ul> 73 74 <h3>Troubleshooting</h3> 75 <ul> 76 <li><strong>Email Not Protected:</strong> Ensure the shortcode syntax is correct with both opening and closing 77 tags 78 </li> 79 <li><strong>Link Not Working:</strong> Verify that JavaScript is enabled in the browser</li> 80 <li><strong>Strange Characters:</strong> Make sure the email address format is valid</li> 81 </ul> 82 83 <div class="cryptx-notes"> 84 <h4>Important Notes</h4> 85 <ul> 86 <li>The shortcode works independently of global CryptX settings</li> 87 <li>JavaScript must be enabled in the visitor's browser</li> 88 <li>Email addresses are protected using JavaScript encryption</li> 89 </ul> 90 </div> 91 92 <div class="cryptx-version"> 93 <p><strong>Available since:</strong> Version 2.7</p> 94 </div> 95 </div> 96 97 <h4><?php esc_html_e("How to use CryptX javascript function", 'cryptx'); ?></h4> 98 <div class="cryptx-documentation"> 99 <h2>JavaScript Email Protection Functions</h2> 100 101 <p>This section describes the JavaScript functions available for email protection in CryptX.</p> 102 103 <h3>generateDeCryptXHandler()</h3> 104 105 <p>A JavaScript function that generates an encrypted handler for email address protection. 106 This function creates a special URL format that encrypts email addresses to protect them 107 from spam bots while keeping them clickable for real users.</p> 108 109 <h4>Parameters</h4> 110 <ul> 111 <li><code>emailAddress</code> (string) - The email address to encrypt (e.g., "[email protected]")</li> 112 </ul> 113 114 <h4>Returns</h4> 115 <ul> 116 <li>(string) A JavaScript handler string in the format "javascript:DeCryptX('encrypted_string')"</li> 117 </ul> 118 119 <h4>Examples</h4> 120 121 <p>Basic usage in JavaScript:</p> 122 <pre><code class="language-javascript">const handler = generateDeCryptXHandler("[email protected]"); 13 <div class="cryptx-tab-content cryptx-howto-settings"> 14 <table class="form-table"> 15 <!-- Shortcode Usage Section --> 16 <tr> 17 <th colspan="2"> 18 <h3 style="margin: 20px 0 10px 0; padding: 10px 0; border-bottom: 1px solid #ddd;"> 19 <?php esc_html_e("How to use CryptX Shortcode", 'cryptx'); ?> 20 </h3> 21 </th> 22 </tr> 23 <tr> 24 <th scope="row"><?php esc_html_e("Basic Usage", 'cryptx'); ?></th> 25 <td> 26 <p><?php esc_html_e("The [cryptx] shortcode allows you to protect email addresses from spam bots in your WordPress posts and pages, even when CryptX is disabled globally for that content.", 'cryptx'); ?></p> 27 <pre><code>[cryptx][email protected][/cryptx]</code></pre> 28 <p class="description"><?php esc_html_e("Displays: A protected version of [email protected]", 'cryptx'); ?></p> 29 </td> 30 </tr> 31 <tr> 32 <th scope="row"><?php esc_html_e("Advanced Usage", 'cryptx'); ?></th> 33 <td> 34 <pre><code>[cryptx linktext="Contact Us" subject="Website Inquiry"][email protected][/cryptx]</code></pre> 35 <p class="description"><?php esc_html_e("Creates a link with custom text and pre-filled subject line.", 'cryptx'); ?></p> 36 </td> 37 </tr> 38 <tr> 39 <th scope="row"><?php esc_html_e("Available Attributes", 'cryptx'); ?></th> 40 <td> 41 <table class="widefat striped"> 42 <thead> 43 <tr> 44 <th><?php esc_html_e("Attribute", 'cryptx'); ?></th> 45 <th><?php esc_html_e("Description", 'cryptx'); ?></th> 46 <th><?php esc_html_e("Default", 'cryptx'); ?></th> 47 <th><?php esc_html_e("Example", 'cryptx'); ?></th> 48 </tr> 49 </thead> 50 <tbody> 51 <tr> 52 <td><code>linktext</code></td> 53 <td><?php esc_html_e("Custom text to display instead of the email address", 'cryptx'); ?></td> 54 <td><?php esc_html_e("Email address", 'cryptx'); ?></td> 55 <td><code>linktext="Contact Us"</code></td> 56 </tr> 57 <tr> 58 <td><code>subject</code></td> 59 <td><?php esc_html_e("Pre-defined subject line for the email", 'cryptx'); ?></td> 60 <td><?php esc_html_e("None", 'cryptx'); ?></td> 61 <td><code>subject="Website Inquiry"</code></td> 62 </tr> 63 </tbody> 64 </table> 65 </td> 66 </tr> 67 68 <!-- Examples Section --> 69 <tr> 70 <th colspan="2"> 71 <h3 style="margin: 20px 0 10px 0; padding: 10px 0; border-bottom: 1px solid #ddd;"> 72 <?php esc_html_e("Examples", 'cryptx'); ?> 73 </h3> 74 </th> 75 </tr> 76 <tr> 77 <th scope="row"><?php esc_html_e("1. Basic Email Protection", 'cryptx'); ?></th> 78 <td> 79 <pre><code>[cryptx][email protected][/cryptx]</code></pre> 80 <p class="description"><?php esc_html_e("Displays: A protected version of [email protected]", 'cryptx'); ?></p> 81 </td> 82 </tr> 83 <tr> 84 <th scope="row"><?php esc_html_e("2. Custom Link Text", 'cryptx'); ?></th> 85 <td> 86 <pre><code>[cryptx linktext="Send us an email"][email protected][/cryptx]</code></pre> 87 <p class="description"><?php esc_html_e("Displays: \"Send us an email\" as a protected link", 'cryptx'); ?></p> 88 </td> 89 </tr> 90 <tr> 91 <th scope="row"><?php esc_html_e("3. With Subject Line", 'cryptx'); ?></th> 92 <td> 93 <pre><code>[cryptx subject="Product Inquiry"][email protected][/cryptx]</code></pre> 94 <p class="description"><?php esc_html_e("Creates a link that opens the email client with a pre-filled subject line", 'cryptx'); ?></p> 95 </td> 96 </tr> 97 98 <!-- Best Practices Section --> 99 <tr> 100 <th colspan="2"> 101 <h3 style="margin: 20px 0 10px 0; padding: 10px 0; border-bottom: 1px solid #ddd;"> 102 <?php esc_html_e("Best Practices", 'cryptx'); ?> 103 </h3> 104 </th> 105 </tr> 106 <tr> 107 <th scope="row"><?php esc_html_e("Recommendations", 'cryptx'); ?></th> 108 <td> 109 <ul> 110 <li><?php esc_html_e("Use the shortcode when you need to protect individual email addresses in content where CryptX is disabled globally", 'cryptx'); ?></li> 111 <li><?php esc_html_e("Consider using custom link text for better user experience", 'cryptx'); ?></li> 112 <li><?php esc_html_e("Use meaningful subject lines when applicable", 'cryptx'); ?></li> 113 <li><?php esc_html_e("Don't nest shortcodes within the email address", 'cryptx'); ?></li> 114 </ul> 115 </td> 116 </tr> 117 118 <!-- JavaScript Functions Section --> 119 <tr> 120 <th colspan="2"> 121 <h3 style="margin: 20px 0 10px 0; padding: 10px 0; border-bottom: 1px solid #ddd;"> 122 <?php esc_html_e("JavaScript Functions", 'cryptx'); ?> 123 </h3> 124 </th> 125 </tr> 126 <tr> 127 <th scope="row"><?php esc_html_e("generateDeCryptXHandler()", 'cryptx'); ?></th> 128 <td> 129 <p><?php esc_html_e("A JavaScript function that generates an encrypted handler for email address protection. This function creates a special URL format that encrypts email addresses to protect them from spam bots while keeping them clickable for real users.", 'cryptx'); ?></p> 130 131 <h4><?php esc_html_e("Parameters", 'cryptx'); ?></h4> 132 <ul> 133 <li><code>emailAddress</code> (string) - <?php esc_html_e("The email address to encrypt (e.g., \"[email protected]\")", 'cryptx'); ?></li> 134 </ul> 135 136 <h4><?php esc_html_e("Returns", 'cryptx'); ?></h4> 137 <ul> 138 <li><?php esc_html_e("(string) A JavaScript handler string in the format \"javascript:DeCryptX('encrypted_string')\"", 'cryptx'); ?></li> 139 </ul> 140 141 <h4><?php esc_html_e("Basic Usage", 'cryptx'); ?></h4> 142 <pre><code class="language-javascript">const handler = generateDeCryptXHandler("[email protected]"); 123 143 // Returns: javascript:DeCryptX('1A2B3C...')</code></pre> 124 144 125 <p>Creating a protected link:</p>126 <pre><code class="language-javascript">const link = document.createElement('a');145 <h4><?php esc_html_e("Creating a Protected Link", 'cryptx'); ?></h4> 146 <pre><code class="language-javascript">const link = document.createElement('a'); 127 147 link.href = generateDeCryptXHandler('[email protected]'); 128 148 link.textContent = "Contact Us"; 129 149 document.body.appendChild(link);</code></pre> 130 150 131 <p>Direct HTML usage:</p> 132 <pre><code class="language-html"><a href="javascript:generateDeCryptXHandler('[email protected]')">Contact Us</a></code></pre> 133 134 <h4>Implementation with Error Handling</h4> 135 <pre><code class="language-javascript">function createSafeEmailLink(email, linkText) { 151 <h4><?php esc_html_e("Implementation with Error Handling", 'cryptx'); ?></h4> 152 <pre><code class="language-javascript">function createSafeEmailLink(email, linkText) { 136 153 try { 137 154 // Input validation … … 155 172 } 156 173 }</code></pre> 157 158 <h4>Important Notes</h4> 159 160 <h5>1. Dependencies</h5> 161 <ul> 162 <li>Requires generateHashFromString function</li> 163 <li>Requires DeCryptX function in the global scope</li> 164 </ul> 165 166 <h5>2. Browser Requirements</h5> 167 <ul> 168 <li>Works in all modern browsers</li> 169 <li>JavaScript must be enabled</li> 170 </ul> 171 172 <h5>3. Best Practices</h5> 173 <ul> 174 <li>Provide fallback for users with JavaScript disabled</li> 175 <li>Use meaningful link text instead of showing the email address</li> 176 <li>Add title or aria-label for accessibility</li> 177 </ul> 178 179 <h5>4. Security Considerations</h5> 180 <ul> 181 <li>Encryption is for spam prevention only</li> 182 <li>Not suitable for sensitive data transmission</li> 183 <li>Email address will be visible in browser's JavaScript console when decrypted</li> 184 </ul> 185 186 <h4>Troubleshooting</h4> 187 188 <h5>1. If links are not working:</h5> 189 <ul> 190 <li>Verify DeCryptX function is included</li> 191 <li>Check if JavaScript is enabled</li> 192 <li>Ensure email address format is valid</li> 193 </ul> 194 195 <h5>2. Different encryptions:</h5> 196 <ul> 197 <li>Normal behavior: same email generates different encrypted strings</li> 198 <li>Each encryption uses random values for security</li> 199 </ul> 200 201 <h5>3. Performance:</h5> 202 <ul> 203 <li>Lightweight function suitable for multiple uses</li> 204 <li>Safe for use in loops or event handlers</li> 205 </ul> 206 207 <div class="cryptx-related"> 208 <h4>Related Functions</h4> 209 <ul> 210 <li><a href="#DeCryptX">DeCryptX()</a> - For the decryption function</li> 211 <li><a href="#generateHashFromString">generateHashFromString()</a> - For the internal encryption function 212 </li> 213 </ul> 214 </div> 215 216 <div class="cryptx-version"> 217 <p><strong>Since:</strong> Version 3.5.0</p> 218 </div> 174 </td> 175 </tr> 176 177 <!-- Important Notes Section --> 178 <tr> 179 <th colspan="2"> 180 <h3 style="margin: 20px 0 10px 0; padding: 10px 0; border-bottom: 1px solid #ddd;"> 181 <?php esc_html_e("Important Notes", 'cryptx'); ?> 182 </h3> 183 </th> 184 </tr> 185 <tr> 186 <th scope="row"><?php esc_html_e("Requirements", 'cryptx'); ?></th> 187 <td> 188 <ul> 189 <li><?php esc_html_e("The shortcode works independently of global CryptX settings", 'cryptx'); ?></li> 190 <li><?php esc_html_e("JavaScript must be enabled in the visitor's browser", 'cryptx'); ?></li> 191 <li><?php esc_html_e("Email addresses are protected using JavaScript encryption", 'cryptx'); ?></li> 192 </ul> 193 </td> 194 </tr> 195 <tr> 196 <th scope="row"><?php esc_html_e("Browser Support", 'cryptx'); ?></th> 197 <td> 198 <ul> 199 <li><?php esc_html_e("Works in all modern browsers", 'cryptx'); ?></li> 200 <li><?php esc_html_e("Provide fallback for users with JavaScript disabled", 'cryptx'); ?></li> 201 </ul> 202 </td> 203 </tr> 204 205 <!-- Troubleshooting Section --> 206 <tr> 207 <th colspan="2"> 208 <h3 style="margin: 20px 0 10px 0; padding: 10px 0; border-bottom: 1px solid #ddd;"> 209 <?php esc_html_e("Troubleshooting", 'cryptx'); ?> 210 </h3> 211 </th> 212 </tr> 213 <tr> 214 <th scope="row"><?php esc_html_e("Common Issues", 'cryptx'); ?></th> 215 <td> 216 <ul> 217 <li><strong><?php esc_html_e("Email Not Protected:", 'cryptx'); ?></strong> <?php esc_html_e("Ensure the shortcode syntax is correct with both opening and closing tags", 'cryptx'); ?></li> 218 <li><strong><?php esc_html_e("Link Not Working:", 'cryptx'); ?></strong> <?php esc_html_e("Verify that JavaScript is enabled in the browser", 'cryptx'); ?></li> 219 <li><strong><?php esc_html_e("Strange Characters:", 'cryptx'); ?></strong> <?php esc_html_e("Make sure the email address format is valid", 'cryptx'); ?></li> 220 <li><strong><?php esc_html_e("Different encryptions:", 'cryptx'); ?></strong> <?php esc_html_e("Normal behavior: same email generates different encrypted strings for security", 'cryptx'); ?></li> 221 </ul> 222 </td> 223 </tr> 224 225 <!-- Version Info --> 226 <tr> 227 <th scope="row"><?php esc_html_e("Version Information", 'cryptx'); ?></th> 228 <td> 229 <p><strong><?php esc_html_e("Shortcode available since:", 'cryptx'); ?></strong> <?php esc_html_e("Version 2.7", 'cryptx'); ?></p> 230 <p><strong><?php esc_html_e("JavaScript functions since:", 'cryptx'); ?></strong> <?php esc_html_e("Version 3.5.0", 'cryptx'); ?></p> 231 </td> 232 </tr> 233 </table> 219 234 </div> 220 235 221 236 <style> 222 .cryptx-documentation { 223 max-width: 900px; 224 margin: 20px auto; 225 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; 226 line-height: 1.6; 227 } 228 229 .cryptx-documentation h2 { 230 color: #23282d; 231 border-bottom: 1px solid #eee; 232 padding-bottom: 10px; 233 } 234 235 .cryptx-documentation h3, 236 .cryptx-documentation h4, 237 .cryptx-documentation h5 { 238 color: #23282d; 239 margin-top: 1.5em; 240 } 241 242 .cryptx-documentation code { 237 .cryptx-howto-settings pre { 238 background: #f4f4f4; 239 padding: 15px; 240 border-radius: 4px; 241 overflow-x: auto; 242 margin: 10px 0; 243 } 244 245 .cryptx-howto-settings code { 243 246 background: #f4f4f4; 244 247 padding: 2px 6px; … … 247 250 } 248 251 249 .cryptx-documentation pre { 250 background: #f4f4f4; 251 padding: 15px; 252 border-radius: 4px; 253 overflow-x: auto; 254 } 255 256 .cryptx-documentation pre code { 252 .cryptx-howto-settings pre code { 257 253 background: none; 258 254 padding: 0; 259 255 } 260 256 261 .cryptx- documentationul {257 .cryptx-howto-settings ul { 262 258 margin-left: 20px; 263 259 } 264 260 265 .cryptx- documentationli {261 .cryptx-howto-settings li { 266 262 margin-bottom: 8px; 267 263 } 268 264 269 .cryptx-related { 270 margin-top: 30px; 271 padding: 15px; 272 background: #f8f9fa; 273 border-radius: 4px; 274 } 275 276 .cryptx-version { 265 .cryptx-howto-settings h4 { 277 266 margin-top: 20px; 278 color: #666; 279 font-style: italic; 267 margin-bottom: 10px; 268 color: #23282d; 269 } 270 271 .cryptx-howto-settings .widefat { 272 margin-top: 10px; 273 } 274 275 .cryptx-howto-settings .widefat th, 276 .cryptx-howto-settings .widefat td { 277 padding: 8px 10px; 280 278 } 281 279 </style> -
cryptx/tags/4.0.0/templates/admin/tabs/presentation.php
r3323475 r3339444 1 1 <?php 2 2 /** 3 * Template for the CryptX presentation settings tab 4 * 5 * @var array $css CSS settings 6 * @var array $linkTextOptions Link text display options 7 * @var int $selectedOption Currently selected link text option 3 * CryptX Presentation Settings Tab Template 4 * Variables extracted from PresentationSettingsTab: 5 * - $css: CSS settings array 6 * - $emailReplacements: Email replacement settings 7 * - $linkTextOptions: Link text configuration options 8 * - $selectedOption: Currently selected option value 8 9 */ 9 10 defined('ABSPATH') || exit; 10 if (!defined('ABSPATH')) { 11 exit; 12 } 11 13 ?> 12 14 13 <h4><?php _e("Define CSS Options", 'cryptx'); ?></h4>15 <div class="cryptx-tab-content cryptx-presentation-settings"> 14 16 <table class="form-table"> 15 <tr> 16 <th><label for="cryptX_var[css_id]"><?php _e("CSS ID", 'cryptx'); ?></label></th> 17 18 <!-- CSS Settings Section --> 19 <tr> 20 <th colspan="2"> 21 <h3 style="margin: 20px 0 10px 0; padding: 10px 0; border-bottom: 1px solid #ddd;"> 22 <?php _e('CSS Settings', 'cryptx'); ?> 23 </h3> 24 </th> 25 </tr> 26 <tr> 27 <th scope="row"><?php _e("CSS-ID", 'cryptx'); ?></th> 17 28 <td> 18 <input name="cryptX_var[css_id]" 19 id="cryptX_var[css_id]" 20 type="text" 21 value="<?php echo esc_attr($css['id']); ?>" 22 class="regular-text" /> 23 <p class="description"> 24 <?php _e("Please be careful using this feature! IDs should be unique. You should prefer using a css class instead.", 'cryptx'); ?> 25 </p> 29 <input name="cryptX_var[css_id]" id="css_id" value="<?php echo esc_attr($css['id']); ?>" type="text" class="regular-text" /> 30 <p class="description"><?php _e("Please be careful using this feature! IDs should be unique. You should prefer using a CSS class instead.", 'cryptx'); ?></p> 26 31 </td> 27 32 </tr> 28 33 <tr> 29 <th ><label for="cryptX_var[css_class]"><?php _e("CSS Class", 'cryptx'); ?></label></th>34 <th scope="row"><?php _e("CSS-Class", 'cryptx'); ?></th> 30 35 <td> 31 <input name="cryptX_var[css_class]" 32 id="cryptX_var[css_class]" 33 type="text" 34 value="<?php echo esc_attr($css['class']); ?>" 35 class="regular-text" /> 36 <input name="cryptX_var[css_class]" id="css_class" value="<?php echo esc_attr($css['class']); ?>" type="text" class="regular-text" /> 37 <p class="description"><?php _e("Add custom CSS class to encrypted email links.", 'cryptx'); ?></p> 36 38 </td> 37 39 </tr> 40 41 <!-- Link Text Options Section --> 42 <tr> 43 <th colspan="2"> 44 <h3 style="margin: 20px 0 10px 0; padding: 10px 0; border-bottom: 1px solid #ddd;"> 45 <?php _e('Link Text Options', 'cryptx'); ?> 46 </h3> 47 </th> 48 </tr> 49 <tr> 50 <th scope="row"><?php _e('Presentation Method', 'cryptx'); ?></th> 51 <td> 52 <fieldset> 53 <!-- Option 0: Replacement Text --> 54 <label> 55 <input name="cryptX_var[opt_linktext]" type="radio" value="0" <?php checked($selectedOption, 0); ?> /> 56 <strong><?php _e("Show Email with text replacement", 'cryptx'); ?></strong> 57 </label> 58 <div style="margin-left: 25px; margin-top: 10px; margin-bottom: 20px;"> 59 <table class="form-table" style="margin: 0;"> 60 <?php foreach ($linkTextOptions['replacement']['fields'] as $field => $config): ?> 61 <tr> 62 <th scope="row" style="padding-left: 0;"><?php echo $config['label']; ?></th> 63 <td style="padding-left: 10px;"> 64 <input name="cryptX_var[<?php echo esc_attr($field); ?>]" 65 value="<?php echo esc_attr($config['value']); ?>" 66 type="text" class="regular-text" /> 67 </td> 68 </tr> 69 <?php endforeach; ?> 70 </table> 71 </div> 72 73 <!-- Option 1: Custom Text --> 74 <label> 75 <input name="cryptX_var[opt_linktext]" type="radio" value="1" <?php checked($selectedOption, 1); ?> /> 76 <strong><?php _e("Show custom text", 'cryptx'); ?></strong> 77 </label> 78 <div style="margin-left: 25px; margin-top: 10px; margin-bottom: 20px;"> 79 <table class="form-table" style="margin: 0;"> 80 <?php foreach ($linkTextOptions['customText']['fields'] as $field => $config): ?> 81 <tr> 82 <th scope="row" style="padding-left: 0;"><?php echo $config['label']; ?></th> 83 <td style="padding-left: 10px;"> 84 <input name="cryptX_var[<?php echo esc_attr($field); ?>]" 85 value="<?php echo esc_attr($config['value']); ?>" 86 type="text" class="regular-text" /> 87 </td> 88 </tr> 89 <?php endforeach; ?> 90 </table> 91 </div> 92 93 <!-- Option 2: External Image --> 94 <label> 95 <input name="cryptX_var[opt_linktext]" type="radio" value="2" <?php checked($selectedOption, 2); ?> /> 96 <strong><?php _e("Show external image", 'cryptx'); ?></strong> 97 </label> 98 <div style="margin-left: 25px; margin-top: 10px; margin-bottom: 20px;"> 99 <table class="form-table" style="margin: 0;"> 100 <?php foreach ($linkTextOptions['externalImage']['fields'] as $field => $config): ?> 101 <tr> 102 <th scope="row" style="padding-left: 0;"><?php echo $config['label']; ?></th> 103 <td style="padding-left: 10px;"> 104 <?php if ($field === 'alt_linkimage'): ?> 105 <input name="cryptX_var[<?php echo esc_attr($field); ?>]" 106 value="<?php echo esc_attr($config['value']); ?>" 107 type="url" class="regular-text" 108 placeholder="https://example.com/image.png" /> 109 <?php else: ?> 110 <input name="cryptX_var[<?php echo esc_attr($field); ?>]" 111 value="<?php echo esc_attr($config['value']); ?>" 112 type="text" class="regular-text" /> 113 <?php endif; ?> 114 </td> 115 </tr> 116 <?php endforeach; ?> 117 </table> 118 </div> 119 120 <!-- Option 3: Uploaded Image --> 121 <label> 122 <input name="cryptX_var[opt_linktext]" type="radio" value="3" <?php checked($selectedOption, 3); ?> /> 123 <strong><?php _e("Show uploaded image", 'cryptx'); ?></strong> 124 </label> 125 <div style="margin-left: 25px; margin-top: 10px; margin-bottom: 20px;"> 126 <table class="form-table" style="margin: 0;"> 127 <tr> 128 <th scope="row" style="padding-left: 0;"><?php _e("Select Image", 'cryptx'); ?></th> 129 <td style="padding-left: 10px;"> 130 <input name="cryptX_var[alt_uploadedimage]" id="alt_uploadedimage" 131 value="<?php echo esc_attr($linkTextOptions['uploadedImage']['fields']['alt_uploadedimage']['value']); ?>" 132 type="hidden" /> 133 <button type="button" class="button" id="upload_image_button"> 134 <?php _e("Choose Image", 'cryptx'); ?> 135 </button> 136 <div id="image_preview" style="margin-top: 10px;"></div> 137 </td> 138 </tr> 139 <tr> 140 <th scope="row" style="padding-left: 0;"> 141 <?php echo $linkTextOptions['uploadedImage']['fields']['alt_linkimage_title']['label']; ?> 142 </th> 143 <td style="padding-left: 10px;"> 144 <input name="cryptX_var[alt_linkimage_title]" 145 value="<?php echo esc_attr($linkTextOptions['uploadedImage']['fields']['alt_linkimage_title']['value']); ?>" 146 type="text" class="regular-text" /> 147 </td> 148 </tr> 149 </table> 150 </div> 151 152 <!-- Option 4: Scrambled Text --> 153 <label> 154 <input name="cryptX_var[opt_linktext]" type="radio" value="4" <?php checked($selectedOption, 4); ?> /> 155 <strong><?php echo $linkTextOptions['scrambled']['label']; ?></strong> 156 </label><br/><br/> 157 158 <!-- Option 5: PNG Image --> 159 <label> 160 <input name="cryptX_var[opt_linktext]" type="radio" value="5" <?php checked($selectedOption, 5); ?> /> 161 <strong><?php echo $linkTextOptions['pngImage']['label']; ?></strong> 162 </label> 163 <div style="margin-left: 25px; margin-top: 10px; margin-bottom: 20px;"> 164 <table class="form-table" style="margin: 0;"> 165 <?php foreach ($linkTextOptions['pngImage']['fields'] as $field => $config): ?> 166 <tr> 167 <th scope="row" style="padding-left: 0;"><?php echo $config['label']; ?></th> 168 <td style="padding-left: 10px;"> 169 <?php if ($field === 'c2i_font'): ?> 170 <select name="cryptX_var[<?php echo esc_attr($field); ?>]"> 171 <?php foreach ($config['options'] as $value => $label): ?> 172 <option value="<?php echo esc_attr($value); ?>" <?php selected($config['value'], $value); ?>> 173 <?php echo esc_html($label); ?> 174 </option> 175 <?php endforeach; ?> 176 </select> 177 <?php elseif ($field === 'c2i_fontRGB'): ?> 178 <input name="cryptX_var[<?php echo esc_attr($field); ?>]" 179 id="c2i_fontRGB" 180 value="<?php echo esc_attr($config['value']); ?>" 181 type="text" class="color-field" /> 182 <?php elseif ($field === 'c2i_fontSize'): ?> 183 <input name="cryptX_var[<?php echo esc_attr($field); ?>]" 184 value="<?php echo esc_attr($config['value']); ?>" 185 type="number" min="8" max="72" /> 186 <?php else: ?> 187 <input name="cryptX_var[<?php echo esc_attr($field); ?>]" 188 value="<?php echo esc_attr($config['value']); ?>" 189 type="text" class="regular-text" /> 190 <?php endif; ?> 191 </td> 192 </tr> 193 <?php endforeach; ?> 194 </table> 195 </div> 196 </fieldset> 197 </td> 198 </tr> 38 199 </table> 39 200 40 <h4><?php _e("Define Presentation Options", 'cryptx'); ?></h4> 41 <table class="form-table"> 42 <tbody> 43 <!-- Character Replacement Option --> 44 <tr> 45 <td> 46 <input type="radio" 47 name="cryptX_var[opt_linktext]" 48 id="opt_linktext_0" 49 value="<?php echo esc_attr($linkTextOptions['replacement']['value']); ?>" 50 <?php checked($selectedOption, $linkTextOptions['replacement']['value']); ?> /> 51 </td> 52 <th scope="row"> 53 <?php foreach ($linkTextOptions['replacement']['fields'] as $key => $field): ?> 54 <div class="cryptx-field-row"> 55 <label for="cryptX_var[<?php echo esc_attr($key); ?>]"> 56 <?php echo esc_html($field['label']); ?> 57 </label> 58 <input type="text" 59 name="cryptX_var[<?php echo esc_attr($key); ?>]" 60 id="cryptX_var[<?php echo esc_attr($key); ?>]" 61 value="<?php echo esc_attr($field['value']); ?>" 62 class="regular-text" /> 63 </div> 64 <?php endforeach; ?> 65 </th> 66 </tr> 67 68 <tr class="spacer"><td colspan="3"><hr></td></tr> 69 70 <!-- Custom Text Option --> 71 <tr> 72 <td> 73 <input type="radio" 74 name="cryptX_var[opt_linktext]" 75 id="opt_linktext_1" 76 value="<?php echo esc_attr($linkTextOptions['customText']['value']); ?>" 77 <?php checked($selectedOption, $linkTextOptions['customText']['value']); ?> /> 78 </td> 79 <th> 80 <?php foreach ($linkTextOptions['customText']['fields'] as $key => $field): ?> 81 <label for="cryptX_var[<?php echo esc_attr($key); ?>]"> 82 <?php echo esc_html($field['label']); ?> 83 </label> 84 <input type="text" 85 name="cryptX_var[<?php echo esc_attr($key); ?>]" 86 id="cryptX_var[<?php echo esc_attr($key); ?>]" 87 value="<?php echo esc_attr($field['value']); ?>" 88 class="regular-text" /> 89 <?php endforeach; ?> 90 </th> 91 </tr> 92 93 <tr class="spacer"><td colspan="3"><hr></td></tr> 94 95 <!-- External Image Option --> 96 <tr> 97 <td> 98 <input type="radio" 99 name="cryptX_var[opt_linktext]" 100 id="opt_linktext_2" 101 value="<?php echo esc_attr($linkTextOptions['externalImage']['value']); ?>" 102 <?php checked($selectedOption, $linkTextOptions['externalImage']['value']); ?> /> 103 </td> 104 <th> 105 <?php foreach ($linkTextOptions['externalImage']['fields'] as $key => $field): ?> 106 <div class="cryptx-field-row"> 107 <label for="cryptX_var[<?php echo esc_attr($key); ?>]"> 108 <?php echo esc_html($field['label']); ?> 109 </label> 110 <input type="text" 111 name="cryptX_var[<?php echo esc_attr($key); ?>]" 112 id="cryptX_var[<?php echo esc_attr($key); ?>]" 113 value="<?php echo esc_attr($field['value']); ?>" 114 class="regular-text" /> 115 </div> 116 <?php endforeach; ?> 117 </th> 118 </tr> 119 120 <tr class="spacer"><td colspan="3"><hr></td></tr> 121 122 <!-- Uploaded Image Option --> 123 <tr> 124 <td> 125 <input type="radio" 126 name="cryptX_var[opt_linktext]" 127 id="opt_linktext_3" 128 value="<?php echo esc_attr($linkTextOptions['uploadedImage']['value']); ?>" 129 <?php checked($selectedOption, $linkTextOptions['uploadedImage']['value']); ?> /> 130 </td> 131 <th> 132 <label for="upload_image_button"><?php _e("Select an uploaded image", 'cryptx'); ?></label> 133 </th> 134 <td> 135 <input id="upload_image_button" type="button" class="button" value="<?php esc_attr_e('Upload image'); ?>" /> 136 <input id="remove_image_button" type="button" class="button button-link-delete hidden" value="<?php esc_attr_e('Delete image'); ?>" /> 137 <span id="opt_linktext4_notice"><?php _e("You have to upload an image first before this option can be activated.", 'cryptx'); ?></span> 138 <input type="hidden" 139 name="cryptX_var[alt_uploadedimage]" 140 id="image_attachment_id" 141 value="<?php echo esc_attr($linkTextOptions['uploadedImage']['fields']['alt_uploadedimage']['value']); ?>"> 142 <div> 143 <img id="image-preview" src="<?php echo esc_url(wp_get_attachment_url($linkTextOptions['uploadedImage']['fields']['alt_uploadedimage']['value'])); ?>"> 144 </div> 145 <label for="cryptX_var[alt_linkimage_title]"> 146 <?php echo esc_html($linkTextOptions['uploadedImage']['fields']['alt_linkimage_title']['label']); ?> 147 </label> 148 <input type="text" 149 name="cryptX_var[alt_linkimage_title]" 150 value="<?php echo esc_attr($linkTextOptions['uploadedImage']['fields']['alt_linkimage_title']['value']); ?>" 151 class="regular-text" /> 152 </td> 153 </tr> 154 155 <tr class="spacer"><td colspan="3"><hr></td></tr> 156 157 <!-- Text Scrambling Option --> 158 <tr> 159 <td> 160 <input type="radio" 161 name="cryptX_var[opt_linktext]" 162 id="opt_linktext_4" 163 value="<?php echo esc_attr($linkTextOptions['scrambled']['value']); ?>" 164 <?php checked($selectedOption, $linkTextOptions['scrambled']['value']); ?> /> 165 </td> 166 <th colspan="2"> 167 <?php echo esc_html($linkTextOptions['scrambled']['label']); ?> 168 <small><?php _e("Try it and look at your site and check the html source!", 'cryptx'); ?></small> 169 </th> 170 </tr> 171 172 <tr class="spacer"><td colspan="3"><hr></td></tr> 173 174 <!-- PNG Image Conversion Option --> 175 <tr> 176 <td> 177 <input type="radio" 178 name="cryptX_var[opt_linktext]" 179 id="opt_linktext_5" 180 value="<?php echo esc_attr($linkTextOptions['pngImage']['value']); ?>" 181 <?php checked($selectedOption, $linkTextOptions['pngImage']['value']); ?> /> 182 </td> 183 <th><?php echo esc_html($linkTextOptions['pngImage']['label']); ?></th> 184 <td> 185 <?php foreach ($linkTextOptions['pngImage']['fields'] as $key => $field): ?> 186 <div class="cryptx-field-row"> 187 <label for="cryptX_var[<?php echo esc_attr($key); ?>]"> 188 <?php echo esc_html($field['label']); ?> 189 </label> 190 <?php if ($key === 'c2i_font'): ?> 191 <select name="cryptX_var[<?php echo esc_attr($key); ?>]" 192 id="cryptX_var[<?php echo esc_attr($key); ?>]"> 193 <?php foreach ($field['options'] as $value => $label): ?> 194 <option value="<?php echo esc_attr($value); ?>" 195 <?php selected($field['value'], $value); ?>> 196 <?php echo esc_html($label); ?> 197 </option> 198 <?php endforeach; ?> 199 </select> 200 <?php elseif ($key === 'c2i_fontSize'): ?> 201 <input type="number" 202 name="cryptX_var[<?php echo esc_attr($key); ?>]" 203 id="cryptX_var[<?php echo esc_attr($key); ?>]" 204 value="<?php echo esc_attr($field['value']); ?>" 205 class="small-text" /> 206 <?php else: ?> 207 <input type="text" 208 name="cryptX_var[<?php echo esc_attr($key); ?>]" 209 id="cryptX_var[<?php echo esc_attr($key); ?>]" 210 value="<?php echo esc_attr($field['value']); ?>" 211 class="color-field" /> 212 <?php endif; ?> 213 </div> 214 <?php endforeach; ?> 215 </td> 216 </tr> 217 </tbody> 218 </table> 219 220 <?php submit_button(__('Save Presentation Settings', 'cryptx'), 'primary', 'cryptX_save_presentation_settings'); ?> 201 <!-- Submit Button --> 202 <p class="submit"> 203 <input type="submit" 204 name="cryptX_save_presentation_settings" 205 class="button-primary" 206 value="<?php _e('Save Changes', 'cryptx'); ?>" /> 207 <input type="submit" 208 name="cryptX_var_reset" 209 class="button-secondary" 210 value="<?php _e('Reset to Defaults', 'cryptx'); ?>" 211 onclick="return confirm('<?php _e('Are you sure you want to reset all presentation settings to defaults?', 'cryptx'); ?>');" /> 212 </p> 213 </div> 214 215 <script> 216 document.addEventListener('DOMContentLoaded', function() { 217 // Initialize WordPress Color Picker 218 if (typeof jQuery !== 'undefined' && jQuery.fn.wpColorPicker) { 219 jQuery('.color-field').wpColorPicker(); 220 } 221 222 // Handle image upload functionality 223 const uploadButton = document.getElementById('upload_image_button'); 224 const imageField = document.getElementById('alt_uploadedimage'); 225 const imagePreview = document.getElementById('image_preview'); 226 227 if (uploadButton && typeof wp !== 'undefined' && wp.media) { 228 let mediaUploader; 229 230 uploadButton.addEventListener('click', function(e) { 231 e.preventDefault(); 232 233 if (mediaUploader) { 234 mediaUploader.open(); 235 return; 236 } 237 238 mediaUploader = wp.media({ 239 title: '<?php _e("Choose Image", "cryptx"); ?>', 240 button: { 241 text: '<?php _e("Choose Image", "cryptx"); ?>' 242 }, 243 multiple: false, 244 library: { 245 type: 'image' 246 } 247 }); 248 249 mediaUploader.on('select', function() { 250 const attachment = mediaUploader.state().get('selection').first().toJSON(); 251 imageField.value = attachment.id; 252 253 if (attachment.sizes && attachment.sizes.thumbnail) { 254 imagePreview.innerHTML = '<img src="' + attachment.sizes.thumbnail.url + '" style="max-width: 150px; height: auto;" />'; 255 } else { 256 imagePreview.innerHTML = '<img src="' + attachment.url + '" style="max-width: 150px; height: auto;" />'; 257 } 258 }); 259 260 mediaUploader.open(); 261 }); 262 263 // Show current image if one is selected 264 if (imageField.value && imageField.value !== '0') { 265 wp.media.attachment(imageField.value).fetch().then(function(attachment) { 266 if (attachment.attributes.sizes && attachment.attributes.sizes.thumbnail) { 267 imagePreview.innerHTML = '<img src="' + attachment.attributes.sizes.thumbnail.url + '" style="max-width: 150px; height: auto;" />'; 268 } else { 269 imagePreview.innerHTML = '<img src="' + attachment.attributes.url + '" style="max-width: 150px; height: auto;" />'; 270 } 271 }); 272 } 273 } 274 }); 275 </script> -
cryptx/trunk/classes/Admin/ChangelogSettingsTab.php
r3323475 r3339444 5 5 use CryptX\Config; 6 6 7 /**8 * Class ChangelogSettingsTab9 * Handles the changelog tab functionality in the CryptX plugin admin interface10 */11 7 class ChangelogSettingsTab 12 8 { … … 15 11 */ 16 12 private Config $config; 13 14 /** 15 * Template path 16 */ 17 private const TEMPLATE_PATH = CRYPTX_DIR_PATH . 'templates/admin/tabs/changelog.php'; 17 18 18 19 /** … … 31 32 public function render(): void 32 33 { 33 echo '<h4>' . esc_html__('Changelog', 'cryptx') . '</h4>'; 34 $this->renderChangelogContent(); 34 if ('changelog' !== $this->getActiveTab()) { 35 return; 36 } 37 38 $changelogs = $this->getChangelogData(); 39 $this->renderTemplate(self::TEMPLATE_PATH, ['changelogs' => $changelogs]); 35 40 } 36 41 37 42 /** 38 * Parse and render changelog content from readme.txt43 * Get changelog data 39 44 */ 40 private function renderChangelogContent(): void45 private function getChangelogData(): array 41 46 { 42 47 $readmePath = CRYPTX_DIR_PATH . '/readme.txt'; 43 48 if (!file_exists($readmePath)) { 44 return ;49 return []; 45 50 } 46 51 47 52 $fileContents = file_get_contents($readmePath); 48 53 if ($fileContents === false) { 49 return ;54 return []; 50 55 } 51 56 52 $changelogs = $this->parseChangelog($fileContents); 53 foreach ($changelogs as $log) { 54 echo wp_kses_post("<dl>" . implode("", $log) . "</dl>"); 57 return $this->parseChangelog($fileContents); 58 } 59 60 /** 61 * Render template with data 62 */ 63 private function renderTemplate(string $path, array $data): void 64 { 65 if (!file_exists($path)) { 66 throw new \RuntimeException(sprintf('Template file not found: %s', $path)); 55 67 } 68 69 extract($data); 70 include $path; 71 } 72 73 /** 74 * Get active tab 75 */ 76 private function getActiveTab(): string 77 { 78 return sanitize_text_field($_GET['tab'] ?? 'general'); 56 79 } 57 80 … … 89 112 90 113 for ($i = 1; $i <= count($_sections); $i += 2) { 114 if (!isset($_sections[$i - 1]) || !isset($_sections[$i])) { 115 continue; 116 } 117 91 118 $title = $_sections[$i - 1]; 92 119 $sections[str_replace(' ', '_', strtolower($title))] = [ … … 111 138 112 139 for ($i = 1; $i <= count($_changelogs); $i += 2) { 140 if (!isset($_changelogs[$i - 1]) || !isset($_changelogs[$i])) { 141 continue; 142 } 143 113 144 $version = $_changelogs[$i - 1]; 114 145 $content = ltrim($_changelogs[$i], "\n"); 115 $content = str_replace("* ", "<li>", $content);116 $content = str_replace("\n", " </li>\n", $content);117 146 118 $changelogs[] = [ 119 'version' => "<dt>" . esc_html($version) . "</dt>", 120 'content' => "<dd><ul>" . wp_kses_post($content) . "</ul></dd>" 121 ]; 147 // Parse changelog items 148 $items = $this->parseChangelogItems($content); 149 150 if (!empty($items)) { 151 $changelogs[] = [ 152 'version' => $version, 153 'items' => $items 154 ]; 155 } 122 156 } 123 157 124 158 return $changelogs; 125 159 } 160 161 /** 162 * Parse individual changelog items 163 * 164 * @param string $content 165 * @return array 166 */ 167 private function parseChangelogItems(string $content): array 168 { 169 $lines = explode("\n", $content); 170 $items = []; 171 172 foreach ($lines as $line) { 173 $line = trim($line); 174 if (empty($line)) { 175 continue; 176 } 177 178 // Remove leading asterisk and clean up 179 if (strpos($line, '* ') === 0) { 180 $line = substr($line, 2); 181 } 182 183 if (!empty($line)) { 184 $items[] = trim($line); 185 } 186 } 187 188 return $items; 189 } 126 190 } -
cryptx/trunk/classes/Admin/GeneralSettingsTab.php
r3323475 r3339444 18 18 'disable_rss' => 0, 19 19 'java' => 1, 20 'load_java' => 1 20 'load_java' => 1, 21 'use_secure_encryption' => 0, 22 'encryption_mode' => 'legacy' 21 23 ]; 24 22 25 public function __construct(Config $config) { 23 26 $this->config = $config; … … 33 36 $settings = [ 34 37 'options' => $options, // Pass all options 38 'securitySettings' => [ 39 'use_secure_encryption' => [ 40 'label' => __('Use Secure Encryption', 'cryptx'), 41 'description' => __('Enable AES-256-GCM encryption for enhanced security (recommended for new installations)', 'cryptx'), 42 'value' => $options['use_secure_encryption'] ?? 0 43 ], 44 'encryption_mode' => [ 45 'label' => __('Encryption Mode', 'cryptx'), 46 'description' => __('Choose encryption method. Legacy mode maintains backward compatibility with existing encrypted emails.', 'cryptx'), 47 'value' => $options['encryption_mode'] ?? 'legacy', 48 'options' => [ 49 'legacy' => __('Legacy (Compatible)', 'cryptx'), 50 'secure' => __('Secure (AES-256-GCM)', 'cryptx') 51 ] 52 ] 53 ], 35 54 'applyTo' => [ 36 55 'the_content' => [ … … 50 69 'widget_text' => [ 51 70 'label' => __('Widgets', 'cryptx'), 52 'description' => __('( works only on all widgets, not on a single widget!)', 'cryptx')71 'description' => __('(applies to all text and HTML widgets)', 'cryptx') 53 72 ] 54 73 ], -
cryptx/trunk/classes/Admin/PresentationSettingsTab.php
r3323475 r3339444 13 13 } 14 14 15 /** 16 * Renders the template for the active tab. 17 * 18 * The function checks if the 'presentation' tab is active. If not, it returns without further 19 * execution. When active, it retrieves configuration options, organizes them into a structured 20 * settings array, and renders the template with the specified settings. 21 * 22 * @return void 23 */ 15 24 public function render(): void { 16 25 if ('presentation' !== $this->getActiveTab()) { … … 108 117 } 109 118 119 /** 120 * Renders a template file by including it and extracting the provided data for use within the template scope. 121 * 122 * @param string $path The file path to the template to be rendered. Must be a valid, existing file. 123 * @param array $data An associative array of data to be extracted and made available to the template. 124 * 125 * @return void 126 * 127 * @throws \RuntimeException If the specified template file does not exist. 128 */ 110 129 private function renderTemplate(string $path, array $data): void { 111 130 if (!file_exists($path)) { … … 117 136 } 118 137 138 /** 139 * Retrieves the active tab from the request, defaulting to 'general' if not specified. 140 * 141 * @return string The sanitized active tab value from the request or 'general' if no tab is provided. 142 */ 119 143 private function getActiveTab(): string { 120 144 return sanitize_text_field($_GET['tab'] ?? 'general'); 121 145 } 122 146 147 /** 148 * Retrieves the available font options by scanning a directory for font files. 149 * 150 * @return array An associative array where the keys are the font file names and the values are the font names without the '.ttf' extension. 151 */ 123 152 private function getFontOptions(): array { 124 153 $fonts = []; … … 132 161 } 133 162 163 /** 164 * Retrieves a list of files in a specified directory that match the given file extensions. 165 * 166 * @param string $path The path to the directory to scan for files. 167 * @param array $extensions An array of file extensions to filter the files by. 168 * Only files matching these extensions will be included in the results. 169 * 170 * @return array An array of filenames from the specified directory that match the provided extensions. 171 * If the directory does not exist, an empty array is returned. 172 */ 134 173 private function getFilesInDirectory(string $path, array $extensions): array { 135 174 if (!is_dir($path)) { … … 149 188 } 150 189 190 /** 191 * Saves and processes the settings for the application. Handles security checks, 192 * optional reset functionality, and updates sanitized settings to the configuration. 193 * 194 * @param array $data The input array containing settings data to be saved. 195 * The data is sanitized before being saved into the configuration. 196 * 197 * @return void This method does not return a value. 198 */ 151 199 public function saveSettings(array $data): void { 152 200 if (!current_user_can('manage_options')) { … … 156 204 check_admin_referer('cryptX'); 157 205 158 $sanitized = $this->sanitizeSettings($data); 159 206 // Handle reset button 160 207 if (!empty($_POST['cryptX_var_reset'])) { 161 208 $this->config->reset(); 209 add_settings_error( 210 'cryptx_messages', 211 'settings_reset', 212 __('Presentation settings have been reset to defaults.', 'cryptx'), 213 'updated' 214 ); 162 215 return; 163 216 } 164 217 218 $sanitized = $this->sanitizeSettings($data); 165 219 $this->config->update($sanitized); 166 220 … … 173 227 } 174 228 229 /** 230 * Sanitizes an array of settings data to ensure secure and valid formatting. 231 * 232 * @param array $data The input array containing settings data to be sanitized. 233 * Expected keys include CSS settings, text replacements, link text options, 234 * and font settings, each of which is sanitized according to its respective type. 235 * 236 * @return array The sanitized array with cleaned or validated values for all specified settings. 237 */ 175 238 private function sanitizeSettings(array $data): array { 176 239 $sanitized = []; … … 180 243 $sanitized['css_class'] = sanitize_html_class($data['css_class'] ?? ''); 181 244 182 // Text replacements 183 $sanitized['at'] = sanitize_text_field($data['at'] ?? ' [at] ');184 $sanitized['dot'] = sanitize_text_field($data['dot'] ?? ' [dot] ');245 // Text replacements - preserve whitespace for 'at' and 'dot' fields 246 $sanitized['at'] = wp_kses_post($data['at'] ?? ' [at] '); 247 $sanitized['dot'] = wp_kses_post($data['dot'] ?? ' [dot] '); 185 248 186 249 // Link text options … … 199 262 return $sanitized; 200 263 } 264 201 265 } -
cryptx/trunk/classes/Config.php
r3323475 r3339444 33 33 'whiteList' => 'jpeg,jpg,png,gif', 34 34 'disable_rss' => 1, // Disable CryptX in RSS feeds by default 35 'encryption_mode' => 'secure', // Changed to 'secure' by default 36 'encryption_password' => null, // Will be auto-generated if null 37 'use_secure_encryption' => 1, // Enable secure encryption by default 38 ]; 39 40 // Define the actual widget filters that will be used when widget_text is enabled 41 private const WIDGET_FILTERS = [ 42 'widget_text', // Legacy text widget (pre-4.9) 43 'widget_text_content', // Modern text widget (4.9+) 44 'widget_custom_html_content' // Custom HTML widget (4.8.1+) 35 45 ]; 36 46 … … 99 109 public function getVersion(): ?string { 100 110 return $this->options['version']; 111 } 112 113 /** 114 * Get the actual widget filters to be applied 115 * 116 * @return array 117 */ 118 public function getWidgetFilters(): array { 119 return self::WIDGET_FILTERS; 101 120 } 102 121 … … 123 142 // Convert checkbox values to integers 124 143 foreach (['the_content', 'the_meta_key', 'the_excerpt', 'comment_text', 125 'widget_text', 'autolink', 'metaBox', 'disable_rss' ] as $key) {144 'widget_text', 'autolink', 'metaBox', 'disable_rss', 'use_secure_encryption'] as $key) { 126 145 if (isset($newOptions[$key])) { 127 146 $newOptions[$key] = (int)$newOptions[$key]; … … 165 184 $this->save(); 166 185 } 186 187 /** 188 * Retrieves the encryption mode configured in the options. 189 * 190 * @return string Returns the encryption mode as a string. Defaults to 'secure' if not set. 191 */ 192 public function getEncryptionMode(): string 193 { 194 return $this->options['encryption_mode'] ?? 'secure'; 195 } 196 197 /** 198 * Checks if secure encryption is enabled in the options. 199 * 200 * @return bool Returns true if secure encryption is enabled, false otherwise. 201 */ 202 public function isSecureEncryptionEnabled(): bool 203 { 204 return (bool) ($this->options['use_secure_encryption'] ?? true); 205 } 206 207 /** 208 * Retrieves the encryption password configured in the options or generates a secure password if not set. 209 * 210 * @return string Returns the encryption password as a string. 211 */ 212 public function getEncryptionPassword(): string 213 { 214 if (empty($this->options['encryption_password'])) { 215 // Generate a secure password based on WordPress keys 216 $this->options['encryption_password'] = hash('sha256', 217 (defined('AUTH_KEY') ? AUTH_KEY : '') . 218 (defined('SECURE_AUTH_KEY') ? SECURE_AUTH_KEY : '') . 219 get_site_url() 220 ); 221 $this->save(); 222 } 223 return $this->options['encryption_password']; 224 } 167 225 } -
cryptx/trunk/classes/CryptX.php
r3335910 r3339444 25 25 { 26 26 $this->settingsTabs = new CryptXSettingsTabs($this); 27 $this->config = new Config( get_option('cryptX', []));27 $this->config = new Config(get_option('cryptX', [])); 28 28 self::$cryptXOptions = $this->loadCryptXOptionsWithDefaults(); 29 29 } … … 62 62 { 63 63 $this->checkAndUpdateVersion(); 64 $this->addUniversalWidgetFilters(); // Add this line 64 65 $this->initializePluginFilters(); 65 66 $this->registerCoreHooks(); … … 86 87 * @return void 87 88 */ 88 private function initializePluginFilters(): void 89 { 90 foreach (self::$cryptXOptions['filter'] as $filter) { 91 if (isset(self::$cryptXOptions[$filter]) && self::$cryptXOptions[$filter]) { 92 $this->addPluginFilters($filter); 89 public function initializePluginFilters(): void 90 { 91 if (empty($this->config)) { 92 return; 93 } 94 95 $activeFilters = $this->config->getActiveFilters(); 96 97 foreach ($activeFilters as $filter) { 98 if ($filter === 'widget_text') { 99 $this->addWidgetFilters(); 100 } else { 101 // Add autolink filters for non-widget filters if autolink is enabled 102 if ($this->config->isAutolinkEnabled()) { 103 $this->addAutoLinkFilters($filter, 10); 104 } 105 $this->addOtherFilters($filter); 93 106 } 94 107 } … … 236 249 } 237 250 238 // Process content 251 // Process content (inline the encryptAndLinkContent logic) 239 252 if (self::$cryptXOptions['autolink'] ?? false) { 240 253 $content = $this->addLinkToEmailAddresses($content, true); 241 254 } 242 255 243 $processedContent = $this->encryptAndLinkContent($content, true); 256 $content = $this->findEmailAddressesInContent($content, true); 257 $processedContent = $this->replaceEmailInContent($content, true); 244 258 245 259 // Reset options to defaults … … 263 277 } 264 278 265 private function processAndEncryptEmails(EmailProcessingConfig $config): string 266 { 267 $content = $this->encryptMailtoLinks($config); 268 return $this->encryptPlainEmails($content, $config); 269 } 270 271 private function encryptMailtoLinks(EmailProcessingConfig $config): ?string 272 { 273 $content = $config->getContent(); 274 if ($content === null) { 275 return null; 276 } 277 278 $postId = $config->getPostId() ?? $this->getCurrentPostId(); 279 280 if (!$this->isIdExcluded($postId) || $config->isShortcode()) { 281 return preg_replace_callback( 282 self::MAILTO_PATTERN, 283 [$this, 'encryptEmailAddress'], 284 $content 285 ); 286 } 287 288 return $content; 289 } 290 291 private function encryptPlainEmails(string $content, EmailProcessingConfig $config): string 292 { 293 $postId = $config->getPostId() ?? $this->getCurrentPostId(); 294 295 if ((!$this->isIdExcluded($postId) || $config->isShortcode()) && !empty($content)) { 296 return preg_replace_callback( 297 self::EMAIL_PATTERN, 298 [$this, 'encodeEmailToLinkText'], 299 $content 300 ); 301 } 302 303 return $content; 304 } 305 279 /** 280 * Retrieves the ID of the current post. 281 * 282 * @return int The current post ID if available, or -1 if no post object is present. 283 */ 306 284 private function getCurrentPostId(): int 307 285 { … … 362 340 363 341 /** 364 * Add plugin filters.365 *366 * This function adds the specified plugin filter if the 'autolink' key is present and its value is true in the global $cryptXOptions variable.367 * It also adds the 'autolink' function as a filter to the $filterName if the global $shortcode_tags variable is not empty.368 * Additionally, this function calls the addCommonFilters() and addOtherFilters() functions at specific points.369 *370 * @param string $filterName The name of the filter to add.371 *372 * @return void373 */374 private function addPluginFilters(string $filterName): void375 {376 global $shortcode_tags;377 378 if (array_key_exists('autolink', self::$cryptXOptions) && self::$cryptXOptions['autolink']) {379 $this->addAutoLinkFilters($filterName);380 if (!empty($shortcode_tags)) {381 $this->addAutoLinkFilters($filterName, 11);382 }383 }384 $this->addOtherFilters($filterName);385 }386 387 /**388 342 * Adds common filters to a given filter name. 389 343 * … … 412 366 private function addOtherFilters(string $filterName): void 413 367 { 414 add_filter($filterName, [$this, 'findEmailAddressesInContent'], 12); 415 add_filter($filterName, [$this, 'replaceEmailInContent'], 13); 368 // Check if this is a widget filter 369 $widgetFilters = $this->config->getWidgetFilters(); 370 $isWidgetFilter = in_array($filterName, $widgetFilters); 371 372 if ($isWidgetFilter) { 373 // Use higher priority for widget filters (after autolink at priority 10) 374 add_filter($filterName, [$this, 'findEmailAddressesInContent'], 15); 375 add_filter($filterName, [$this, 'replaceEmailInContent'], 16); 376 } else { 377 // Standard priorities for other filters 378 add_filter($filterName, [$this, 'findEmailAddressesInContent'], 12); 379 add_filter($filterName, [$this, 'replaceEmailInContent'], 13); 380 } 381 } 382 383 384 /** 385 * Adds and applies widget filters from the configuration. 386 * 387 * @return void 388 */ 389 private function addWidgetFilters(): void 390 { 391 $widgetFilters = $this->config->getWidgetFilters(); 392 393 foreach ($widgetFilters as $widgetFilter) { 394 $this->addAutoLinkFilters($widgetFilter, 10); 395 $this->addOtherFilters($widgetFilter); 396 } 416 397 } 417 398 … … 442 423 global $post; 443 424 444 if (self::$cryptXOptions['disable_rss'] && $this->isRssFeed()) return $content; 425 if (self::$cryptXOptions['disable_rss'] && $this->isRssFeed()) return $content; 426 427 // Check if current filter is a widget filter 428 $widgetFilters = $this->config->getWidgetFilters(); 429 $isWidgetContext = in_array(current_filter(), $widgetFilters); 445 430 446 431 $postId = (is_object($post)) ? $post->ID : -1; 447 if ((!$this->isIdExcluded($postId) || $isShortcode) && !empty($content)) { 432 433 // For widgets, always process; for other content, check exclusion rules 434 if (($isWidgetContext || !$this->isIdExcluded($postId) || $isShortcode) && !empty($content)) { 448 435 $content = $this->replaceEmailWithLinkText($content); 449 436 } … … 451 438 return $content; 452 439 } 440 453 441 454 442 /** … … 618 606 global $post; 619 607 620 if (self::$cryptXOptions['disable_rss'] && $this->isRssFeed()) return $content;608 if (self::$cryptXOptions['disable_rss'] && $this->isRssFeed()) return $content; 621 609 622 610 if ($content === null) { … … 624 612 } 625 613 614 // Check if current filter is a widget filter 615 $widgetFilters = $this->config->getWidgetFilters(); 616 $isWidgetContext = in_array(current_filter(), $widgetFilters); 617 626 618 $postId = (is_object($post)) ? $post->ID : -1; 627 619 $isIdExcluded = $this->isIdExcluded($postId); 628 620 629 // FIXED: Added 's' modifier to handle multiline HTML (like Elementor buttons)630 621 $mailtoRegex = '/<a\s+[^>]*href=(["\'])mailto:([^"\']+)\1[^>]*>(.*?)<\/a>/is'; 631 622 632 if ((!$isIdExcluded || $shortcode !== null)) { 633 $content = preg_replace_callback($mailtoRegex, [$this, 'encryptEmailAddressNew'], $content); 623 // For widgets, always process since there's no specific post context 624 // For other content, check exclusion rules 625 if ($isWidgetContext || !$isIdExcluded || $shortcode) { 626 // $content = preg_replace_callback($mailtoRegex, [$this, 'encryptEmailAddressNew'], $content); 627 $content = preg_replace_callback($mailtoRegex, [$this, 'encryptEmailAddressSecure'], $content); 634 628 } 635 629 636 630 return $content; 637 631 } 632 638 633 639 634 /** … … 659 654 660 655 $return = $originalValue; 661 656 662 657 // Apply JavaScript handler if enabled 663 658 if (!empty(self::$cryptXOptions['java'])) { … … 682 677 683 678 /** 684 * Encrypts an email address found in the search results and modifies it to safeguard against email harvesting. 685 * 686 * @param array $searchResults Array containing search result data, where: 687 * - Index 0 contains the full match. 688 * - Index 2 contains the email address. 689 * - Index 3 contains the link text for the email. 690 * @return string The encrypted or modified email link. 679 * Encrypts an email address within the provided search results and generates a secure or obfuscated link. 680 * If secure encryption is enabled, the function uses secure encryption. Otherwise, it falls back to legacy methods 681 * or antispambot obfuscation if JavaScript is not enabled. Additional CSS attributes can be added if specified. 682 * 683 * @param array $searchResults The array containing match results: 684 * - Index 0: The full match value (original string), 685 * - Index 2: The email address to encrypt, 686 * - Index 3: The link text for the email link. 687 * @return string Returns the modified string where the email address is encrypted or obfuscated based on the configuration. 691 688 */ 692 689 private function encryptEmailAddressNew(array $searchResults): string … … 708 705 // Apply JavaScript handler if enabled 709 706 if (!empty(self::$cryptXOptions['java'])) { 710 $javaHandler = "javascript:DeCryptX('" . $this->generateHashFromString($emailAddress) . "')"; 707 // Check if secure encryption is enabled and working 708 if ($this->config->isSecureEncryptionEnabled()) { 709 try { 710 // Use secure encryption - encrypt the full mailto URL 711 $password = $this->config->getEncryptionPassword(); 712 $mailtoUrl = 'mailto:' . $emailAddress; 713 $encryptedEmail = SecureEncryption::encrypt($mailtoUrl, $password); 714 715 $javaHandler = "javascript:secureDecryptAndNavigate('" . 716 $this->escapeJavaScript($encryptedEmail) . "', '" . 717 $this->escapeJavaScript($password) . "')"; 718 } catch (\Exception $e) { 719 // Fallback to legacy encryption if secure encryption fails 720 error_log('CryptX Secure Encryption failed: ' . $e->getMessage()); 721 $encryptedEmail = $this->generateHashFromString($emailAddress); 722 $javaHandler = "javascript:DeCryptX('" . $this->escapeJavaScript($encryptedEmail) . "')"; 723 } 724 } else { 725 // Use legacy encryption 726 $encryptedEmail = $this->generateHashFromString($emailAddress); 727 $javaHandler = "javascript:DeCryptX('" . $this->escapeJavaScript($encryptedEmail) . "')"; 728 } 729 711 730 $return = str_replace('mailto:' . $emailAddress, $javaHandler, $originalValue); 712 731 } else { 713 // Only apply antispambot if JavaScript is not enabled 714 $return = str_replace('mailto:' . $emailAddress, antispambot('mailto:' . $emailAddress), $return); 732 // Fallback to antispambot if JavaScript is not enabled 733 $return = str_replace('mailto:' . $emailAddress, 734 antispambot('mailto:' . $emailAddress), $return); 715 735 } 716 736 717 737 // Add CSS attributes if specified 718 738 if (!empty(self::$cryptXOptions['css_id'])) { 719 $return = preg_replace('/(<a\s+[^>]*)(>)/i', '$1 id="' . self::$cryptXOptions['css_id'] . '"$2', $return); 739 $return = preg_replace('/(<a\s+[^>]*)(>)/i', 740 '$1 id="' . self::$cryptXOptions['css_id'] . '"$2', $return); 720 741 } 721 742 722 743 if (!empty(self::$cryptXOptions['css_class'])) { 723 $return = preg_replace('/(<a\s+[^>]*)(>)/i', '$1 class="' . self::$cryptXOptions['css_class'] . '"$2', $return); 744 $return = preg_replace('/(<a\s+[^>]*)(>)/i', 745 '$1 class="' . self::$cryptXOptions['css_class'] . '"$2', $return); 724 746 } 725 747 … … 768 790 { 769 791 global $post; 792 793 // Check if current filter is a widget filter 794 $widgetFilters = $this->config->getWidgetFilters(); 795 $isWidgetContext = in_array(current_filter(), $widgetFilters); 796 770 797 $postID = is_object($post) ? $post->ID : -1; 771 798 772 if ($this->isIdExcluded($postID) && !$shortcode) { 799 // For widgets, always process; for other content, check exclusion rules 800 if (!$isWidgetContext && $this->isIdExcluded($postID) && !$shortcode) { 773 801 return $content; 774 802 } 775 803 776 $emailPattern = "[_a-zA-Z0-9-+]+(\ .[_a-zA-Z0-9-+]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(\.[a-zA-Z]{2,})";804 $emailPattern = "[_a-zA-Z0-9-+]+(\\.[_a-zA-Z0-9-+]+)*@[a-zA-Z0-9-]+(\\.[a-zA-Z0-9-]+)*(\\.[a-zA-Z]{2,})"; 777 805 $linkPattern = "<a href=\"mailto:\\2\">\\2</a>"; 778 806 $src = [ 779 "/([\ s])($emailPattern)/si",807 "/([\\s])($emailPattern)/si", 780 808 "/(>)($emailPattern)(<)/si", 781 "/(\ ()($emailPattern)(\))/si",782 "/(>)($emailPattern)([\ s])/si",783 "/([\ s])($emailPattern)(<)/si",809 "/(\\()($emailPattern)(\\))/si", 810 "/(>)($emailPattern)([\\s])/si", 811 "/([\\s])($emailPattern)(<)/si", 784 812 "/^($emailPattern)/si", 785 813 "/(<a[^>]*>)<a[^>]*>/", 786 "/(<\ /A>)<\/A>/i"814 "/(<\\/A>)<\\/A>/i" 787 815 ]; 788 816 $tar = [ … … 1114 1142 } 1115 1143 1144 /** 1145 * Converts an associative array into an argument string. 1146 * 1147 * @param array $args An optional associative array where keys represent argument names and values represent argument values. 1148 * @return string A formatted string of arguments where each key-value pair is encoded and concatenated. 1149 */ 1116 1150 public function convertArrayToArgumentString(array $args = []): string 1117 1151 { … … 1140 1174 * Adds plugin action links to the WordPress plugin row 1141 1175 * 1142 * @param array $links Existing plugin row links1143 * @param string $file Plugin file path1176 * @param array $links Existing plugin row links 1177 * @param string $file Plugin file path 1144 1178 * @return array Modified plugin row links 1145 1179 */ … … 1159 1193 1160 1194 /** 1161 * Creates the settings link for the plugin 1195 * Creates and returns a settings link for the options page. 1196 * 1197 * @return string The HTML link to the settings page. 1162 1198 */ 1163 1199 private function create_settings_link(): string … … 1171 1207 1172 1208 /** 1173 * Creates the donation link for the plugin 1209 * Creates and returns a donation link in HTML format. 1210 * 1211 * @return string The HTML string for the donation link. 1174 1212 */ 1175 1213 private function create_donation_link(): string … … 1181 1219 ); 1182 1220 } 1221 1222 /** 1223 * Adds a universal filter for all widget types by hooking into the widget display process. 1224 * 1225 * @return void 1226 */ 1227 private function addUniversalWidgetFilters(): void 1228 { 1229 // Hook into the widget display process to catch all widget types 1230 add_filter('widget_display_callback', [$this, 'processWidgetContent'], 10, 3); 1231 } 1232 1233 /** 1234 * Processes the widget content to detect and modify email addresses. 1235 * 1236 * @param array $instance The current widget instance settings. 1237 * @param object $widget The widget object being processed. 1238 * @param array $args Additional arguments passed by the widget function. 1239 * 1240 * @return array The modified widget instance with updated content. 1241 */ 1242 public function processWidgetContent($instance, $widget, $args) 1243 { 1244 // Only process if widget_text option is enabled 1245 if (!(self::$cryptXOptions['widget_text'] ?? false)) { 1246 return $instance; 1247 } 1248 1249 // Check if instance has text content (traditional text widgets) 1250 if (isset($instance['text']) && stripos($instance['text'], '@') !== false) { 1251 $instance['text'] = $this->addLinkToEmailAddresses($instance['text']); 1252 $instance['text'] = $this->findEmailAddressesInContent($instance['text']); 1253 $instance['text'] = $this->replaceEmailInContent($instance['text']); 1254 } 1255 1256 // Check if instance has content field (block widgets) 1257 if (isset($instance['content']) && stripos($instance['content'], '@') !== false) { 1258 $instance['content'] = $this->addLinkToEmailAddresses($instance['content']); 1259 $instance['content'] = $this->findEmailAddressesInContent($instance['content']); 1260 $instance['content'] = $this->replaceEmailInContent($instance['content']); 1261 } 1262 1263 return $instance; 1264 } 1265 1266 /** 1267 * Generates hash using secure or legacy encryption based on settings 1268 * 1269 * @param string $inputString 1270 * @return string 1271 */ 1272 private function generateSecureHashFromString(string $inputString): string 1273 { 1274 if ($this->config->isSecureEncryptionEnabled()) { 1275 try { 1276 $password = $this->config->getEncryptionPassword(); 1277 return SecureEncryption::encrypt($inputString, $password); 1278 } catch (\Exception $e) { 1279 error_log('CryptX Secure Encryption failed: ' . $e->getMessage()); 1280 // Fallback to legacy encryption 1281 return $this->generateHashFromString($inputString); 1282 } 1283 } 1284 1285 return $this->generateHashFromString($inputString); 1286 } 1287 1288 /** 1289 * Enhanced email encryption with security validation 1290 * 1291 * @param array $searchResults 1292 * @return string 1293 */ 1294 private function encryptEmailAddressSecure(array $searchResults): string 1295 { 1296 $originalValue = $searchResults[0]; // Full match 1297 $emailAddress = $searchResults[2]; // Email address 1298 $linkText = $searchResults[3]; // Link text 1299 1300 if (strpos($emailAddress, '@') === self::NOT_FOUND) { 1301 return $originalValue; 1302 } 1303 1304 if (str_starts_with($emailAddress, self::SUBJECT_IDENTIFIER)) { 1305 return $originalValue; 1306 } 1307 1308 $return = $originalValue; 1309 1310 // Apply JavaScript handler if enabled 1311 if (!empty(self::$cryptXOptions['java'])) { 1312 $encryptionMode = $this->config->getEncryptionMode(); 1313 1314 // Determine which encryption method to use 1315 if ($encryptionMode === 'secure' && 1316 $this->config->isSecureEncryptionEnabled() && 1317 class_exists('CryptX\SecureEncryption')) { 1318 1319 // Use modern AES-256-GCM encryption 1320 try { 1321 $password = $this->config->getEncryptionPassword(); 1322 $mailtoUrl = 'mailto:' . $emailAddress; 1323 $encryptedEmail = SecureEncryption::encrypt($mailtoUrl, $password); 1324 1325 $javaHandler = "javascript:secureDecryptAndNavigate('" . 1326 $this->escapeJavaScript($encryptedEmail) . "', '" . 1327 $this->escapeJavaScript($password) . "')"; 1328 } catch (\Exception $e) { 1329 // Fallback to legacy if secure encryption fails 1330 error_log('CryptX Secure Encryption failed, falling back to legacy: ' . $e->getMessage()); 1331 $encryptedEmail = $this->generateHashFromString($emailAddress); 1332 $javaHandler = "javascript:DeCryptX('" . $this->escapeJavaScript($encryptedEmail) . "')"; 1333 } 1334 } else { 1335 // Use legacy encryption (original algorithm) 1336 $encryptedEmail = $this->generateHashFromString($emailAddress); 1337 $javaHandler = "javascript:DeCryptX('" . $this->escapeJavaScript($encryptedEmail) . "')"; 1338 } 1339 1340 $return = str_replace('mailto:' . $emailAddress, $javaHandler, $originalValue); 1341 } else { 1342 // Fallback to antispambot if JavaScript is not enabled 1343 $return = str_replace('mailto:' . $emailAddress, 1344 antispambot('mailto:' . $emailAddress), $return); 1345 } 1346 1347 // Add CSS attributes if specified 1348 if (!empty(self::$cryptXOptions['css_id'])) { 1349 $return = preg_replace('/(<a\s+[^>]*)(>)/i', 1350 '$1 id="' . self::$cryptXOptions['css_id'] . '"$2', $return); 1351 } 1352 1353 if (!empty(self::$cryptXOptions['css_class'])) { 1354 $return = preg_replace('/(<a\s+[^>]*)(>)/i', 1355 '$1 class="' . self::$cryptXOptions['css_class'] . '"$2', $return); 1356 } 1357 1358 return $return; 1359 } 1360 1361 /** 1362 * Escapes string for safe JavaScript usage 1363 * 1364 * @param string $string 1365 * @return string 1366 */ 1367 private function escapeJavaScript(string $string): string 1368 { 1369 return str_replace( 1370 ['\\', "'", '"', "\n", "\r", "\t"], 1371 ['\\\\', "\\'", '\\"', '\\n', '\\r', '\\t'], 1372 $string 1373 ); 1374 } 1375 1376 /** 1377 * Secure URL validation 1378 * 1379 * @param string $url 1380 * @return bool 1381 */ 1382 private function isValidUrl(string $url): bool 1383 { 1384 return SecureEncryption::validateUrl($url); 1385 } 1386 1183 1387 } -
cryptx/trunk/classes/CryptXSettingsTabs.php
r3323475 r3339444 79 79 ); 80 80 81 // Enqueue WordPress color picker assets 81 82 wp_enqueue_style('wp-color-picker'); 82 83 // Enqueue JavaScript files 84 wp_enqueue_script( 85 'cryptx-admin-js', 86 CRYPTX_DIR_URL . 'js/cryptx-admin.min.js', 87 ['jquery', 'wp-color-picker'], 88 CRYPTX_VERSION, 89 true 90 ); 91 83 wp_enqueue_script('wp-color-picker'); 84 85 // Enqueue media uploader 92 86 wp_enqueue_media(); 93 87 } … … 133 127 $saveOptions = DataSanitizer::sanitize($_POST['cryptX_var']); 134 128 129 // Handle reset for any tab 135 130 if (isset($_POST['cryptX_var_reset'])) { 136 $saveOptions = $this->cryptX->getCryptXOptionsDefaults(); 131 $this->cryptX->getCryptXOptionsDefaults(); 132 $this->cryptX->getConfig()->reset(); 133 $this->displayResetMessage(); 134 return; 137 135 } 138 136 … … 145 143 } 146 144 } 147 148 145 149 146 /** … … 173 170 'cryptx_messages', 174 171 'cryptx_message', 175 __('Settings saved.'), 172 __('Settings saved.', 'cryptx'), 173 'updated' 174 ); 175 } 176 177 /** 178 * Display reset message 179 */ 180 private function displayResetMessage(): void 181 { 182 add_settings_error( 183 'cryptx_messages', 184 'cryptx_reset', 185 __('Settings have been reset to defaults.', 'cryptx'), 176 186 'updated' 177 187 ); … … 294 304 295 305 // Handle form submission if needed 296 if (isset($_POST['cryptX_save_general_settings']) ) {297 if (!empty($_POST['cryptX_var']) ) {298 $generalTab->saveSettings($_POST['cryptX_var'] );306 if (isset($_POST['cryptX_save_general_settings']) || isset($_POST['cryptX_var_reset'])) { 307 if (!empty($_POST['cryptX_var']) || isset($_POST['cryptX_var_reset'])) { 308 $generalTab->saveSettings($_POST['cryptX_var'] ?? []); 299 309 } 300 310 } … … 328 338 329 339 // Handle form submission if needed 330 if (isset($_POST['cryptX_save_presentation_settings']) ) {331 if (!empty($_POST['cryptX_var']) ) {332 $presentationTab->saveSettings($_POST['cryptX_var'] );340 if (isset($_POST['cryptX_save_presentation_settings']) || isset($_POST['cryptX_var_reset'])) { 341 if (!empty($_POST['cryptX_var']) || isset($_POST['cryptX_var_reset'])) { 342 $presentationTab->saveSettings($_POST['cryptX_var'] ?? []); 333 343 } 334 344 } … … 375 385 } 376 386 } 377 378 379 /**380 * Parse and render changelog content from readme.txt381 */382 private function renderChangelogContent(): void383 {384 $readmePath = CRYPTX_DIR_PATH . '/readme.txt';385 if (!file_exists($readmePath)) {386 return;387 }388 389 $fileContents = file_get_contents($readmePath);390 if ($fileContents === false) {391 return;392 }393 394 $changelogs = $this->parseChangelog($fileContents);395 foreach ($changelogs as $log) {396 echo wp_kses_post("<dl>" . implode("", $log) . "</dl>");397 }398 }399 400 /**401 * Parse changelog content from readme.txt402 *403 * @param string $content404 * @return array405 */406 private function parseChangelog(string $content): array407 {408 $content = str_replace(["\r\n", "\r"], "\n", $content);409 $content = trim($content);410 411 // Split into sections412 $sections = $this->parseSections($content);413 if (!isset($sections['changelog'])) {414 return [];415 }416 417 // Parse changelog entries418 return $this->parseChangelogEntries($sections['changelog']['content']);419 }420 421 /**422 * Parse sections from readme content423 *424 * @param string $content425 * @return array426 */427 private function parseSections(string $content): array428 {429 $_sections = preg_split('/^[\s]*==[\s]*(.+?)[\s]*==/m', $content, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);430 $sections = [];431 432 for ($i = 1; $i <= count($_sections); $i += 2) {433 $title = $_sections[$i - 1];434 $sections[str_replace(' ', '_', strtolower($title))] = [435 'title' => $title,436 'content' => $_sections[$i]437 ];438 }439 440 return $sections;441 }442 443 /**444 * Parse changelog entries445 *446 * @param string $content447 * @return array448 */449 private function parseChangelogEntries(string $content): array450 {451 $_changelogs = preg_split('/^[\s]*=[\s]*(.+?)[\s]*=/m', $content, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);452 $changelogs = [];453 454 for ($i = 1; $i <= count($_changelogs); $i += 2) {455 $version = $_changelogs[$i - 1];456 $content = ltrim($_changelogs[$i], "\n");457 $content = str_replace("* ", "<li>", $content);458 $content = str_replace("\n", " </li>\n", $content);459 460 $changelogs[] = [461 'version' => "<dt>" . esc_html($version) . "</dt>",462 'content' => "<dd><ul>" . wp_kses_post($content) . "</ul></dd>"463 ];464 }465 466 return $changelogs;467 }468 387 } -
cryptx/trunk/cryptx.php
r3335910 r3339444 1 1 <?php 2 /* 3 * Plugin Name: CryptX4 * Plugin URI: http://weber-nrw.de/wordpress/cryptx/5 * Description: No more SPAM by spiders scanning you site for email addresses. With CryptX you can hide all your email addresses, with and without a mailto-link, by converting them using javascript or UNICODE.6 * Version: 3.5.22 /** 3 * Plugin Name: CryptX 4 * Plugin URI: https://wordpress.org/plugins/cryptx/ 5 * Description: CryptX encrypts email addresses in your posts, pages, comments, and text widgets to protect them from spam bots while keeping them readable for your visitors. 6 * Version: 4.0.0 7 7 * Requires at least: 6.7 8 * Author: Ralf Weber 9 * Author URI: http://weber-nrw.de/ 8 * Tested up to: 6.8 9 * Requires PHP: 8.1 10 * Author: Ralf Weber 11 * Author URI: https://weber-nrw.de/ 10 12 * License: GPL v2 or later 11 13 * License URI: https://www.gnu.org/licenses/gpl-2.0.html 12 * Text Domain: cryptx 13 */ 14 * Text Domain: cryptx 15 * Domain Path: /languages 16 * Network: false 17 * Update URI: https://wordpress.org/plugins/cryptx/ 18 * 19 * CryptX is free software: you can redistribute it and/or modify 20 * it under the terms of the GNU General Public License as published by 21 * the Free Software Foundation, either version 2 of the License, or 22 * any later version. 23 * 24 * CryptX is distributed in the hope that it will be useful, 25 * but WITHOUT ANY WARRANTY; without even the implied warranty of 26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 27 * GNU General Public License for more details. 28 * 29 * You should have received a copy of the GNU General Public License 30 * along with CryptX. If not, see https://www.gnu.org/licenses/gpl-2.0.html. 31 * 32 * @package CryptX 33 * @since 1.0.0 34 */ 14 35 15 //avoid direct calls to this file, because now WP core and framework has been used 16 if ( ! function_exists( 'add_action' ) ) { 17 header( 'Status: 403 Forbidden' ); 18 header( 'HTTP/1.1 403 Forbidden' ); 19 exit(); 36 // Prevent direct access 37 if (!defined('ABSPATH')) { 38 exit; 20 39 } 21 40 22 /** @const CryptX version */ 23 define( 'CRYPTX_VERSION', "3.5.2" ); 24 define( 'CRYPTX_BASENAME', plugin_basename( __FILE__ ) ); 25 define( 'CRYPTX_BASEFOLDER', plugin_basename( dirname( __FILE__ ) ) ); 26 define( 'CRYPTX_DIR_URL', rtrim( plugin_dir_url( __FILE__ ), "/" ) . "/" ); 27 define( 'CRYPTX_DIR_PATH', plugin_dir_path( __FILE__ ) ); 28 define( 'CRYPTX_FILENAME', str_replace( CRYPTX_BASEFOLDER . '/', '', plugin_basename( __FILE__ ) ) ); 41 // Plugin constants 42 define('CRYPTX_VERSION', '4.0.0'); 43 define('CRYPTX_PLUGIN_FILE', __FILE__); 44 define('CRYPTX_PLUGIN_BASENAME', plugin_basename(__FILE__)); 45 define('CRYPTX_BASENAME', plugin_basename(__FILE__)); // Add this missing constant 46 define('CRYPTX_DIR_PATH', plugin_dir_path(__FILE__)); 47 define('CRYPTX_DIR_URL', plugin_dir_url(__FILE__)); 48 define('CRYPTX_BASEFOLDER', dirname(CRYPTX_PLUGIN_BASENAME)); 29 49 30 spl_autoload_register(function ($class_name) { 31 // Handle classes in the CryptX namespace 32 if (strpos($class_name, 'CryptX\\') === 0) { 33 // Convert namespace separators to directory separators 34 $file_path = str_replace('\\', DIRECTORY_SEPARATOR, $class_name); 35 $file_path = str_replace('CryptX' . DIRECTORY_SEPARATOR, '', $file_path); 50 // Minimum requirements check 51 if (version_compare(PHP_VERSION, '8.1.0', '<')) { 52 add_action('admin_notices', function() { 53 echo '<div class="notice notice-error"><p>'; 54 printf( 55 /* translators: %1$s: Required PHP version, %2$s: Current PHP version */ 56 __('CryptX requires PHP version %1$s or higher. You are running version %2$s. Please update PHP.', 'cryptx'), 57 '8.1.0', 58 PHP_VERSION 59 ); 60 echo '</p></div>'; 61 }); 62 return; 63 } 36 64 37 // Construct the full file path 38 $file = CRYPTX_DIR_PATH . 'classes' . DIRECTORY_SEPARATOR . $file_path . '.php'; 65 // WordPress version check 66 global $wp_version; 67 if (version_compare($wp_version, '6.7', '<')) { 68 add_action('admin_notices', function() { 69 echo '<div class="notice notice-error"><p>'; 70 printf( 71 /* translators: %1$s: Required WordPress version, %2$s: Current WordPress version */ 72 __('CryptX requires WordPress version %1$s or higher. You are running version %2$s. Please update WordPress.', 'cryptx'), 73 '5.0', 74 $GLOBALS['wp_version'] 75 ); 76 echo '</p></div>'; 77 }); 78 return; 79 } 39 80 40 if (file_exists($file)) { 41 require_once $file; 42 } 81 // Autoloader for plugin classes 82 spl_autoload_register(function ($class) { 83 // Check if the class belongs to our namespace 84 if (strpos($class, 'CryptX\\') !== 0) { 85 return; 86 } 87 88 // Remove namespace prefix 89 $class = substr($class, 7); 90 91 // Convert namespace separators to directory separators 92 $class = str_replace('\\', DIRECTORY_SEPARATOR, $class); 93 94 // Build the full path 95 $file = CRYPTX_DIR_PATH . 'classes' . DIRECTORY_SEPARATOR . $class . '.php'; 96 97 // Include the file if it exists 98 if (file_exists($file)) { 99 require_once $file; 43 100 } 44 101 }); 45 102 103 // Initialize the plugin 104 add_action('plugins_loaded', function() { 105 // Check if all required classes can be loaded 106 $requiredClasses = [ 107 'CryptX\\CryptX', 108 'CryptX\\Config', 109 'CryptX\\CryptXSettingsTabs', 110 'CryptX\\SecureEncryption', 111 'CryptX\\Admin\\ChangelogSettingsTab', 112 'CryptX\\Admin\\GeneralSettingsTab', 113 'CryptX\\Admin\\PresentationSettingsTab', 114 'CryptX\\Util\\DataSanitizer', 115 ]; 46 116 47 //require_once( CRYPTX_DIR_PATH . 'classes/CryptX.php' ); 48 //require_once( CRYPTX_DIR_PATH . 'include/admin_option_page.php' ); 117 $missingClasses = []; 118 foreach ($requiredClasses as $class) { 119 if (!class_exists($class)) { 120 $missingClasses[] = $class; 121 } 122 } 49 123 50 $CryptX_instance = CryptX\CryptX::get_instance(); 51 $CryptX_instance->startCryptX(); 124 if (!empty($missingClasses)) { 125 add_action('admin_notices', function() use ($missingClasses) { 126 echo '<div class="notice notice-error"><p>'; 127 echo esc_html__('CryptX: Missing required classes: ', 'cryptx') . implode(', ', $missingClasses); 128 echo '</p></div>'; 129 }); 130 return; 131 } 52 132 53 /** 54 * Encrypts the given content using the CryptX WordPress plugin. 55 * 56 * @param string|null $content The content to encrypt. 57 * @param array|null $args The optional arguments for the encryption. Default is an empty array. 58 * 59 * @return string The encrypted content wrapped in the '[cryptx]' shortcode. 60 */ 61 function encryptx( ?string $content, ?array $args = [] ): string { 62 $CryptX_instance = Cryptx\CryptX::get_instance(); 63 return do_shortcode( '[cryptx'. $CryptX_instance->convertArrayToArgumentString( $args ).']' . $content . '[/cryptx]' ); 64 } 133 // Initialize the main plugin class 134 try { 135 $cryptx_instance = CryptX\CryptX::get_instance(); 136 $cryptx_instance->startCryptX(); 137 } catch (Exception $e) { 138 add_action('admin_notices', function() use ($e) { 139 echo '<div class="notice notice-error"><p>'; 140 echo esc_html__('CryptX initialization failed: ', 'cryptx') . esc_html($e->getMessage()); 141 echo '</p></div>'; 142 }); 143 } 144 }); 145 146 // Remove the strict activation requirements - let the plugin handle fallbacks 147 register_activation_hook(__FILE__, function() { 148 // Just flush rewrite rules 149 flush_rewrite_rules(); 150 }); 151 152 // Plugin deactivation hook 153 register_deactivation_hook(__FILE__, function() { 154 // Clean up any transients or cached data 155 global $wpdb; 156 $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_cryptx_%'"); 157 $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_timeout_cryptx_%'"); 158 }); 159 160 // Add plugin action links - updated to match the settings page slug 161 add_filter('plugin_action_links_' . plugin_basename(__FILE__), function($links) { 162 $settings_link = '<a href="' . admin_url('options-general.php?page=cryptx') . '">' . 163 esc_html__('Settings', 'cryptx') . '</a>'; 164 array_unshift($links, $settings_link); 165 return $links; 166 }); -
cryptx/trunk/js/cryptx.js
r3323475 r3339444 1 const UPPER_LIMIT = 8364; 2 const DEFAULT_VALUE = 128; 3 4 /** 5 * Decrypts an encrypted string using a specific encryption algorithm. 6 * 7 * @param {string} encryptedString - The encrypted string to be decrypted. 8 * @returns {string} The decrypted string. 1 /** 2 * Secure CryptX Library - Fixed for backward compatibility 3 */ 4 5 // Configuration constants 6 const CONFIG = { 7 ALLOWED_PROTOCOLS: ['http:', 'https:', 'mailto:'], 8 MAX_URL_LENGTH: 2048, 9 ENCRYPTION_KEY_SIZE: 32, 10 IV_SIZE: 16 11 }; 12 13 /** 14 * Utility functions for secure operations 15 */ 16 class SecureUtils { 17 static getSecureRandomBytes(length) { 18 if (typeof crypto === 'undefined' || !crypto.getRandomValues) { 19 throw new Error('Secure random number generation not available'); 20 } 21 return crypto.getRandomValues(new Uint8Array(length)); 22 } 23 24 static arrayBufferToBase64(buffer) { 25 const bytes = new Uint8Array(buffer); 26 let binary = ''; 27 for (let i = 0; i < bytes.byteLength; i++) { 28 binary += String.fromCharCode(bytes[i]); 29 } 30 return btoa(binary); 31 } 32 33 static base64ToArrayBuffer(base64) { 34 const binary = atob(base64); 35 const buffer = new ArrayBuffer(binary.length); 36 const bytes = new Uint8Array(buffer); 37 for (let i = 0; i < binary.length; i++) { 38 bytes[i] = binary.charCodeAt(i); 39 } 40 return buffer; 41 } 42 43 static validateUrl(url) { 44 if (typeof url !== 'string' || url.length === 0) { 45 return null; 46 } 47 48 if (url.length > CONFIG.MAX_URL_LENGTH) { 49 console.error('URL exceeds maximum length'); 50 return null; 51 } 52 53 try { 54 const urlObj = new URL(url); 55 56 if (!CONFIG.ALLOWED_PROTOCOLS.includes(urlObj.protocol)) { 57 console.error('Protocol not allowed:', urlObj.protocol); 58 return null; 59 } 60 61 if (urlObj.protocol === 'mailto:') { 62 const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; 63 if (!emailRegex.test(urlObj.pathname)) { 64 console.error('Invalid email format in mailto URL'); 65 return null; 66 } 67 } 68 69 return url; 70 } catch (error) { 71 console.error('Invalid URL format:', error.message); 72 return null; 73 } 74 } 75 76 static escapeJavaScript(str) { 77 if (typeof str !== 'string') { 78 return ''; 79 } 80 return str.replace(/\\/g, '\\\\') 81 .replace(/'/g, "\\'") 82 .replace(/"/g, '\\"') 83 .replace(/\n/g, '\\n') 84 .replace(/\r/g, '\\r') 85 .replace(/\t/g, '\\t'); 86 } 87 } 88 89 /** 90 * Legacy encryption class - Fixed to match original PHP algorithm 91 */ 92 class LegacyEncryption { 93 /** 94 * Decrypts using the original CryptX algorithm (matches PHP version) 95 * @param {string} encryptedString 96 * @returns {string} 97 */ 98 static originalDecrypt(encryptedString) { 99 if (typeof encryptedString !== 'string' || encryptedString.length === 0) { 100 throw new Error('Invalid encrypted string'); 101 } 102 103 // Constants from original algorithm 104 const UPPER_LIMIT = 8364; 105 const DEFAULT_VALUE = 128; 106 107 let charCode = 0; 108 let decryptedString = "mailto:"; 109 let encryptionKey = 0; 110 111 try { 112 for (let i = 0; i < encryptedString.length; i += 2) { 113 if (i + 1 >= encryptedString.length) { 114 break; 115 } 116 117 // Get the salt (encryption key) from current position 118 encryptionKey = parseInt(encryptedString.charAt(i), 10); 119 120 // Handle invalid salt values 121 if (isNaN(encryptionKey)) { 122 encryptionKey = 0; 123 } 124 125 // Get the character code from next position 126 charCode = encryptedString.charCodeAt(i + 1); 127 128 // Apply the same logic as original 129 if (charCode >= UPPER_LIMIT) { 130 charCode = DEFAULT_VALUE; 131 } 132 133 // Decrypt by subtracting the salt 134 const decryptedCharCode = charCode - encryptionKey; 135 136 // Validate the result 137 if (decryptedCharCode < 0 || decryptedCharCode > 1114111) { 138 throw new Error('Invalid character code during decryption'); 139 } 140 141 decryptedString += String.fromCharCode(decryptedCharCode); 142 } 143 144 return decryptedString; 145 } catch (error) { 146 throw new Error('Original decryption failed: ' + error.message); 147 } 148 } 149 150 /** 151 * Encrypts using the original CryptX algorithm (matches PHP version) 152 * @param {string} inputString 153 * @returns {string} 154 */ 155 static originalEncrypt(inputString) { 156 if (typeof inputString !== 'string' || inputString.length === 0) { 157 throw new Error('Invalid input string'); 158 } 159 160 // Remove "mailto:" prefix if present for encryption 161 const cleanInput = inputString.replace(/^mailto:/, ''); 162 let crypt = ''; 163 164 // ASCII values blacklist (from PHP constant) 165 const ASCII_VALUES_BLACKLIST = ['32', '34', '39', '60', '62', '63', '92', '94', '96', '127']; 166 167 try { 168 for (let i = 0; i < cleanInput.length; i++) { 169 let salt, asciiValue; 170 let attempts = 0; 171 const maxAttempts = 20; // Prevent infinite loops 172 173 do { 174 if (attempts >= maxAttempts) { 175 // Fallback to a safe salt if we can't find a valid one 176 salt = 1; 177 asciiValue = cleanInput.charCodeAt(i) + salt; 178 break; 179 } 180 181 // Generate random number between 0 and 3 (matching PHP rand(0,3)) 182 const randomValues = SecureUtils.getSecureRandomBytes(1); 183 salt = randomValues[0] % 4; 184 185 // Get ASCII value and add salt 186 asciiValue = cleanInput.charCodeAt(i) + salt; 187 188 // Check if value exceeds limit (matching PHP logic) 189 if (asciiValue >= 8364) { 190 asciiValue = 128; 191 } 192 193 attempts++; 194 } while (ASCII_VALUES_BLACKLIST.includes(asciiValue.toString()) && attempts < maxAttempts); 195 196 // Append salt and character to result 197 crypt += salt.toString() + String.fromCharCode(asciiValue); 198 } 199 200 return crypt; 201 } catch (error) { 202 throw new Error('Original encryption failed: ' + error.message); 203 } 204 } 205 } 206 207 /** 208 * Modern encryption class using Web Crypto API - PHP Compatible 209 */ 210 class SecureEncryption { 211 static async deriveKey(password, salt) { 212 const encoder = new TextEncoder(); 213 const keyMaterial = await crypto.subtle.importKey( 214 'raw', 215 encoder.encode(password), 216 { name: 'PBKDF2' }, 217 false, 218 ['deriveKey'] 219 ); 220 221 return crypto.subtle.deriveKey( 222 { 223 name: 'PBKDF2', 224 salt: salt, 225 iterations: 100000, 226 hash: 'SHA-256' 227 }, 228 keyMaterial, 229 { name: 'AES-GCM', length: 256 }, 230 false, 231 ['encrypt', 'decrypt'] 232 ); 233 } 234 235 static async encrypt(plaintext, password) { 236 if (typeof plaintext !== 'string' || typeof password !== 'string') { 237 throw new Error('Both plaintext and password must be strings'); 238 } 239 240 const encoder = new TextEncoder(); 241 const salt = SecureUtils.getSecureRandomBytes(16); 242 const iv = SecureUtils.getSecureRandomBytes(CONFIG.IV_SIZE); 243 244 const key = await this.deriveKey(password, salt); 245 246 const encrypted = await crypto.subtle.encrypt( 247 { name: 'AES-GCM', iv: iv }, 248 key, 249 encoder.encode(plaintext) 250 ); 251 252 // Match PHP format: salt(16) + iv(16) + encrypted_data + tag(16) 253 const encryptedArray = new Uint8Array(encrypted); 254 const encryptedData = encryptedArray.slice(0, -16); // Remove tag from encrypted data 255 const tag = encryptedArray.slice(-16); // Get the tag 256 257 const combined = new Uint8Array(salt.length + iv.length + encryptedData.length + tag.length); 258 combined.set(salt, 0); 259 combined.set(iv, salt.length); 260 combined.set(encryptedData, salt.length + iv.length); 261 combined.set(tag, salt.length + iv.length + encryptedData.length); 262 263 return SecureUtils.arrayBufferToBase64(combined.buffer); 264 } 265 266 static async decrypt(encryptedData, password) { 267 if (typeof encryptedData !== 'string' || typeof password !== 'string') { 268 throw new Error('Both encryptedData and password must be strings'); 269 } 270 271 try { 272 const combined = SecureUtils.base64ToArrayBuffer(encryptedData); 273 const totalLength = combined.byteLength; 274 275 // PHP format: salt(16) + iv(16) + encrypted_data + tag(16) 276 const saltLength = 16; 277 const ivLength = 16; 278 const tagLength = 16; 279 const encryptedDataLength = totalLength - saltLength - ivLength - tagLength; 280 281 if (totalLength < saltLength + ivLength + tagLength) { 282 throw new Error('Encrypted data too short'); 283 } 284 285 const salt = combined.slice(0, saltLength); 286 const iv = combined.slice(saltLength, saltLength + ivLength); 287 const encryptedDataOnly = combined.slice(saltLength + ivLength, saltLength + ivLength + encryptedDataLength); 288 const tag = combined.slice(-tagLength); // Last 16 bytes 289 290 const key = await this.deriveKey(password, new Uint8Array(salt)); 291 292 // Reconstruct the encrypted data with tag for Web Crypto API 293 const encryptedWithTag = new Uint8Array(encryptedDataOnly.byteLength + tag.byteLength); 294 encryptedWithTag.set(new Uint8Array(encryptedDataOnly), 0); 295 encryptedWithTag.set(new Uint8Array(tag), encryptedDataOnly.byteLength); 296 297 const decrypted = await crypto.subtle.decrypt( 298 { name: 'AES-GCM', iv: new Uint8Array(iv) }, 299 key, 300 encryptedWithTag 301 ); 302 303 const decoder = new TextDecoder(); 304 return decoder.decode(decrypted); 305 } catch (error) { 306 throw new Error('Decryption failed: ' + error.message); 307 } 308 } 309 } 310 311 /** 312 * Main CryptX functions with backward compatibility 313 */ 314 315 /** 316 * Securely decrypts and validates a URL before navigation 317 * @param {string} encryptedUrl 318 * @param {string} password 319 */ 320 async function secureDecryptAndNavigate(encryptedUrl, password = 'default_key') { 321 if (typeof encryptedUrl !== 'string' || encryptedUrl.length === 0) { 322 console.error('Invalid encrypted URL provided'); 323 return; 324 } 325 326 try { 327 let decryptedUrl; 328 329 // Try modern decryption first, then fall back to original algorithm 330 try { 331 decryptedUrl = await SecureEncryption.decrypt(encryptedUrl, password); 332 } catch (modernError) { 333 console.warn('Modern decryption failed, trying original algorithm'); 334 decryptedUrl = LegacyEncryption.originalDecrypt(encryptedUrl); 335 } 336 337 const validatedUrl = SecureUtils.validateUrl(decryptedUrl); 338 if (!validatedUrl) { 339 console.error('Invalid or unsafe URL detected'); 340 return; 341 } 342 343 window.location.href = validatedUrl; 344 345 } catch (error) { 346 console.error('Error during URL decryption and navigation:', error.message); 347 } 348 } 349 350 /** 351 * Legacy function for backward compatibility - using original algorithm 352 * @param {string} encryptedString 353 * @returns {string|null} 9 354 */ 10 355 function DeCryptString(encryptedString) { 11 let charCode = 0; 12 let decryptedString = "mailto:"; 13 let encryptionKey = 0; 14 15 for (let i = 0; i < encryptedString.length; i += 2) { 16 encryptionKey = encryptedString.substr(i, 1); 17 charCode = encryptedString.charCodeAt(i + 1); 18 19 if (charCode >= UPPER_LIMIT) { 20 charCode = DEFAULT_VALUE; 21 } 22 23 decryptedString += String.fromCharCode(charCode - encryptionKey); 24 } 25 26 return decryptedString; 27 } 28 29 /** 30 * Redirects the current page to the decrypted URL. 31 * 32 * @param {string} encryptedUrl - The encrypted URL to be decrypted and redirected to. 33 * @return {void} 34 */ 35 function DeCryptX( encryptedUrl ) 36 { 37 location.href=DeCryptString( encryptedUrl ); 38 } 39 40 /** 41 * Generates a hashed string from the input string using a custom algorithm. 42 * The method applies a randomized salt to the ASCII values of the characters 43 * in the input string while avoiding certain blacklisted ASCII values. 44 * 45 * @param {string} inputString - The input string to be hashed. 46 * @return {string} The generated hash string. 356 try { 357 return LegacyEncryption.originalDecrypt(encryptedString); 358 } catch (error) { 359 console.error('Legacy decryption failed:', error.message); 360 return null; 361 } 362 } 363 364 /** 365 * Legacy function for backward compatibility - secured 366 * @param {string} encryptedUrl 367 */ 368 function DeCryptX(encryptedUrl) { 369 const decryptedUrl = DeCryptString(encryptedUrl); 370 if (!decryptedUrl) { 371 console.error('Failed to decrypt URL'); 372 return; 373 } 374 375 const validatedUrl = SecureUtils.validateUrl(decryptedUrl); 376 if (!validatedUrl) { 377 console.error('Invalid or unsafe URL detected'); 378 return; 379 } 380 381 window.location.href = validatedUrl; 382 } 383 384 /** 385 * Generates encrypted email link with proper security 386 * @param {string} emailAddress 387 * @param {string} password 388 * @returns {Promise<string>} 389 */ 390 async function generateSecureEmailLink(emailAddress, password = 'default_key') { 391 if (typeof emailAddress !== 'string' || emailAddress.length === 0) { 392 throw new Error('Valid email address required'); 393 } 394 395 const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; 396 if (!emailRegex.test(emailAddress)) { 397 throw new Error('Invalid email format'); 398 } 399 400 const mailtoUrl = `mailto:${emailAddress}`; 401 const encryptedData = await SecureEncryption.encrypt(mailtoUrl, password); 402 const escapedData = SecureUtils.escapeJavaScript(encryptedData); 403 404 return `javascript:secureDecryptAndNavigate('${escapedData}', '${SecureUtils.escapeJavaScript(password)}')`; 405 } 406 407 /** 408 * Legacy function for backward compatibility - using original algorithm 409 * @param {string} emailAddress 410 * @returns {string} 411 */ 412 function generateDeCryptXHandler(emailAddress) { 413 if (typeof emailAddress !== 'string' || emailAddress.length === 0) { 414 console.error('Valid email address required'); 415 return 'javascript:void(0)'; 416 } 417 418 try { 419 const encrypted = LegacyEncryption.originalEncrypt(emailAddress); 420 const escaped = SecureUtils.escapeJavaScript(encrypted); 421 return `javascript:DeCryptX('${escaped}')`; 422 } catch (error) { 423 console.error('Error generating handler:', error.message); 424 return 'javascript:void(0)'; 425 } 426 } 427 428 /** 429 * Legacy function - matches original PHP generateHashFromString 430 * @param {string} inputString 431 * @returns {string} 47 432 */ 48 433 function generateHashFromString(inputString) { 49 // Replace & with itself (this line seems redundant in the original PHP code) 50 inputString = inputString.replace("&", "&"); 51 let crypt = ''; 52 53 // ASCII values blacklist (taken from the PHP constant) 54 const ASCII_VALUES_BLACKLIST = ['32', '34', '39', '60', '62', '63', '92', '94', '96', '127']; 55 56 for (let i = 0; i < inputString.length; i++) { 57 let salt, asciiValue; 58 do { 59 // Generate random number between 0 and 3 60 salt = Math.floor(Math.random() * 4); 61 // Get ASCII value and add salt 62 asciiValue = inputString.charCodeAt(i) + salt; 63 64 // Check if value exceeds limit 65 if (8364 <= asciiValue) { 66 asciiValue = 128; 67 } 68 } while (ASCII_VALUES_BLACKLIST.includes(asciiValue.toString())); 69 70 // Append salt and character to result 71 crypt += salt + String.fromCharCode(asciiValue); 72 } 73 74 return crypt; 75 } 76 77 /** 78 * Generates a DeCryptX handler URL with a hashed email address. 79 * 80 * @param {string} emailAddress - The email address to be hashed and included in the handler URL. 81 * @return {string} A string representing the JavaScript DeCryptX handler with the hashed email address. 82 */ 83 function generateDeCryptXHandler(emailAddress) { 84 return `javascript:DeCryptX('${generateHashFromString(emailAddress)}')`; 85 } 434 try { 435 return LegacyEncryption.originalEncrypt(inputString); 436 } catch (error) { 437 console.error('Error generating hash:', error.message); 438 return ''; 439 } 440 } 441 442 // Export functions for module usage 443 if (typeof module !== 'undefined' && module.exports) { 444 module.exports = { 445 secureDecryptAndNavigate, 446 generateSecureEmailLink, 447 DeCryptX, 448 DeCryptString, 449 generateDeCryptXHandler, 450 generateHashFromString, 451 SecureEncryption, 452 LegacyEncryption, 453 SecureUtils 454 }; 455 } -
cryptx/trunk/js/cryptx.min.js
r3323475 r3339444 1 const UPPER_LIMIT=8364,DEFAULT_VALUE=128;function DeCryptString(t){let r=0,e="mailto:",n=0;for(let o=0;o<t.length;o+=2)n=t.substr(o,1),r=t.charCodeAt(o+1),r>=8364&&(r=128),e+=String.fromCharCode(r-n);return e}function DeCryptX(t){location.href=DeCryptString(t)}function generateHashFromString(t){t=t.replace("&","&");let r="";const e=["32","34","39","60","62","63","92","94","96","127"];for(let n=0;n<t.length;n++){let o,a;do{o=Math.floor(4*Math.random()),a=t.charCodeAt(n)+o,8364<=a&&(a=128)}while(e.includes(a.toString()));r+=o+String.fromCharCode(a)}return r}function generateDeCryptXHandler(t){return`javascript:DeCryptX('${generateHashFromString(t)}')`}1 const CONFIG={ALLOWED_PROTOCOLS:["http:","https:","mailto:"],MAX_URL_LENGTH:2048,ENCRYPTION_KEY_SIZE:32,IV_SIZE:16};class SecureUtils{static getSecureRandomBytes(e){if("undefined"==typeof crypto||!crypto.getRandomValues)throw new Error("Secure random number generation not available");return crypto.getRandomValues(new Uint8Array(e))}static arrayBufferToBase64(e){const r=new Uint8Array(e);let t="";for(let e=0;e<r.byteLength;e++)t+=String.fromCharCode(r[e]);return btoa(t)}static base64ToArrayBuffer(e){const r=atob(e),t=new ArrayBuffer(r.length),n=new Uint8Array(t);for(let e=0;e<r.length;e++)n[e]=r.charCodeAt(e);return t}static validateUrl(e){if("string"!=typeof e||0===e.length)return null;if(e.length>CONFIG.MAX_URL_LENGTH)return console.error("URL exceeds maximum length"),null;try{const r=new URL(e);if(!CONFIG.ALLOWED_PROTOCOLS.includes(r.protocol))return console.error("Protocol not allowed:",r.protocol),null;if("mailto:"===r.protocol){if(!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(r.pathname))return console.error("Invalid email format in mailto URL"),null}return e}catch(e){return console.error("Invalid URL format:",e.message),null}}static escapeJavaScript(e){return"string"!=typeof e?"":e.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(/"/g,'\\"').replace(/\n/g,"\\n").replace(/\r/g,"\\r").replace(/\t/g,"\\t")}}class LegacyEncryption{static originalDecrypt(e){if("string"!=typeof e||0===e.length)throw new Error("Invalid encrypted string");let r=0,t="mailto:",n=0;try{for(let a=0;a<e.length&&!(a+1>=e.length);a+=2){n=parseInt(e.charAt(a),10),isNaN(n)&&(n=0),r=e.charCodeAt(a+1),r>=8364&&(r=128);const o=r-n;if(o<0||o>1114111)throw new Error("Invalid character code during decryption");t+=String.fromCharCode(o)}return t}catch(e){throw new Error("Original decryption failed: "+e.message)}}static originalEncrypt(e){if("string"!=typeof e||0===e.length)throw new Error("Invalid input string");const r=e.replace(/^mailto:/,"");let t="";const n=["32","34","39","60","62","63","92","94","96","127"];try{for(let e=0;e<r.length;e++){let a,o,i=0;const c=20;do{if(i>=c){a=1,o=r.charCodeAt(e)+a;break}a=SecureUtils.getSecureRandomBytes(1)[0]%4,o=r.charCodeAt(e)+a,o>=8364&&(o=128),i++}while(n.includes(o.toString())&&i<c);t+=a.toString()+String.fromCharCode(o)}return t}catch(e){throw new Error("Original encryption failed: "+e.message)}}}class SecureEncryption{static async deriveKey(e,r){const t=new TextEncoder,n=await crypto.subtle.importKey("raw",t.encode(e),{name:"PBKDF2"},!1,["deriveKey"]);return crypto.subtle.deriveKey({name:"PBKDF2",salt:r,iterations:1e5,hash:"SHA-256"},n,{name:"AES-GCM",length:256},!1,["encrypt","decrypt"])}static async encrypt(e,r){if("string"!=typeof e||"string"!=typeof r)throw new Error("Both plaintext and password must be strings");const t=new TextEncoder,n=SecureUtils.getSecureRandomBytes(16),a=SecureUtils.getSecureRandomBytes(CONFIG.IV_SIZE),o=await this.deriveKey(r,n),i=await crypto.subtle.encrypt({name:"AES-GCM",iv:a},o,t.encode(e)),c=new Uint8Array(i),s=c.slice(0,-16),l=c.slice(-16),y=new Uint8Array(n.length+a.length+s.length+l.length);return y.set(n,0),y.set(a,n.length),y.set(s,n.length+a.length),y.set(l,n.length+a.length+s.length),SecureUtils.arrayBufferToBase64(y.buffer)}static async decrypt(e,r){if("string"!=typeof e||"string"!=typeof r)throw new Error("Both encryptedData and password must be strings");try{const t=SecureUtils.base64ToArrayBuffer(e),n=t.byteLength,a=16,o=16,i=16,c=n-a-o-i;if(n<a+o+i)throw new Error("Encrypted data too short");const s=t.slice(0,a),l=t.slice(a,a+o),y=t.slice(a+o,a+o+c),g=t.slice(-i),d=await this.deriveKey(r,new Uint8Array(s)),p=new Uint8Array(y.byteLength+g.byteLength);p.set(new Uint8Array(y),0),p.set(new Uint8Array(g),y.byteLength);const u=await crypto.subtle.decrypt({name:"AES-GCM",iv:new Uint8Array(l)},d,p);return(new TextDecoder).decode(u)}catch(e){throw new Error("Decryption failed: "+e.message)}}}async function secureDecryptAndNavigate(e,r="default_key"){if("string"==typeof e&&0!==e.length)try{let t;try{t=await SecureEncryption.decrypt(e,r)}catch(r){console.warn("Modern decryption failed, trying original algorithm"),t=LegacyEncryption.originalDecrypt(e)}const n=SecureUtils.validateUrl(t);if(!n)return void console.error("Invalid or unsafe URL detected");window.location.href=n}catch(e){console.error("Error during URL decryption and navigation:",e.message)}else console.error("Invalid encrypted URL provided")}function DeCryptString(e){try{return LegacyEncryption.originalDecrypt(e)}catch(e){return console.error("Legacy decryption failed:",e.message),null}}function DeCryptX(e){const r=DeCryptString(e);if(!r)return void console.error("Failed to decrypt URL");const t=SecureUtils.validateUrl(r);t?window.location.href=t:console.error("Invalid or unsafe URL detected")}async function generateSecureEmailLink(e,r="default_key"){if("string"!=typeof e||0===e.length)throw new Error("Valid email address required");if(!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e))throw new Error("Invalid email format");const t=`mailto:${e}`,n=await SecureEncryption.encrypt(t,r);return`javascript:secureDecryptAndNavigate('${SecureUtils.escapeJavaScript(n)}', '${SecureUtils.escapeJavaScript(r)}')`}function generateDeCryptXHandler(e){if("string"!=typeof e||0===e.length)return console.error("Valid email address required"),"javascript:void(0)";try{const r=LegacyEncryption.originalEncrypt(e);return`javascript:DeCryptX('${SecureUtils.escapeJavaScript(r)}')`}catch(e){return console.error("Error generating handler:",e.message),"javascript:void(0)"}}function generateHashFromString(e){try{return LegacyEncryption.originalEncrypt(e)}catch(e){return console.error("Error generating hash:",e.message),""}}"undefined"!=typeof module&&module.exports&&(module.exports={secureDecryptAndNavigate:secureDecryptAndNavigate,generateSecureEmailLink:generateSecureEmailLink,DeCryptX:DeCryptX,DeCryptString:DeCryptString,generateDeCryptXHandler:generateDeCryptXHandler,generateHashFromString:generateHashFromString,SecureEncryption:SecureEncryption,LegacyEncryption:LegacyEncryption,SecureUtils:SecureUtils}); -
cryptx/trunk/readme.txt
r3335910 r3339444 2 2 Contributors: d3395 3 3 Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=4026696 4 Tags: antispam, email, mail, addresses 5 Requires at least: 6. 04 Tags: antispam, email, mail, addresses, spam protection, email encryption, privacy 5 Requires at least: 6.7 6 6 Tested up to: 6.8 7 Stable tag: 3.5.28 Requires PHP: 8. 07 Stable tag: 4.0.0 8 Requires PHP: 8.1 9 9 License: GPLv2 or later 10 10 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 16 16 No more SPAM by spiders scanning you site for email addresses. With CryptX you can hide all your email addresses, with and without a mailto-link, by converting them using javascript or UNICODE. 17 17 18 CryptX protects your email addresses from spam bots while keeping them readable and functional for your visitors. The plugin automatically detects email addresses in your content and encrypts them using various methods including JavaScript encryption, Unicode conversion, and image replacement. 19 20 **Key Features:** 21 22 * **Automatic Email Detection** - Finds and encrypts email addresses in posts, pages, comments, and widgets 23 * **Multiple Encryption Methods** - JavaScript, Unicode, image replacement, and custom text options 24 * **Widget Support** - Works with text widgets and other widget content 25 * **RSS Feed Control** - Option to disable encryption in RSS feeds 26 * **Whitelist Support** - Exclude specific domains from encryption 27 * **Per-Post Control** - Enable/disable encryption on individual posts and pages 28 * **Shortcode Support** - Use `[cryptx][email protected][/cryptx]` for manual encryption 29 * **Template Functions** - Developer-friendly functions for theme integration 30 18 31 [Plugin Homepage](http://weber-nrw.de/wordpress/cryptx/ "Plugin Homepage") 19 32 20 33 == Screenshots == 21 34 22 1. Plugin settings 23 2. Template functions 35 1. Plugin settings - General configuration options 36 2. Email encryption methods and display options 37 3. Advanced settings and whitelist configuration 38 39 == Installation == 40 41 1. Upload the CryptX folder to the `/wp-content/plugins/` directory 42 2. Activate the plugin through the 'Plugins' menu in WordPress 43 3. Configure the plugin settings under Settings > CryptX 44 4. Your email addresses will now be automatically protected! 45 46 == Frequently Asked Questions == 47 48 = How does CryptX protect my email addresses? = 49 50 CryptX uses various methods to hide email addresses from spam bots while keeping them functional for visitors. Methods include JavaScript encryption, Unicode conversion, and replacing emails with images or custom text. 51 52 = Will this affect my website's performance? = 53 54 CryptX is designed to be lightweight and only loads JavaScript when needed. The performance impact is minimal. 55 56 = Can I exclude certain email addresses from encryption? = 57 58 Yes, you can use the whitelist feature to exclude specific domains or email addresses from encryption. 59 60 = Does it work with contact forms? = 61 62 CryptX primarily works with email addresses displayed in content. It doesn't interfere with contact forms or other form functionality. 63 64 = Can I disable encryption on specific posts? = 65 66 Yes, you can enable the meta box feature to control encryption on individual posts and pages. 67 68 For more information, visit the [Plugin Homepage](http://weber-nrw.de/wordpress/cryptx/ "Plugin Homepage") 24 69 25 70 == Changelog == 71 72 = 4.0.0 = 73 * **Major Update**: Complete code refactoring and modernization 74 * Improved PHP 8.1+ compatibility and performance 75 * Enhanced plugin architecture with better separation of concerns 76 * Improved widget filtering and universal widget support 77 * Better error handling and debugging capabilities 78 * Updated minimum requirements: WordPress 6.7+ and PHP 8.1+ 79 * Improved security and code quality 80 * Enhanced admin interface and settings organization 81 * Better handling of complex HTML structures and multiline content 26 82 = 3.5.2 = 27 83 * Fixed a bug where activating CryptX for the first time caused a PHP Fatal error … … 165 221 * Add Option to disable CryptX on single post/page 166 222 167 == Installation ==168 169 1. Upload "cryptX folder" to the `/wp-content/plugins/` directory170 2. Activate the plugin through the 'Plugins' menu in WordPress171 3. Edit the Options under the Options Page.172 4. Look at your Blog and be happy.173 174 223 == Upgrade Notice == 175 Nothing special to do. 176 177 == Frequently Asked Questions == 178 179 [Plugin Homepage](http://weber-nrw.de/wordpress/cryptx/ "Plugin Homepage") 224 225 = 4.0.0 = 226 Major update with improved PHP 8.1+ compatibility, enhanced performance, and modernized codebase. Please test on a staging site first. Minimum requirements: WordPress 6.7+ and PHP 8.1+. 227 228 = 3.5.2 = 229 Bug fixes for activation errors and Elementor compatibility issues. 230 -
cryptx/trunk/templates/admin/tabs/general.php
r3323475 r3339444 1 <?php 2 /** 3 * CryptX General Settings Tab Template 4 * 5 * @var array $options 6 * @var array $applyTo 7 * @var array $decryptionType 8 * @var array $javascriptLocation 9 * @var string $excludedIds 10 * @var bool $metaBox 11 * @var bool $autolink 12 * @var string $whiteList 13 */ 14 15 defined('ABSPATH') || exit; 16 ?> 17 18 <h4><?php esc_html_e("General", 'cryptx'); ?></h4> 19 <table class="form-table" role="presentation"> 20 <!-- Apply CryptX Section --> 21 <tr> 22 <th scope="row"><?php _e("Apply CryptX to...", 'cryptx'); ?></th> 23 <td> 24 <?php foreach ($applyTo as $key => $setting): ?> 25 <label> 26 <input type="checkbox" 27 name="cryptX_var[<?php echo esc_attr($key); ?>]" 28 value="1" 29 <?php checked($options[$key] ?? 0, 1); ?> /> 30 <?php echo esc_html($setting['label']); ?> 31 <?php if (isset($setting['description'])): ?> 32 <small><?php echo esc_html($setting['description']); ?></small> 33 <?php endif; ?> 34 </label><br/> 35 <?php endforeach; ?> 36 </td> 37 </tr> 38 39 <tr class="spacer"> 40 <td colspan="2"><hr></td> 41 </tr> 42 43 <!-- RSS Feed Section --> 44 <tr> 45 <th scope="row" colspan="2"> 46 <label> 47 <input name="cryptX_var[disable_rss]" 48 type="checkbox" 49 value="1" 50 <?php checked($options['disable_rss'] ?? true, 1); ?> /> 51 <?php esc_html_e("Disable CryptX in RSS feeds", 'cryptx'); ?> 52 </label> 53 <p class="description"> 54 <?php esc_html_e("When enabled, email addresses in RSS feeds will not be encrypted.", 'cryptx'); ?> 55 </p> 56 </th> 57 </tr> 58 59 <tr class="spacer"> 60 <td colspan="2"><hr></td> 61 </tr> 62 63 <!-- Excluded IDs Section --> 64 <tr> 65 <th scope="row"><?php esc_html_e("Excluded ID's...", 'cryptx'); ?></th> 66 <td> 67 <input name="cryptX_var[excludedIDs]" 68 type="text" 69 value="<?php echo esc_attr($excludedIds); ?>" 70 class="regular-text" /> 71 <p class="description"> 72 <?php esc_html_e("Enter all Page/Post ID's to exclude from CryptX as comma separated list.", 'cryptx'); ?> 73 </p> 74 <label> 75 <input name="cryptX_var[metaBox]" 76 type="checkbox" 77 value="1" 78 <?php checked($metaBox, 1); ?> /> 79 <?php esc_html_e("Enable the CryptX Widget on editing a post or page.", 'cryptx'); ?> 80 </label> 81 </td> 82 </tr> 83 84 <tr class="spacer"> 85 <td colspan="2"><hr></td> 86 </tr> 87 88 <!-- Decryption Type Section --> 89 <tr> 90 <th scope="row"><?php esc_html_e("Type of decryption", 'cryptx'); ?></th> 91 <td> 92 <?php foreach ($decryptionType as $type): ?> 93 <label> 94 <input name="cryptX_var[java]" 95 type="radio" 96 value="<?php echo esc_attr($type['value']); ?>" 97 <?php checked($options['java'], $type['value']); ?> /> 98 <?php echo esc_html($type['label']); ?> 99 </label><br /> 100 <?php endforeach; ?> 101 </td> 102 </tr> 103 104 <tr class="spacer"> 105 <td colspan="2"><hr></td> 106 </tr> 107 108 <!-- JavaScript Location Section --> 109 <tr> 110 <th scope="row"><?php esc_html_e("Where to load the needed javascript...", 'cryptx'); ?></th> 111 <td> 112 <?php foreach ($javascriptLocation as $location): ?> 113 <label> 114 <input name="cryptX_var[load_java]" 115 type="radio" 116 value="<?php echo esc_attr($location['value']); ?>" 117 <?php //checked(get_option('load_java'), $location['value']); ?> 118 <?php checked($options['load_java'], $location['value']); ?> /> 119 <?php echo wp_kses($location['label'], ['b' => []]); ?> 120 </label><br /> 121 <?php endforeach; ?> 122 </td> 123 </tr> 124 125 <tr class="spacer"> 126 <td colspan="2"><hr></td> 127 </tr> 128 129 <!-- Autolink Section --> 130 <tr> 131 <th scope="row" colspan="2"> 132 <label> 133 <input name="cryptX_var[autolink]" 134 type="checkbox" 135 value="1" 136 <?php checked($autolink, 1); ?> /> 137 <?php esc_html_e("Add mailto to all unlinked email addresses", 'cryptx'); ?> 138 </label> 139 </th> 140 </tr> 141 142 <tr class="spacer"> 143 <td colspan="2"><hr></td> 144 </tr> 145 146 <!-- Whitelist Section --> 147 <tr> 148 <th scope="row"><?php esc_html_e("Whitelist of extensions", 'cryptx'); ?></th> 149 <td> 150 <input name="cryptX_var[whiteList]" 151 type="text" 152 value="<?php echo esc_attr($whiteList); ?>" 153 class="regular-text" /> 154 <p class="description"> 155 <?php echo wp_kses( 156 __("<strong>This is a workaround for the 'retina issue'.</strong><br/>You can provide a comma separated list of extensions like 'jpeg,jpg,png,gif' which will be ignored by CryptX.", 'cryptx'), 157 ['strong' => [], 'br' => []] 158 ); ?> 159 </p> 160 </td> 161 </tr> 162 163 <tr class="spacer"> 164 <td colspan="2"><hr></td> 165 </tr> 166 167 <!-- Reset Options Section --> 168 <tr> 169 <th scope="row" colspan="2" class="warning"> 170 <label> 171 <input name="cryptX_var_reset" 172 type="checkbox" 173 value="1" /> 174 <?php esc_html_e("Reset CryptX options to defaults. Use it carefully and at your own risk. All changes will be deleted!", 'cryptx'); ?> 175 </label> 176 </th> 177 </tr> 178 </table> 179 180 <input type="hidden" name="cryptX_save_general_settings" value="true"> 181 <?php submit_button(__('Save General Settings', 'cryptx'), 'primary', 'cryptX_save_general_settings'); ?> 182 1 <?php if (!defined('ABSPATH')) exit; ?> 2 3 <div class="cryptx-tab-content cryptx-general-settings"> 4 <table class="form-table"> 5 6 <!-- Security Settings Section --> 7 <tr> 8 <th colspan="2"> 9 <h3 style="margin: 20px 0 10px 0; padding: 10px 0; border-bottom: 1px solid #ddd;"> 10 <?php _e('Encryption Mode', 'cryptx'); ?> 11 </h3> 12 </th> 13 </tr> 14 <tr> 15 <th scope="row"><?php echo $securitySettings['encryption_mode']['label']; ?></th> 16 <td> 17 <select name="cryptX_var[encryption_mode]" id="encryption_mode"> 18 <?php foreach ($securitySettings['encryption_mode']['options'] as $value => $label): ?> 19 <option value="<?php echo esc_attr($value); ?>" <?php selected($securitySettings['encryption_mode']['value'], $value); ?>> 20 <?php echo esc_html($label); ?> 21 </option> 22 <?php endforeach; ?> 23 </select> 24 <p class="description"> 25 <?php echo $securitySettings['encryption_mode']['description']; ?> 26 <br/> 27 <strong><?php _e('Legacy:', 'cryptx'); ?></strong> <?php _e('Uses the original CryptX encryption algorithm for backward compatibility.', 'cryptx'); ?><br/> 28 <strong><?php _e('Secure:', 'cryptx'); ?></strong> <?php _e('Uses modern AES-256-GCM encryption with PBKDF2 key derivation for enhanced security.', 'cryptx'); ?> 29 </p> 30 31 <!-- Hidden checkbox that gets set based on the dropdown --> 32 <input type="hidden" name="cryptX_var[use_secure_encryption]" id="use_secure_encryption" 33 value="<?php echo $securitySettings['use_secure_encryption']['value']; ?>" /> 34 </td> 35 </tr> 36 37 <!-- Separator --> 38 <tr> 39 <th colspan="2"> 40 <h3 style="margin: 20px 0 10px 0; padding: 10px 0; border-bottom: 1px solid #ddd;"> 41 <?php _e('General Settings', 'cryptx'); ?> 42 </h3> 43 </th> 44 </tr> 45 46 <!-- Apply CryptX to Section --> 47 <tr> 48 <th scope="row"><?php _e('Apply CryptX to', 'cryptx'); ?></th> 49 <td> 50 <fieldset> 51 <?php foreach ($applyTo as $key => $setting): ?> 52 <label for="<?php echo esc_attr($key); ?>"> 53 <input type="hidden" name="cryptX_var[<?php echo esc_attr($key); ?>]" value="0" /> 54 <input type="checkbox" 55 id="<?php echo esc_attr($key); ?>" 56 name="cryptX_var[<?php echo esc_attr($key); ?>]" 57 value="1" 58 <?php checked($options[$key] ?? 0, 1); ?> /> 59 <?php echo esc_html($setting['label']); ?> 60 <?php if (isset($setting['description'])): ?> 61 <span class="description"><?php echo esc_html($setting['description']); ?></span> 62 <?php endif; ?> 63 </label><br /> 64 <?php endforeach; ?> 65 </fieldset> 66 </td> 67 </tr> 68 69 <!-- Decryption Type --> 70 <tr> 71 <th scope="row"><?php _e('Decryption type', 'cryptx'); ?></th> 72 <td> 73 <fieldset> 74 <?php foreach ($decryptionType as $key => $setting): ?> 75 <label for="java_<?php echo esc_attr($key); ?>"> 76 <input type="radio" 77 id="java_<?php echo esc_attr($key); ?>" 78 name="cryptX_var[java]" 79 value="<?php echo esc_attr($setting['value']); ?>" 80 <?php checked($options['java'] ?? 1, $setting['value']); ?> /> 81 <?php echo $setting['label']; ?> 82 </label><br /> 83 <?php endforeach; ?> 84 </fieldset> 85 </td> 86 </tr> 87 88 <!-- JavaScript Location --> 89 <tr> 90 <th scope="row"><?php _e('Javascript location', 'cryptx'); ?></th> 91 <td> 92 <fieldset> 93 <?php foreach ($javascriptLocation as $key => $setting): ?> 94 <label for="load_java_<?php echo esc_attr($key); ?>"> 95 <input type="radio" 96 id="load_java_<?php echo esc_attr($key); ?>" 97 name="cryptX_var[load_java]" 98 value="<?php echo esc_attr($setting['value']); ?>" 99 <?php checked($options['load_java'] ?? 1, $setting['value']); ?> /> 100 <?php echo $setting['label']; ?> 101 </label><br /> 102 <?php endforeach; ?> 103 </fieldset> 104 </td> 105 </tr> 106 107 <!-- Additional Options --> 108 <tr> 109 <th scope="row"><?php _e('Additional options', 'cryptx'); ?></th> 110 <td> 111 <fieldset> 112 <label for="autolink"> 113 <input type="hidden" name="cryptX_var[autolink]" value="0" /> 114 <input type="checkbox" 115 id="autolink" 116 name="cryptX_var[autolink]" 117 value="1" 118 <?php checked($options['autolink'] ?? 0, 1); ?> /> 119 <?php _e('Automatically add a link to non-linked email addresses.', 'cryptx'); ?> 120 </label><br /> 121 122 <label for="metaBox"> 123 <input type="hidden" name="cryptX_var[metaBox]" value="0" /> 124 <input type="checkbox" 125 id="metaBox" 126 name="cryptX_var[metaBox]" 127 value="1" 128 <?php checked($options['metaBox'] ?? 0, 1); ?> /> 129 <?php _e('Show the "Disable CryptX" checkbox in the post editor.', 'cryptx'); ?> 130 </label><br /> 131 132 <label for="disable_rss"> 133 <input type="hidden" name="cryptX_var[disable_rss]" value="0" /> 134 <input type="checkbox" 135 id="disable_rss" 136 name="cryptX_var[disable_rss]" 137 value="1" 138 <?php checked($options['disable_rss'] ?? 1, 1); ?> /> 139 <?php _e('Disable CryptX in RSS feeds.', 'cryptx'); ?> 140 </label> 141 </fieldset> 142 </td> 143 </tr> 144 145 <!-- Excluded Post IDs --> 146 <tr> 147 <th scope="row"> 148 <label for="excludedIDs"><?php _e('Excluded posts/pages IDs', 'cryptx'); ?></label> 149 </th> 150 <td> 151 <input type="text" 152 id="excludedIDs" 153 name="cryptX_var[excludedIDs]" 154 value="<?php echo esc_attr($options['excludedIDs'] ?? ''); ?>" 155 class="regular-text" /> 156 <p class="description"> 157 <?php _e('Comma-separated list of post/page IDs where CryptX should be disabled.', 'cryptx'); ?> 158 </p> 159 </td> 160 </tr> 161 162 <!-- Whitelist --> 163 <tr> 164 <th scope="row"> 165 <label for="whiteList"><?php _e('Whitelist', 'cryptx'); ?></label> 166 </th> 167 <td> 168 <input type="text" 169 id="whiteList" 170 name="cryptX_var[whiteList]" 171 value="<?php echo esc_attr($options['whiteList'] ?? 'jpeg,jpg,png,gif'); ?>" 172 class="regular-text" /> 173 <p class="description"> 174 <?php _e('Comma-separated list of file extensions that should not be encrypted when found in email addresses.', 'cryptx'); ?> 175 </p> 176 </td> 177 </tr> 178 </table> 179 180 <!-- Submit Button --> 181 <p class="submit"> 182 <input type="submit" 183 name="cryptX_save_general_settings" 184 class="button-primary" 185 value="<?php _e('Save Changes', 'cryptx'); ?>" /> 186 <input type="submit" 187 name="cryptX_var_reset" 188 class="button-secondary" 189 value="<?php _e('Reset to Defaults', 'cryptx'); ?>" 190 onclick="return confirm('<?php _e('Are you sure you want to reset all settings to defaults?', 'cryptx'); ?>');" /> 191 </p> 192 </div> 193 194 <script> 195 // Auto-sync the dropdown with the hidden checkbox 196 document.addEventListener('DOMContentLoaded', function() { 197 const encryptionModeSelect = document.getElementById('encryption_mode'); 198 const useSecureEncryption = document.getElementById('use_secure_encryption'); 199 200 if (encryptionModeSelect && useSecureEncryption) { 201 // Set initial value 202 useSecureEncryption.value = (encryptionModeSelect.value === 'secure') ? '1' : '0'; 203 204 // Update on change 205 encryptionModeSelect.addEventListener('change', function() { 206 useSecureEncryption.value = (this.value === 'secure') ? '1' : '0'; 207 }); 208 } 209 }); 210 </script> -
cryptx/trunk/templates/admin/tabs/howto.php
r3323475 r3339444 11 11 ?> 12 12 13 <h4><?php esc_html_e("How to use CryptX in your Template", 'cryptx'); ?></h4> 14 <div class="cryptx-documentation"> 15 <p>The <code>[cryptx]</code> shortcode allows you to protect email addresses from spam bots in your WordPress posts 16 and pages, even when CryptX is disabled globally for that content.</p> 17 18 <h3>Basic Usage</h3> 19 <pre><code>[cryptx][email protected][/cryptx]</code></pre> 20 21 <h3>Advanced Usage</h3> 22 <pre><code>[cryptx linktext="Contact Us" subject="Website Inquiry"][email protected][/cryptx]</code></pre> 23 24 <h4>Available Attributes</h4> 25 <table class="cryptx-attributes"> 26 <thead> 27 <tr> 28 <th>Attribute</th> 29 <th>Description</th> 30 <th>Default</th> 31 <th>Example</th> 32 </tr> 33 </thead> 34 <tbody> 35 <tr> 36 <td><code>linktext</code></td> 37 <td>Custom text to display instead of the email address</td> 38 <td>Email address</td> 39 <td><code>linktext="Contact Us"</code></td> 40 </tr> 41 <tr> 42 <td><code>subject</code></td> 43 <td>Pre-defined subject line for the email</td> 44 <td>None</td> 45 <td><code>subject="Website Inquiry"</code></td> 46 </tr> 47 </tbody> 48 </table> 49 50 <h3>Examples</h3> 51 52 <h4>1. Basic Email Protection</h4> 53 <pre><code>[cryptx][email protected][/cryptx]</code></pre> 54 <p>Displays: A protected version of [email protected]</p> 55 56 <h4>2. Custom Link Text</h4> 57 <pre><code>[cryptx linktext="Send us an email"][email protected][/cryptx]</code></pre> 58 <p>Displays: "Send us an email" as a protected link</p> 59 60 <h4>3. With Subject Line</h4> 61 <pre><code>[cryptx subject="Product Inquiry"][email protected][/cryptx]</code></pre> 62 <p>Creates a link that opens the email client with a pre-filled subject line</p> 63 64 <h3>Best Practices</h3> 65 <ul> 66 <li>Use the shortcode when you need to protect individual email addresses in content where CryptX is disabled 67 globally 68 </li> 69 <li>Consider using custom link text for better user experience</li> 70 <li>Use meaningful subject lines when applicable</li> 71 <li>Don't nest shortcodes within the email address</li> 72 </ul> 73 74 <h3>Troubleshooting</h3> 75 <ul> 76 <li><strong>Email Not Protected:</strong> Ensure the shortcode syntax is correct with both opening and closing 77 tags 78 </li> 79 <li><strong>Link Not Working:</strong> Verify that JavaScript is enabled in the browser</li> 80 <li><strong>Strange Characters:</strong> Make sure the email address format is valid</li> 81 </ul> 82 83 <div class="cryptx-notes"> 84 <h4>Important Notes</h4> 85 <ul> 86 <li>The shortcode works independently of global CryptX settings</li> 87 <li>JavaScript must be enabled in the visitor's browser</li> 88 <li>Email addresses are protected using JavaScript encryption</li> 89 </ul> 90 </div> 91 92 <div class="cryptx-version"> 93 <p><strong>Available since:</strong> Version 2.7</p> 94 </div> 95 </div> 96 97 <h4><?php esc_html_e("How to use CryptX javascript function", 'cryptx'); ?></h4> 98 <div class="cryptx-documentation"> 99 <h2>JavaScript Email Protection Functions</h2> 100 101 <p>This section describes the JavaScript functions available for email protection in CryptX.</p> 102 103 <h3>generateDeCryptXHandler()</h3> 104 105 <p>A JavaScript function that generates an encrypted handler for email address protection. 106 This function creates a special URL format that encrypts email addresses to protect them 107 from spam bots while keeping them clickable for real users.</p> 108 109 <h4>Parameters</h4> 110 <ul> 111 <li><code>emailAddress</code> (string) - The email address to encrypt (e.g., "[email protected]")</li> 112 </ul> 113 114 <h4>Returns</h4> 115 <ul> 116 <li>(string) A JavaScript handler string in the format "javascript:DeCryptX('encrypted_string')"</li> 117 </ul> 118 119 <h4>Examples</h4> 120 121 <p>Basic usage in JavaScript:</p> 122 <pre><code class="language-javascript">const handler = generateDeCryptXHandler("[email protected]"); 13 <div class="cryptx-tab-content cryptx-howto-settings"> 14 <table class="form-table"> 15 <!-- Shortcode Usage Section --> 16 <tr> 17 <th colspan="2"> 18 <h3 style="margin: 20px 0 10px 0; padding: 10px 0; border-bottom: 1px solid #ddd;"> 19 <?php esc_html_e("How to use CryptX Shortcode", 'cryptx'); ?> 20 </h3> 21 </th> 22 </tr> 23 <tr> 24 <th scope="row"><?php esc_html_e("Basic Usage", 'cryptx'); ?></th> 25 <td> 26 <p><?php esc_html_e("The [cryptx] shortcode allows you to protect email addresses from spam bots in your WordPress posts and pages, even when CryptX is disabled globally for that content.", 'cryptx'); ?></p> 27 <pre><code>[cryptx][email protected][/cryptx]</code></pre> 28 <p class="description"><?php esc_html_e("Displays: A protected version of [email protected]", 'cryptx'); ?></p> 29 </td> 30 </tr> 31 <tr> 32 <th scope="row"><?php esc_html_e("Advanced Usage", 'cryptx'); ?></th> 33 <td> 34 <pre><code>[cryptx linktext="Contact Us" subject="Website Inquiry"][email protected][/cryptx]</code></pre> 35 <p class="description"><?php esc_html_e("Creates a link with custom text and pre-filled subject line.", 'cryptx'); ?></p> 36 </td> 37 </tr> 38 <tr> 39 <th scope="row"><?php esc_html_e("Available Attributes", 'cryptx'); ?></th> 40 <td> 41 <table class="widefat striped"> 42 <thead> 43 <tr> 44 <th><?php esc_html_e("Attribute", 'cryptx'); ?></th> 45 <th><?php esc_html_e("Description", 'cryptx'); ?></th> 46 <th><?php esc_html_e("Default", 'cryptx'); ?></th> 47 <th><?php esc_html_e("Example", 'cryptx'); ?></th> 48 </tr> 49 </thead> 50 <tbody> 51 <tr> 52 <td><code>linktext</code></td> 53 <td><?php esc_html_e("Custom text to display instead of the email address", 'cryptx'); ?></td> 54 <td><?php esc_html_e("Email address", 'cryptx'); ?></td> 55 <td><code>linktext="Contact Us"</code></td> 56 </tr> 57 <tr> 58 <td><code>subject</code></td> 59 <td><?php esc_html_e("Pre-defined subject line for the email", 'cryptx'); ?></td> 60 <td><?php esc_html_e("None", 'cryptx'); ?></td> 61 <td><code>subject="Website Inquiry"</code></td> 62 </tr> 63 </tbody> 64 </table> 65 </td> 66 </tr> 67 68 <!-- Examples Section --> 69 <tr> 70 <th colspan="2"> 71 <h3 style="margin: 20px 0 10px 0; padding: 10px 0; border-bottom: 1px solid #ddd;"> 72 <?php esc_html_e("Examples", 'cryptx'); ?> 73 </h3> 74 </th> 75 </tr> 76 <tr> 77 <th scope="row"><?php esc_html_e("1. Basic Email Protection", 'cryptx'); ?></th> 78 <td> 79 <pre><code>[cryptx][email protected][/cryptx]</code></pre> 80 <p class="description"><?php esc_html_e("Displays: A protected version of [email protected]", 'cryptx'); ?></p> 81 </td> 82 </tr> 83 <tr> 84 <th scope="row"><?php esc_html_e("2. Custom Link Text", 'cryptx'); ?></th> 85 <td> 86 <pre><code>[cryptx linktext="Send us an email"][email protected][/cryptx]</code></pre> 87 <p class="description"><?php esc_html_e("Displays: \"Send us an email\" as a protected link", 'cryptx'); ?></p> 88 </td> 89 </tr> 90 <tr> 91 <th scope="row"><?php esc_html_e("3. With Subject Line", 'cryptx'); ?></th> 92 <td> 93 <pre><code>[cryptx subject="Product Inquiry"][email protected][/cryptx]</code></pre> 94 <p class="description"><?php esc_html_e("Creates a link that opens the email client with a pre-filled subject line", 'cryptx'); ?></p> 95 </td> 96 </tr> 97 98 <!-- Best Practices Section --> 99 <tr> 100 <th colspan="2"> 101 <h3 style="margin: 20px 0 10px 0; padding: 10px 0; border-bottom: 1px solid #ddd;"> 102 <?php esc_html_e("Best Practices", 'cryptx'); ?> 103 </h3> 104 </th> 105 </tr> 106 <tr> 107 <th scope="row"><?php esc_html_e("Recommendations", 'cryptx'); ?></th> 108 <td> 109 <ul> 110 <li><?php esc_html_e("Use the shortcode when you need to protect individual email addresses in content where CryptX is disabled globally", 'cryptx'); ?></li> 111 <li><?php esc_html_e("Consider using custom link text for better user experience", 'cryptx'); ?></li> 112 <li><?php esc_html_e("Use meaningful subject lines when applicable", 'cryptx'); ?></li> 113 <li><?php esc_html_e("Don't nest shortcodes within the email address", 'cryptx'); ?></li> 114 </ul> 115 </td> 116 </tr> 117 118 <!-- JavaScript Functions Section --> 119 <tr> 120 <th colspan="2"> 121 <h3 style="margin: 20px 0 10px 0; padding: 10px 0; border-bottom: 1px solid #ddd;"> 122 <?php esc_html_e("JavaScript Functions", 'cryptx'); ?> 123 </h3> 124 </th> 125 </tr> 126 <tr> 127 <th scope="row"><?php esc_html_e("generateDeCryptXHandler()", 'cryptx'); ?></th> 128 <td> 129 <p><?php esc_html_e("A JavaScript function that generates an encrypted handler for email address protection. This function creates a special URL format that encrypts email addresses to protect them from spam bots while keeping them clickable for real users.", 'cryptx'); ?></p> 130 131 <h4><?php esc_html_e("Parameters", 'cryptx'); ?></h4> 132 <ul> 133 <li><code>emailAddress</code> (string) - <?php esc_html_e("The email address to encrypt (e.g., \"[email protected]\")", 'cryptx'); ?></li> 134 </ul> 135 136 <h4><?php esc_html_e("Returns", 'cryptx'); ?></h4> 137 <ul> 138 <li><?php esc_html_e("(string) A JavaScript handler string in the format \"javascript:DeCryptX('encrypted_string')\"", 'cryptx'); ?></li> 139 </ul> 140 141 <h4><?php esc_html_e("Basic Usage", 'cryptx'); ?></h4> 142 <pre><code class="language-javascript">const handler = generateDeCryptXHandler("[email protected]"); 123 143 // Returns: javascript:DeCryptX('1A2B3C...')</code></pre> 124 144 125 <p>Creating a protected link:</p>126 <pre><code class="language-javascript">const link = document.createElement('a');145 <h4><?php esc_html_e("Creating a Protected Link", 'cryptx'); ?></h4> 146 <pre><code class="language-javascript">const link = document.createElement('a'); 127 147 link.href = generateDeCryptXHandler('[email protected]'); 128 148 link.textContent = "Contact Us"; 129 149 document.body.appendChild(link);</code></pre> 130 150 131 <p>Direct HTML usage:</p> 132 <pre><code class="language-html"><a href="javascript:generateDeCryptXHandler('[email protected]')">Contact Us</a></code></pre> 133 134 <h4>Implementation with Error Handling</h4> 135 <pre><code class="language-javascript">function createSafeEmailLink(email, linkText) { 151 <h4><?php esc_html_e("Implementation with Error Handling", 'cryptx'); ?></h4> 152 <pre><code class="language-javascript">function createSafeEmailLink(email, linkText) { 136 153 try { 137 154 // Input validation … … 155 172 } 156 173 }</code></pre> 157 158 <h4>Important Notes</h4> 159 160 <h5>1. Dependencies</h5> 161 <ul> 162 <li>Requires generateHashFromString function</li> 163 <li>Requires DeCryptX function in the global scope</li> 164 </ul> 165 166 <h5>2. Browser Requirements</h5> 167 <ul> 168 <li>Works in all modern browsers</li> 169 <li>JavaScript must be enabled</li> 170 </ul> 171 172 <h5>3. Best Practices</h5> 173 <ul> 174 <li>Provide fallback for users with JavaScript disabled</li> 175 <li>Use meaningful link text instead of showing the email address</li> 176 <li>Add title or aria-label for accessibility</li> 177 </ul> 178 179 <h5>4. Security Considerations</h5> 180 <ul> 181 <li>Encryption is for spam prevention only</li> 182 <li>Not suitable for sensitive data transmission</li> 183 <li>Email address will be visible in browser's JavaScript console when decrypted</li> 184 </ul> 185 186 <h4>Troubleshooting</h4> 187 188 <h5>1. If links are not working:</h5> 189 <ul> 190 <li>Verify DeCryptX function is included</li> 191 <li>Check if JavaScript is enabled</li> 192 <li>Ensure email address format is valid</li> 193 </ul> 194 195 <h5>2. Different encryptions:</h5> 196 <ul> 197 <li>Normal behavior: same email generates different encrypted strings</li> 198 <li>Each encryption uses random values for security</li> 199 </ul> 200 201 <h5>3. Performance:</h5> 202 <ul> 203 <li>Lightweight function suitable for multiple uses</li> 204 <li>Safe for use in loops or event handlers</li> 205 </ul> 206 207 <div class="cryptx-related"> 208 <h4>Related Functions</h4> 209 <ul> 210 <li><a href="#DeCryptX">DeCryptX()</a> - For the decryption function</li> 211 <li><a href="#generateHashFromString">generateHashFromString()</a> - For the internal encryption function 212 </li> 213 </ul> 214 </div> 215 216 <div class="cryptx-version"> 217 <p><strong>Since:</strong> Version 3.5.0</p> 218 </div> 174 </td> 175 </tr> 176 177 <!-- Important Notes Section --> 178 <tr> 179 <th colspan="2"> 180 <h3 style="margin: 20px 0 10px 0; padding: 10px 0; border-bottom: 1px solid #ddd;"> 181 <?php esc_html_e("Important Notes", 'cryptx'); ?> 182 </h3> 183 </th> 184 </tr> 185 <tr> 186 <th scope="row"><?php esc_html_e("Requirements", 'cryptx'); ?></th> 187 <td> 188 <ul> 189 <li><?php esc_html_e("The shortcode works independently of global CryptX settings", 'cryptx'); ?></li> 190 <li><?php esc_html_e("JavaScript must be enabled in the visitor's browser", 'cryptx'); ?></li> 191 <li><?php esc_html_e("Email addresses are protected using JavaScript encryption", 'cryptx'); ?></li> 192 </ul> 193 </td> 194 </tr> 195 <tr> 196 <th scope="row"><?php esc_html_e("Browser Support", 'cryptx'); ?></th> 197 <td> 198 <ul> 199 <li><?php esc_html_e("Works in all modern browsers", 'cryptx'); ?></li> 200 <li><?php esc_html_e("Provide fallback for users with JavaScript disabled", 'cryptx'); ?></li> 201 </ul> 202 </td> 203 </tr> 204 205 <!-- Troubleshooting Section --> 206 <tr> 207 <th colspan="2"> 208 <h3 style="margin: 20px 0 10px 0; padding: 10px 0; border-bottom: 1px solid #ddd;"> 209 <?php esc_html_e("Troubleshooting", 'cryptx'); ?> 210 </h3> 211 </th> 212 </tr> 213 <tr> 214 <th scope="row"><?php esc_html_e("Common Issues", 'cryptx'); ?></th> 215 <td> 216 <ul> 217 <li><strong><?php esc_html_e("Email Not Protected:", 'cryptx'); ?></strong> <?php esc_html_e("Ensure the shortcode syntax is correct with both opening and closing tags", 'cryptx'); ?></li> 218 <li><strong><?php esc_html_e("Link Not Working:", 'cryptx'); ?></strong> <?php esc_html_e("Verify that JavaScript is enabled in the browser", 'cryptx'); ?></li> 219 <li><strong><?php esc_html_e("Strange Characters:", 'cryptx'); ?></strong> <?php esc_html_e("Make sure the email address format is valid", 'cryptx'); ?></li> 220 <li><strong><?php esc_html_e("Different encryptions:", 'cryptx'); ?></strong> <?php esc_html_e("Normal behavior: same email generates different encrypted strings for security", 'cryptx'); ?></li> 221 </ul> 222 </td> 223 </tr> 224 225 <!-- Version Info --> 226 <tr> 227 <th scope="row"><?php esc_html_e("Version Information", 'cryptx'); ?></th> 228 <td> 229 <p><strong><?php esc_html_e("Shortcode available since:", 'cryptx'); ?></strong> <?php esc_html_e("Version 2.7", 'cryptx'); ?></p> 230 <p><strong><?php esc_html_e("JavaScript functions since:", 'cryptx'); ?></strong> <?php esc_html_e("Version 3.5.0", 'cryptx'); ?></p> 231 </td> 232 </tr> 233 </table> 219 234 </div> 220 235 221 236 <style> 222 .cryptx-documentation { 223 max-width: 900px; 224 margin: 20px auto; 225 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; 226 line-height: 1.6; 227 } 228 229 .cryptx-documentation h2 { 230 color: #23282d; 231 border-bottom: 1px solid #eee; 232 padding-bottom: 10px; 233 } 234 235 .cryptx-documentation h3, 236 .cryptx-documentation h4, 237 .cryptx-documentation h5 { 238 color: #23282d; 239 margin-top: 1.5em; 240 } 241 242 .cryptx-documentation code { 237 .cryptx-howto-settings pre { 238 background: #f4f4f4; 239 padding: 15px; 240 border-radius: 4px; 241 overflow-x: auto; 242 margin: 10px 0; 243 } 244 245 .cryptx-howto-settings code { 243 246 background: #f4f4f4; 244 247 padding: 2px 6px; … … 247 250 } 248 251 249 .cryptx-documentation pre { 250 background: #f4f4f4; 251 padding: 15px; 252 border-radius: 4px; 253 overflow-x: auto; 254 } 255 256 .cryptx-documentation pre code { 252 .cryptx-howto-settings pre code { 257 253 background: none; 258 254 padding: 0; 259 255 } 260 256 261 .cryptx- documentationul {257 .cryptx-howto-settings ul { 262 258 margin-left: 20px; 263 259 } 264 260 265 .cryptx- documentationli {261 .cryptx-howto-settings li { 266 262 margin-bottom: 8px; 267 263 } 268 264 269 .cryptx-related { 270 margin-top: 30px; 271 padding: 15px; 272 background: #f8f9fa; 273 border-radius: 4px; 274 } 275 276 .cryptx-version { 265 .cryptx-howto-settings h4 { 277 266 margin-top: 20px; 278 color: #666; 279 font-style: italic; 267 margin-bottom: 10px; 268 color: #23282d; 269 } 270 271 .cryptx-howto-settings .widefat { 272 margin-top: 10px; 273 } 274 275 .cryptx-howto-settings .widefat th, 276 .cryptx-howto-settings .widefat td { 277 padding: 8px 10px; 280 278 } 281 279 </style> -
cryptx/trunk/templates/admin/tabs/presentation.php
r3323475 r3339444 1 1 <?php 2 2 /** 3 * Template for the CryptX presentation settings tab 4 * 5 * @var array $css CSS settings 6 * @var array $linkTextOptions Link text display options 7 * @var int $selectedOption Currently selected link text option 3 * CryptX Presentation Settings Tab Template 4 * Variables extracted from PresentationSettingsTab: 5 * - $css: CSS settings array 6 * - $emailReplacements: Email replacement settings 7 * - $linkTextOptions: Link text configuration options 8 * - $selectedOption: Currently selected option value 8 9 */ 9 10 defined('ABSPATH') || exit; 10 if (!defined('ABSPATH')) { 11 exit; 12 } 11 13 ?> 12 14 13 <h4><?php _e("Define CSS Options", 'cryptx'); ?></h4>15 <div class="cryptx-tab-content cryptx-presentation-settings"> 14 16 <table class="form-table"> 15 <tr> 16 <th><label for="cryptX_var[css_id]"><?php _e("CSS ID", 'cryptx'); ?></label></th> 17 18 <!-- CSS Settings Section --> 19 <tr> 20 <th colspan="2"> 21 <h3 style="margin: 20px 0 10px 0; padding: 10px 0; border-bottom: 1px solid #ddd;"> 22 <?php _e('CSS Settings', 'cryptx'); ?> 23 </h3> 24 </th> 25 </tr> 26 <tr> 27 <th scope="row"><?php _e("CSS-ID", 'cryptx'); ?></th> 17 28 <td> 18 <input name="cryptX_var[css_id]" 19 id="cryptX_var[css_id]" 20 type="text" 21 value="<?php echo esc_attr($css['id']); ?>" 22 class="regular-text" /> 23 <p class="description"> 24 <?php _e("Please be careful using this feature! IDs should be unique. You should prefer using a css class instead.", 'cryptx'); ?> 25 </p> 29 <input name="cryptX_var[css_id]" id="css_id" value="<?php echo esc_attr($css['id']); ?>" type="text" class="regular-text" /> 30 <p class="description"><?php _e("Please be careful using this feature! IDs should be unique. You should prefer using a CSS class instead.", 'cryptx'); ?></p> 26 31 </td> 27 32 </tr> 28 33 <tr> 29 <th ><label for="cryptX_var[css_class]"><?php _e("CSS Class", 'cryptx'); ?></label></th>34 <th scope="row"><?php _e("CSS-Class", 'cryptx'); ?></th> 30 35 <td> 31 <input name="cryptX_var[css_class]" 32 id="cryptX_var[css_class]" 33 type="text" 34 value="<?php echo esc_attr($css['class']); ?>" 35 class="regular-text" /> 36 <input name="cryptX_var[css_class]" id="css_class" value="<?php echo esc_attr($css['class']); ?>" type="text" class="regular-text" /> 37 <p class="description"><?php _e("Add custom CSS class to encrypted email links.", 'cryptx'); ?></p> 36 38 </td> 37 39 </tr> 40 41 <!-- Link Text Options Section --> 42 <tr> 43 <th colspan="2"> 44 <h3 style="margin: 20px 0 10px 0; padding: 10px 0; border-bottom: 1px solid #ddd;"> 45 <?php _e('Link Text Options', 'cryptx'); ?> 46 </h3> 47 </th> 48 </tr> 49 <tr> 50 <th scope="row"><?php _e('Presentation Method', 'cryptx'); ?></th> 51 <td> 52 <fieldset> 53 <!-- Option 0: Replacement Text --> 54 <label> 55 <input name="cryptX_var[opt_linktext]" type="radio" value="0" <?php checked($selectedOption, 0); ?> /> 56 <strong><?php _e("Show Email with text replacement", 'cryptx'); ?></strong> 57 </label> 58 <div style="margin-left: 25px; margin-top: 10px; margin-bottom: 20px;"> 59 <table class="form-table" style="margin: 0;"> 60 <?php foreach ($linkTextOptions['replacement']['fields'] as $field => $config): ?> 61 <tr> 62 <th scope="row" style="padding-left: 0;"><?php echo $config['label']; ?></th> 63 <td style="padding-left: 10px;"> 64 <input name="cryptX_var[<?php echo esc_attr($field); ?>]" 65 value="<?php echo esc_attr($config['value']); ?>" 66 type="text" class="regular-text" /> 67 </td> 68 </tr> 69 <?php endforeach; ?> 70 </table> 71 </div> 72 73 <!-- Option 1: Custom Text --> 74 <label> 75 <input name="cryptX_var[opt_linktext]" type="radio" value="1" <?php checked($selectedOption, 1); ?> /> 76 <strong><?php _e("Show custom text", 'cryptx'); ?></strong> 77 </label> 78 <div style="margin-left: 25px; margin-top: 10px; margin-bottom: 20px;"> 79 <table class="form-table" style="margin: 0;"> 80 <?php foreach ($linkTextOptions['customText']['fields'] as $field => $config): ?> 81 <tr> 82 <th scope="row" style="padding-left: 0;"><?php echo $config['label']; ?></th> 83 <td style="padding-left: 10px;"> 84 <input name="cryptX_var[<?php echo esc_attr($field); ?>]" 85 value="<?php echo esc_attr($config['value']); ?>" 86 type="text" class="regular-text" /> 87 </td> 88 </tr> 89 <?php endforeach; ?> 90 </table> 91 </div> 92 93 <!-- Option 2: External Image --> 94 <label> 95 <input name="cryptX_var[opt_linktext]" type="radio" value="2" <?php checked($selectedOption, 2); ?> /> 96 <strong><?php _e("Show external image", 'cryptx'); ?></strong> 97 </label> 98 <div style="margin-left: 25px; margin-top: 10px; margin-bottom: 20px;"> 99 <table class="form-table" style="margin: 0;"> 100 <?php foreach ($linkTextOptions['externalImage']['fields'] as $field => $config): ?> 101 <tr> 102 <th scope="row" style="padding-left: 0;"><?php echo $config['label']; ?></th> 103 <td style="padding-left: 10px;"> 104 <?php if ($field === 'alt_linkimage'): ?> 105 <input name="cryptX_var[<?php echo esc_attr($field); ?>]" 106 value="<?php echo esc_attr($config['value']); ?>" 107 type="url" class="regular-text" 108 placeholder="https://example.com/image.png" /> 109 <?php else: ?> 110 <input name="cryptX_var[<?php echo esc_attr($field); ?>]" 111 value="<?php echo esc_attr($config['value']); ?>" 112 type="text" class="regular-text" /> 113 <?php endif; ?> 114 </td> 115 </tr> 116 <?php endforeach; ?> 117 </table> 118 </div> 119 120 <!-- Option 3: Uploaded Image --> 121 <label> 122 <input name="cryptX_var[opt_linktext]" type="radio" value="3" <?php checked($selectedOption, 3); ?> /> 123 <strong><?php _e("Show uploaded image", 'cryptx'); ?></strong> 124 </label> 125 <div style="margin-left: 25px; margin-top: 10px; margin-bottom: 20px;"> 126 <table class="form-table" style="margin: 0;"> 127 <tr> 128 <th scope="row" style="padding-left: 0;"><?php _e("Select Image", 'cryptx'); ?></th> 129 <td style="padding-left: 10px;"> 130 <input name="cryptX_var[alt_uploadedimage]" id="alt_uploadedimage" 131 value="<?php echo esc_attr($linkTextOptions['uploadedImage']['fields']['alt_uploadedimage']['value']); ?>" 132 type="hidden" /> 133 <button type="button" class="button" id="upload_image_button"> 134 <?php _e("Choose Image", 'cryptx'); ?> 135 </button> 136 <div id="image_preview" style="margin-top: 10px;"></div> 137 </td> 138 </tr> 139 <tr> 140 <th scope="row" style="padding-left: 0;"> 141 <?php echo $linkTextOptions['uploadedImage']['fields']['alt_linkimage_title']['label']; ?> 142 </th> 143 <td style="padding-left: 10px;"> 144 <input name="cryptX_var[alt_linkimage_title]" 145 value="<?php echo esc_attr($linkTextOptions['uploadedImage']['fields']['alt_linkimage_title']['value']); ?>" 146 type="text" class="regular-text" /> 147 </td> 148 </tr> 149 </table> 150 </div> 151 152 <!-- Option 4: Scrambled Text --> 153 <label> 154 <input name="cryptX_var[opt_linktext]" type="radio" value="4" <?php checked($selectedOption, 4); ?> /> 155 <strong><?php echo $linkTextOptions['scrambled']['label']; ?></strong> 156 </label><br/><br/> 157 158 <!-- Option 5: PNG Image --> 159 <label> 160 <input name="cryptX_var[opt_linktext]" type="radio" value="5" <?php checked($selectedOption, 5); ?> /> 161 <strong><?php echo $linkTextOptions['pngImage']['label']; ?></strong> 162 </label> 163 <div style="margin-left: 25px; margin-top: 10px; margin-bottom: 20px;"> 164 <table class="form-table" style="margin: 0;"> 165 <?php foreach ($linkTextOptions['pngImage']['fields'] as $field => $config): ?> 166 <tr> 167 <th scope="row" style="padding-left: 0;"><?php echo $config['label']; ?></th> 168 <td style="padding-left: 10px;"> 169 <?php if ($field === 'c2i_font'): ?> 170 <select name="cryptX_var[<?php echo esc_attr($field); ?>]"> 171 <?php foreach ($config['options'] as $value => $label): ?> 172 <option value="<?php echo esc_attr($value); ?>" <?php selected($config['value'], $value); ?>> 173 <?php echo esc_html($label); ?> 174 </option> 175 <?php endforeach; ?> 176 </select> 177 <?php elseif ($field === 'c2i_fontRGB'): ?> 178 <input name="cryptX_var[<?php echo esc_attr($field); ?>]" 179 id="c2i_fontRGB" 180 value="<?php echo esc_attr($config['value']); ?>" 181 type="text" class="color-field" /> 182 <?php elseif ($field === 'c2i_fontSize'): ?> 183 <input name="cryptX_var[<?php echo esc_attr($field); ?>]" 184 value="<?php echo esc_attr($config['value']); ?>" 185 type="number" min="8" max="72" /> 186 <?php else: ?> 187 <input name="cryptX_var[<?php echo esc_attr($field); ?>]" 188 value="<?php echo esc_attr($config['value']); ?>" 189 type="text" class="regular-text" /> 190 <?php endif; ?> 191 </td> 192 </tr> 193 <?php endforeach; ?> 194 </table> 195 </div> 196 </fieldset> 197 </td> 198 </tr> 38 199 </table> 39 200 40 <h4><?php _e("Define Presentation Options", 'cryptx'); ?></h4> 41 <table class="form-table"> 42 <tbody> 43 <!-- Character Replacement Option --> 44 <tr> 45 <td> 46 <input type="radio" 47 name="cryptX_var[opt_linktext]" 48 id="opt_linktext_0" 49 value="<?php echo esc_attr($linkTextOptions['replacement']['value']); ?>" 50 <?php checked($selectedOption, $linkTextOptions['replacement']['value']); ?> /> 51 </td> 52 <th scope="row"> 53 <?php foreach ($linkTextOptions['replacement']['fields'] as $key => $field): ?> 54 <div class="cryptx-field-row"> 55 <label for="cryptX_var[<?php echo esc_attr($key); ?>]"> 56 <?php echo esc_html($field['label']); ?> 57 </label> 58 <input type="text" 59 name="cryptX_var[<?php echo esc_attr($key); ?>]" 60 id="cryptX_var[<?php echo esc_attr($key); ?>]" 61 value="<?php echo esc_attr($field['value']); ?>" 62 class="regular-text" /> 63 </div> 64 <?php endforeach; ?> 65 </th> 66 </tr> 67 68 <tr class="spacer"><td colspan="3"><hr></td></tr> 69 70 <!-- Custom Text Option --> 71 <tr> 72 <td> 73 <input type="radio" 74 name="cryptX_var[opt_linktext]" 75 id="opt_linktext_1" 76 value="<?php echo esc_attr($linkTextOptions['customText']['value']); ?>" 77 <?php checked($selectedOption, $linkTextOptions['customText']['value']); ?> /> 78 </td> 79 <th> 80 <?php foreach ($linkTextOptions['customText']['fields'] as $key => $field): ?> 81 <label for="cryptX_var[<?php echo esc_attr($key); ?>]"> 82 <?php echo esc_html($field['label']); ?> 83 </label> 84 <input type="text" 85 name="cryptX_var[<?php echo esc_attr($key); ?>]" 86 id="cryptX_var[<?php echo esc_attr($key); ?>]" 87 value="<?php echo esc_attr($field['value']); ?>" 88 class="regular-text" /> 89 <?php endforeach; ?> 90 </th> 91 </tr> 92 93 <tr class="spacer"><td colspan="3"><hr></td></tr> 94 95 <!-- External Image Option --> 96 <tr> 97 <td> 98 <input type="radio" 99 name="cryptX_var[opt_linktext]" 100 id="opt_linktext_2" 101 value="<?php echo esc_attr($linkTextOptions['externalImage']['value']); ?>" 102 <?php checked($selectedOption, $linkTextOptions['externalImage']['value']); ?> /> 103 </td> 104 <th> 105 <?php foreach ($linkTextOptions['externalImage']['fields'] as $key => $field): ?> 106 <div class="cryptx-field-row"> 107 <label for="cryptX_var[<?php echo esc_attr($key); ?>]"> 108 <?php echo esc_html($field['label']); ?> 109 </label> 110 <input type="text" 111 name="cryptX_var[<?php echo esc_attr($key); ?>]" 112 id="cryptX_var[<?php echo esc_attr($key); ?>]" 113 value="<?php echo esc_attr($field['value']); ?>" 114 class="regular-text" /> 115 </div> 116 <?php endforeach; ?> 117 </th> 118 </tr> 119 120 <tr class="spacer"><td colspan="3"><hr></td></tr> 121 122 <!-- Uploaded Image Option --> 123 <tr> 124 <td> 125 <input type="radio" 126 name="cryptX_var[opt_linktext]" 127 id="opt_linktext_3" 128 value="<?php echo esc_attr($linkTextOptions['uploadedImage']['value']); ?>" 129 <?php checked($selectedOption, $linkTextOptions['uploadedImage']['value']); ?> /> 130 </td> 131 <th> 132 <label for="upload_image_button"><?php _e("Select an uploaded image", 'cryptx'); ?></label> 133 </th> 134 <td> 135 <input id="upload_image_button" type="button" class="button" value="<?php esc_attr_e('Upload image'); ?>" /> 136 <input id="remove_image_button" type="button" class="button button-link-delete hidden" value="<?php esc_attr_e('Delete image'); ?>" /> 137 <span id="opt_linktext4_notice"><?php _e("You have to upload an image first before this option can be activated.", 'cryptx'); ?></span> 138 <input type="hidden" 139 name="cryptX_var[alt_uploadedimage]" 140 id="image_attachment_id" 141 value="<?php echo esc_attr($linkTextOptions['uploadedImage']['fields']['alt_uploadedimage']['value']); ?>"> 142 <div> 143 <img id="image-preview" src="<?php echo esc_url(wp_get_attachment_url($linkTextOptions['uploadedImage']['fields']['alt_uploadedimage']['value'])); ?>"> 144 </div> 145 <label for="cryptX_var[alt_linkimage_title]"> 146 <?php echo esc_html($linkTextOptions['uploadedImage']['fields']['alt_linkimage_title']['label']); ?> 147 </label> 148 <input type="text" 149 name="cryptX_var[alt_linkimage_title]" 150 value="<?php echo esc_attr($linkTextOptions['uploadedImage']['fields']['alt_linkimage_title']['value']); ?>" 151 class="regular-text" /> 152 </td> 153 </tr> 154 155 <tr class="spacer"><td colspan="3"><hr></td></tr> 156 157 <!-- Text Scrambling Option --> 158 <tr> 159 <td> 160 <input type="radio" 161 name="cryptX_var[opt_linktext]" 162 id="opt_linktext_4" 163 value="<?php echo esc_attr($linkTextOptions['scrambled']['value']); ?>" 164 <?php checked($selectedOption, $linkTextOptions['scrambled']['value']); ?> /> 165 </td> 166 <th colspan="2"> 167 <?php echo esc_html($linkTextOptions['scrambled']['label']); ?> 168 <small><?php _e("Try it and look at your site and check the html source!", 'cryptx'); ?></small> 169 </th> 170 </tr> 171 172 <tr class="spacer"><td colspan="3"><hr></td></tr> 173 174 <!-- PNG Image Conversion Option --> 175 <tr> 176 <td> 177 <input type="radio" 178 name="cryptX_var[opt_linktext]" 179 id="opt_linktext_5" 180 value="<?php echo esc_attr($linkTextOptions['pngImage']['value']); ?>" 181 <?php checked($selectedOption, $linkTextOptions['pngImage']['value']); ?> /> 182 </td> 183 <th><?php echo esc_html($linkTextOptions['pngImage']['label']); ?></th> 184 <td> 185 <?php foreach ($linkTextOptions['pngImage']['fields'] as $key => $field): ?> 186 <div class="cryptx-field-row"> 187 <label for="cryptX_var[<?php echo esc_attr($key); ?>]"> 188 <?php echo esc_html($field['label']); ?> 189 </label> 190 <?php if ($key === 'c2i_font'): ?> 191 <select name="cryptX_var[<?php echo esc_attr($key); ?>]" 192 id="cryptX_var[<?php echo esc_attr($key); ?>]"> 193 <?php foreach ($field['options'] as $value => $label): ?> 194 <option value="<?php echo esc_attr($value); ?>" 195 <?php selected($field['value'], $value); ?>> 196 <?php echo esc_html($label); ?> 197 </option> 198 <?php endforeach; ?> 199 </select> 200 <?php elseif ($key === 'c2i_fontSize'): ?> 201 <input type="number" 202 name="cryptX_var[<?php echo esc_attr($key); ?>]" 203 id="cryptX_var[<?php echo esc_attr($key); ?>]" 204 value="<?php echo esc_attr($field['value']); ?>" 205 class="small-text" /> 206 <?php else: ?> 207 <input type="text" 208 name="cryptX_var[<?php echo esc_attr($key); ?>]" 209 id="cryptX_var[<?php echo esc_attr($key); ?>]" 210 value="<?php echo esc_attr($field['value']); ?>" 211 class="color-field" /> 212 <?php endif; ?> 213 </div> 214 <?php endforeach; ?> 215 </td> 216 </tr> 217 </tbody> 218 </table> 219 220 <?php submit_button(__('Save Presentation Settings', 'cryptx'), 'primary', 'cryptX_save_presentation_settings'); ?> 201 <!-- Submit Button --> 202 <p class="submit"> 203 <input type="submit" 204 name="cryptX_save_presentation_settings" 205 class="button-primary" 206 value="<?php _e('Save Changes', 'cryptx'); ?>" /> 207 <input type="submit" 208 name="cryptX_var_reset" 209 class="button-secondary" 210 value="<?php _e('Reset to Defaults', 'cryptx'); ?>" 211 onclick="return confirm('<?php _e('Are you sure you want to reset all presentation settings to defaults?', 'cryptx'); ?>');" /> 212 </p> 213 </div> 214 215 <script> 216 document.addEventListener('DOMContentLoaded', function() { 217 // Initialize WordPress Color Picker 218 if (typeof jQuery !== 'undefined' && jQuery.fn.wpColorPicker) { 219 jQuery('.color-field').wpColorPicker(); 220 } 221 222 // Handle image upload functionality 223 const uploadButton = document.getElementById('upload_image_button'); 224 const imageField = document.getElementById('alt_uploadedimage'); 225 const imagePreview = document.getElementById('image_preview'); 226 227 if (uploadButton && typeof wp !== 'undefined' && wp.media) { 228 let mediaUploader; 229 230 uploadButton.addEventListener('click', function(e) { 231 e.preventDefault(); 232 233 if (mediaUploader) { 234 mediaUploader.open(); 235 return; 236 } 237 238 mediaUploader = wp.media({ 239 title: '<?php _e("Choose Image", "cryptx"); ?>', 240 button: { 241 text: '<?php _e("Choose Image", "cryptx"); ?>' 242 }, 243 multiple: false, 244 library: { 245 type: 'image' 246 } 247 }); 248 249 mediaUploader.on('select', function() { 250 const attachment = mediaUploader.state().get('selection').first().toJSON(); 251 imageField.value = attachment.id; 252 253 if (attachment.sizes && attachment.sizes.thumbnail) { 254 imagePreview.innerHTML = '<img src="' + attachment.sizes.thumbnail.url + '" style="max-width: 150px; height: auto;" />'; 255 } else { 256 imagePreview.innerHTML = '<img src="' + attachment.url + '" style="max-width: 150px; height: auto;" />'; 257 } 258 }); 259 260 mediaUploader.open(); 261 }); 262 263 // Show current image if one is selected 264 if (imageField.value && imageField.value !== '0') { 265 wp.media.attachment(imageField.value).fetch().then(function(attachment) { 266 if (attachment.attributes.sizes && attachment.attributes.sizes.thumbnail) { 267 imagePreview.innerHTML = '<img src="' + attachment.attributes.sizes.thumbnail.url + '" style="max-width: 150px; height: auto;" />'; 268 } else { 269 imagePreview.innerHTML = '<img src="' + attachment.attributes.url + '" style="max-width: 150px; height: auto;" />'; 270 } 271 }); 272 } 273 } 274 }); 275 </script>
Note: See TracChangeset
for help on using the changeset viewer.