Plugin Directory

Changeset 3339444


Ignore:
Timestamp:
08/05/2025 07:31:08 AM (8 months ago)
Author:
d3395
Message:

tagging version 4.0.0

Location:
cryptx
Files:
4 added
19 edited
11 copied

Legend:

Unmodified
Added
Removed
  • cryptx/tags/4.0.0/classes/Admin/ChangelogSettingsTab.php

    r3323475 r3339444  
    55use CryptX\Config;
    66
    7 /**
    8  * Class ChangelogSettingsTab
    9  * Handles the changelog tab functionality in the CryptX plugin admin interface
    10  */
    117class ChangelogSettingsTab
    128{
     
    1511     */
    1612    private Config $config;
     13
     14    /**
     15     * Template path
     16     */
     17    private const TEMPLATE_PATH = CRYPTX_DIR_PATH . 'templates/admin/tabs/changelog.php';
    1718
    1819    /**
     
    3132    public function render(): void
    3233    {
    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]);
    3540    }
    3641
    3742    /**
    38      * Parse and render changelog content from readme.txt
     43     * Get changelog data
    3944     */
    40     private function renderChangelogContent(): void
     45    private function getChangelogData(): array
    4146    {
    4247        $readmePath = CRYPTX_DIR_PATH . '/readme.txt';
    4348        if (!file_exists($readmePath)) {
    44             return;
     49            return [];
    4550        }
    4651
    4752        $fileContents = file_get_contents($readmePath);
    4853        if ($fileContents === false) {
    49             return;
     54            return [];
    5055        }
    5156
    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));
    5567        }
     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');
    5679    }
    5780
     
    89112
    90113        for ($i = 1; $i <= count($_sections); $i += 2) {
     114            if (!isset($_sections[$i - 1]) || !isset($_sections[$i])) {
     115                continue;
     116            }
     117
    91118            $title = $_sections[$i - 1];
    92119            $sections[str_replace(' ', '_', strtolower($title))] = [
     
    111138
    112139        for ($i = 1; $i <= count($_changelogs); $i += 2) {
     140            if (!isset($_changelogs[$i - 1]) || !isset($_changelogs[$i])) {
     141                continue;
     142            }
     143
    113144            $version = $_changelogs[$i - 1];
    114145            $content = ltrim($_changelogs[$i], "\n");
    115             $content = str_replace("* ", "<li>", $content);
    116             $content = str_replace("\n", " </li>\n", $content);
    117146
    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            }
    122156        }
    123157
    124158        return $changelogs;
    125159    }
     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    }
    126190}
  • cryptx/tags/4.0.0/classes/Admin/GeneralSettingsTab.php

    r3323475 r3339444  
    1818        'disable_rss' => 0,
    1919        'java' => 1,
    20         'load_java' => 1
     20        'load_java' => 1,
     21        'use_secure_encryption' => 0,
     22        'encryption_mode' => 'legacy'
    2123    ];
     24
    2225    public function __construct(Config $config) {
    2326        $this->config = $config;
     
    3336        $settings = [
    3437            '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            ],
    3554            'applyTo' => [
    3655                'the_content' => [
     
    5069                'widget_text' => [
    5170                    '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')
    5372                ]
    5473            ],
  • cryptx/tags/4.0.0/classes/Admin/PresentationSettingsTab.php

    r3323475 r3339444  
    1313    }
    1414
     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     */
    1524    public function render(): void {
    1625        if ('presentation' !== $this->getActiveTab()) {
     
    108117    }
    109118
     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     */
    110129    private function renderTemplate(string $path, array $data): void {
    111130        if (!file_exists($path)) {
     
    117136    }
    118137
     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     */
    119143    private function getActiveTab(): string {
    120144        return sanitize_text_field($_GET['tab'] ?? 'general');
    121145    }
    122146
     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     */
    123152    private function getFontOptions(): array {
    124153        $fonts = [];
     
    132161    }
    133162
     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     */
    134173    private function getFilesInDirectory(string $path, array $extensions): array {
    135174        if (!is_dir($path)) {
     
    149188    }
    150189
     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     */
    151199    public function saveSettings(array $data): void {
    152200        if (!current_user_can('manage_options')) {
     
    156204        check_admin_referer('cryptX');
    157205
    158         $sanitized = $this->sanitizeSettings($data);
    159 
     206        // Handle reset button
    160207        if (!empty($_POST['cryptX_var_reset'])) {
    161208            $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            );
    162215            return;
    163216        }
    164217
     218        $sanitized = $this->sanitizeSettings($data);
    165219        $this->config->update($sanitized);
    166220
     
    173227    }
    174228
     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     */
    175238    private function sanitizeSettings(array $data): array {
    176239        $sanitized = [];
     
    180243        $sanitized['css_class'] = sanitize_html_class($data['css_class'] ?? '');
    181244
    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] ');
    185248
    186249        // Link text options
     
    199262        return $sanitized;
    200263    }
     264
    201265}
  • cryptx/tags/4.0.0/classes/Config.php

    r3323475 r3339444  
    3333        'whiteList' => 'jpeg,jpg,png,gif',
    3434        '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+)
    3545    ];
    3646
     
    99109    public function getVersion(): ?string {
    100110        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;
    101120    }
    102121
     
    123142        // Convert checkbox values to integers
    124143        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) {
    126145            if (isset($newOptions[$key])) {
    127146                $newOptions[$key] = (int)$newOptions[$key];
     
    165184        $this->save();
    166185    }
     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    }
    167225}
  • cryptx/tags/4.0.0/classes/CryptX.php

    r3335910 r3339444  
    2525    {
    2626        $this->settingsTabs = new CryptXSettingsTabs($this);
    27         $this->config = new Config( get_option('cryptX', []) );
     27        $this->config = new Config(get_option('cryptX', []));
    2828        self::$cryptXOptions = $this->loadCryptXOptionsWithDefaults();
    2929    }
     
    6262    {
    6363        $this->checkAndUpdateVersion();
     64        $this->addUniversalWidgetFilters(); // Add this line
    6465        $this->initializePluginFilters();
    6566        $this->registerCoreHooks();
     
    8687     * @return void
    8788     */
    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);
    93106            }
    94107        }
     
    236249        }
    237250
    238         // Process content
     251        // Process content (inline the encryptAndLinkContent logic)
    239252        if (self::$cryptXOptions['autolink'] ?? false) {
    240253            $content = $this->addLinkToEmailAddresses($content, true);
    241254        }
    242255
    243         $processedContent = $this->encryptAndLinkContent($content, true);
     256        $content = $this->findEmailAddressesInContent($content, true);
     257        $processedContent = $this->replaceEmailInContent($content, true);
    244258
    245259        // Reset options to defaults
     
    263277    }
    264278
    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     */
    306284    private function getCurrentPostId(): int
    307285    {
     
    362340
    363341    /**
    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 void
    373      */
    374     private function addPluginFilters(string $filterName): void
    375     {
    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     /**
    388342     * Adds common filters to a given filter name.
    389343     *
     
    412366    private function addOtherFilters(string $filterName): void
    413367    {
    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        }
    416397    }
    417398
     
    442423        global $post;
    443424
    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);
    445430
    446431        $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)) {
    448435            $content = $this->replaceEmailWithLinkText($content);
    449436        }
     
    451438        return $content;
    452439    }
     440
    453441
    454442    /**
     
    618606        global $post;
    619607
    620         if (self::$cryptXOptions['disable_rss'] && $this->isRssFeed())  return $content;
     608        if (self::$cryptXOptions['disable_rss'] && $this->isRssFeed()) return $content;
    621609
    622610        if ($content === null) {
     
    624612        }
    625613
     614        // Check if current filter is a widget filter
     615        $widgetFilters = $this->config->getWidgetFilters();
     616        $isWidgetContext = in_array(current_filter(), $widgetFilters);
     617
    626618        $postId = (is_object($post)) ? $post->ID : -1;
    627619        $isIdExcluded = $this->isIdExcluded($postId);
    628620
    629         // FIXED: Added 's' modifier to handle multiline HTML (like Elementor buttons)
    630621        $mailtoRegex = '/<a\s+[^>]*href=(["\'])mailto:([^"\']+)\1[^>]*>(.*?)<\/a>/is';
    631622
    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);
    634628        }
    635629
    636630        return $content;
    637631    }
     632
    638633
    639634    /**
     
    659654
    660655        $return = $originalValue;
    661        
     656
    662657        // Apply JavaScript handler if enabled
    663658        if (!empty(self::$cryptXOptions['java'])) {
     
    682677
    683678    /**
    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.
    691688     */
    692689    private function encryptEmailAddressNew(array $searchResults): string
     
    708705        // Apply JavaScript handler if enabled
    709706        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
    711730            $return = str_replace('mailto:' . $emailAddress, $javaHandler, $originalValue);
    712731        } 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);
    715735        }
    716736
    717737        // Add CSS attributes if specified
    718738        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);
    720741        }
    721742
    722743        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);
    724746        }
    725747
     
    768790    {
    769791        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
    770797        $postID = is_object($post) ? $post->ID : -1;
    771798
    772         if ($this->isIdExcluded($postID) && !$shortcode) {
     799        // For widgets, always process; for other content, check exclusion rules
     800        if (!$isWidgetContext && $this->isIdExcluded($postID) && !$shortcode) {
    773801            return $content;
    774802        }
    775803
    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,})";
    777805        $linkPattern = "<a href=\"mailto:\\2\">\\2</a>";
    778806        $src = [
    779             "/([\s])($emailPattern)/si",
     807            "/([\\s])($emailPattern)/si",
    780808            "/(>)($emailPattern)(<)/si",
    781             "/(\()($emailPattern)(\))/si",
    782             "/(>)($emailPattern)([\s])/si",
    783             "/([\s])($emailPattern)(<)/si",
     809            "/(\\()($emailPattern)(\\))/si",
     810            "/(>)($emailPattern)([\\s])/si",
     811            "/([\\s])($emailPattern)(<)/si",
    784812            "/^($emailPattern)/si",
    785813            "/(<a[^>]*>)<a[^>]*>/",
    786             "/(<\/A>)<\/A>/i"
     814            "/(<\\/A>)<\\/A>/i"
    787815        ];
    788816        $tar = [
     
    11141142    }
    11151143
     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     */
    11161150    public function convertArrayToArgumentString(array $args = []): string
    11171151    {
     
    11401174     * Adds plugin action links to the WordPress plugin row
    11411175     *
    1142      * @param array  $links Existing plugin row links
    1143      * @param string $file  Plugin file path
     1176     * @param array $links Existing plugin row links
     1177     * @param string $file Plugin file path
    11441178     * @return array Modified plugin row links
    11451179     */
     
    11591193
    11601194    /**
    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.
    11621198     */
    11631199    private function create_settings_link(): string
     
    11711207
    11721208    /**
    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.
    11741212     */
    11751213    private function create_donation_link(): string
     
    11811219        );
    11821220    }
     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
    11831387}
  • cryptx/tags/4.0.0/classes/CryptXSettingsTabs.php

    r3323475 r3339444  
    7979        );
    8080
     81        // Enqueue WordPress color picker assets
    8182        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
    9286        wp_enqueue_media();
    9387    }
     
    133127            $saveOptions = DataSanitizer::sanitize($_POST['cryptX_var']);
    134128
     129            // Handle reset for any tab
    135130            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;
    137135            }
    138136
     
    145143        }
    146144    }
    147 
    148145
    149146    /**
     
    173170            'cryptx_messages',
    174171            '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'),
    176186            'updated'
    177187        );
     
    294304
    295305            // 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'] ?? []);
    299309                }
    300310            }
     
    328338
    329339            // 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'] ?? []);
    333343                }
    334344            }
     
    375385        }
    376386    }
    377 
    378 
    379     /**
    380      * Parse and render changelog content from readme.txt
    381      */
    382     private function renderChangelogContent(): void
    383     {
    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.txt
    402      *
    403      * @param string $content
    404      * @return array
    405      */
    406     private function parseChangelog(string $content): array
    407     {
    408         $content = str_replace(["\r\n", "\r"], "\n", $content);
    409         $content = trim($content);
    410 
    411         // Split into sections
    412         $sections = $this->parseSections($content);
    413         if (!isset($sections['changelog'])) {
    414             return [];
    415         }
    416 
    417         // Parse changelog entries
    418         return $this->parseChangelogEntries($sections['changelog']['content']);
    419     }
    420 
    421     /**
    422      * Parse sections from readme content
    423      *
    424      * @param string $content
    425      * @return array
    426      */
    427     private function parseSections(string $content): array
    428     {
    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 entries
    445      *
    446      * @param string $content
    447      * @return array
    448      */
    449     private function parseChangelogEntries(string $content): array
    450     {
    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     }
    468387}
  • cryptx/tags/4.0.0/cryptx.php

    r3335910 r3339444  
    11<?php
    2 /*
    3  * Plugin Name: CryptX
    4  * 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.2
     2/**
     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
    77 * 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/
    1012 * License:           GPL v2 or later
    1113 * 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 */
    1435
    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
     37if (!defined('ABSPATH')) {
     38    exit;
    2039}
    2140
    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
     42define('CRYPTX_VERSION', '4.0.0');
     43define('CRYPTX_PLUGIN_FILE', __FILE__);
     44define('CRYPTX_PLUGIN_BASENAME', plugin_basename(__FILE__));
     45define('CRYPTX_BASENAME', plugin_basename(__FILE__)); // Add this missing constant
     46define('CRYPTX_DIR_PATH', plugin_dir_path(__FILE__));
     47define('CRYPTX_DIR_URL', plugin_dir_url(__FILE__));
     48define('CRYPTX_BASEFOLDER', dirname(CRYPTX_PLUGIN_BASENAME));
    2949
    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
     51if (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}
    3664
    37         // Construct the full file path
    38         $file = CRYPTX_DIR_PATH . 'classes' . DIRECTORY_SEPARATOR . $file_path . '.php';
     65// WordPress version check
     66global $wp_version;
     67if (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}
    3980
    40         if (file_exists($file)) {
    41             require_once $file;
    42         }
     81// Autoloader for plugin classes
     82spl_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;
    43100    }
    44101});
    45102
     103// Initialize the plugin
     104add_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    ];
    46116
    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    }
    49123
    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    }
    52132
    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
     147register_activation_hook(__FILE__, function() {
     148    // Just flush rewrite rules
     149    flush_rewrite_rules();
     150});
     151
     152// Plugin deactivation hook
     153register_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
     161add_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
     6const 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 */
     16class 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 */
     92class 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 */
     210class 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 */
     320async 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}
    9354 */
    10355function 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 */
     368function 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 */
     390async 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 */
     412function 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}
    47432 */
    48433function 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
     443if (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)}')`}
     1const 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  
    22Contributors: d3395
    33Donate 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.0
     4Tags: antispam, email, mail, addresses, spam protection, email encryption, privacy
     5Requires at least: 6.7
    66Tested up to: 6.8
    7 Stable tag: 3.5.2
    8 Requires PHP: 8.0
     7Stable tag: 4.0.0
     8Requires PHP: 8.1
    99License: GPLv2 or later
    1010License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    1616No 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.
    1717
     18CryptX 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
    1831[Plugin Homepage](http://weber-nrw.de/wordpress/cryptx/ "Plugin Homepage")
    1932
    2033== Screenshots ==
    2134
    22 1. Plugin settings
    23 2. Template functions
     351. Plugin settings - General configuration options
     362. Email encryption methods and display options
     373. Advanced settings and whitelist configuration
     38
     39== Installation ==
     40
     411. Upload the CryptX folder to the `/wp-content/plugins/` directory
     422. Activate the plugin through the 'Plugins' menu in WordPress
     433. Configure the plugin settings under Settings > CryptX
     444. Your email addresses will now be automatically protected!
     45
     46== Frequently Asked Questions ==
     47
     48= How does CryptX protect my email addresses? =
     49
     50CryptX 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
     54CryptX 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
     58Yes, you can use the whitelist feature to exclude specific domains or email addresses from encryption.
     59
     60= Does it work with contact forms? =
     61
     62CryptX 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
     66Yes, you can enable the meta box feature to control encryption on individual posts and pages.
     67
     68For more information, visit the [Plugin Homepage](http://weber-nrw.de/wordpress/cryptx/ "Plugin Homepage")
    2469
    2570== 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
    2682= 3.5.2 =
    2783* Fixed a bug where activating CryptX for the first time caused a PHP Fatal error
     
    165221* Add Option to disable CryptX on single post/page
    166222
    167 == Installation ==
    168 
    169 1. Upload "cryptX folder" to the `/wp-content/plugins/` directory
    170 2. Activate the plugin through the 'Plugins' menu in WordPress
    171 3. Edit the Options under the Options Page.
    172 4. Look at your Blog and be happy.
    173 
    174223== 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 =
     226Major 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 =
     229Bug 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  
    1111?>
    1212
    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]");
    123143// Returns: javascript:DeCryptX('1A2B3C...')</code></pre>
    124144
    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');
    127147link.href = generateDeCryptXHandler('[email protected]');
    128148link.textContent = "Contact Us";
    129149document.body.appendChild(link);</code></pre>
    130150
    131     <p>Direct HTML usage:</p>
    132     <pre><code class="language-html">&lt;a href="javascript:generateDeCryptXHandler('[email protected]')"&gt;Contact Us&lt;/a&gt;</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) {
    136153    try {
    137154        // Input validation
     
    155172    }
    156173}</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>
    219234</div>
    220235
    221236<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 {
    243246        background: #f4f4f4;
    244247        padding: 2px 6px;
     
    247250    }
    248251
    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 {
    257253        background: none;
    258254        padding: 0;
    259255    }
    260256
    261     .cryptx-documentation ul {
     257    .cryptx-howto-settings ul {
    262258        margin-left: 20px;
    263259    }
    264260
    265     .cryptx-documentation li {
     261    .cryptx-howto-settings li {
    266262        margin-bottom: 8px;
    267263    }
    268264
    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 {
    277266        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;
    280278    }
    281279</style>
  • cryptx/tags/4.0.0/templates/admin/tabs/presentation.php

    r3323475 r3339444  
    11<?php
    22/**
    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
    89 */
    9 
    10 defined('ABSPATH') || exit;
     10if (!defined('ABSPATH')) {
     11    exit;
     12}
    1113?>
    1214
    13     <h4><?php _e("Define CSS Options", 'cryptx'); ?></h4>
     15<div class="cryptx-tab-content cryptx-presentation-settings">
    1416    <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>
    1728            <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>
    2631            </td>
    2732        </tr>
    2833        <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>
    3035            <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>
    3638            </td>
    3739        </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>
    38199    </table>
    39200
    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  
    55use CryptX\Config;
    66
    7 /**
    8  * Class ChangelogSettingsTab
    9  * Handles the changelog tab functionality in the CryptX plugin admin interface
    10  */
    117class ChangelogSettingsTab
    128{
     
    1511     */
    1612    private Config $config;
     13
     14    /**
     15     * Template path
     16     */
     17    private const TEMPLATE_PATH = CRYPTX_DIR_PATH . 'templates/admin/tabs/changelog.php';
    1718
    1819    /**
     
    3132    public function render(): void
    3233    {
    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]);
    3540    }
    3641
    3742    /**
    38      * Parse and render changelog content from readme.txt
     43     * Get changelog data
    3944     */
    40     private function renderChangelogContent(): void
     45    private function getChangelogData(): array
    4146    {
    4247        $readmePath = CRYPTX_DIR_PATH . '/readme.txt';
    4348        if (!file_exists($readmePath)) {
    44             return;
     49            return [];
    4550        }
    4651
    4752        $fileContents = file_get_contents($readmePath);
    4853        if ($fileContents === false) {
    49             return;
     54            return [];
    5055        }
    5156
    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));
    5567        }
     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');
    5679    }
    5780
     
    89112
    90113        for ($i = 1; $i <= count($_sections); $i += 2) {
     114            if (!isset($_sections[$i - 1]) || !isset($_sections[$i])) {
     115                continue;
     116            }
     117
    91118            $title = $_sections[$i - 1];
    92119            $sections[str_replace(' ', '_', strtolower($title))] = [
     
    111138
    112139        for ($i = 1; $i <= count($_changelogs); $i += 2) {
     140            if (!isset($_changelogs[$i - 1]) || !isset($_changelogs[$i])) {
     141                continue;
     142            }
     143
    113144            $version = $_changelogs[$i - 1];
    114145            $content = ltrim($_changelogs[$i], "\n");
    115             $content = str_replace("* ", "<li>", $content);
    116             $content = str_replace("\n", " </li>\n", $content);
    117146
    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            }
    122156        }
    123157
    124158        return $changelogs;
    125159    }
     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    }
    126190}
  • cryptx/trunk/classes/Admin/GeneralSettingsTab.php

    r3323475 r3339444  
    1818        'disable_rss' => 0,
    1919        'java' => 1,
    20         'load_java' => 1
     20        'load_java' => 1,
     21        'use_secure_encryption' => 0,
     22        'encryption_mode' => 'legacy'
    2123    ];
     24
    2225    public function __construct(Config $config) {
    2326        $this->config = $config;
     
    3336        $settings = [
    3437            '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            ],
    3554            'applyTo' => [
    3655                'the_content' => [
     
    5069                'widget_text' => [
    5170                    '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')
    5372                ]
    5473            ],
  • cryptx/trunk/classes/Admin/PresentationSettingsTab.php

    r3323475 r3339444  
    1313    }
    1414
     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     */
    1524    public function render(): void {
    1625        if ('presentation' !== $this->getActiveTab()) {
     
    108117    }
    109118
     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     */
    110129    private function renderTemplate(string $path, array $data): void {
    111130        if (!file_exists($path)) {
     
    117136    }
    118137
     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     */
    119143    private function getActiveTab(): string {
    120144        return sanitize_text_field($_GET['tab'] ?? 'general');
    121145    }
    122146
     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     */
    123152    private function getFontOptions(): array {
    124153        $fonts = [];
     
    132161    }
    133162
     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     */
    134173    private function getFilesInDirectory(string $path, array $extensions): array {
    135174        if (!is_dir($path)) {
     
    149188    }
    150189
     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     */
    151199    public function saveSettings(array $data): void {
    152200        if (!current_user_can('manage_options')) {
     
    156204        check_admin_referer('cryptX');
    157205
    158         $sanitized = $this->sanitizeSettings($data);
    159 
     206        // Handle reset button
    160207        if (!empty($_POST['cryptX_var_reset'])) {
    161208            $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            );
    162215            return;
    163216        }
    164217
     218        $sanitized = $this->sanitizeSettings($data);
    165219        $this->config->update($sanitized);
    166220
     
    173227    }
    174228
     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     */
    175238    private function sanitizeSettings(array $data): array {
    176239        $sanitized = [];
     
    180243        $sanitized['css_class'] = sanitize_html_class($data['css_class'] ?? '');
    181244
    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] ');
    185248
    186249        // Link text options
     
    199262        return $sanitized;
    200263    }
     264
    201265}
  • cryptx/trunk/classes/Config.php

    r3323475 r3339444  
    3333        'whiteList' => 'jpeg,jpg,png,gif',
    3434        '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+)
    3545    ];
    3646
     
    99109    public function getVersion(): ?string {
    100110        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;
    101120    }
    102121
     
    123142        // Convert checkbox values to integers
    124143        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) {
    126145            if (isset($newOptions[$key])) {
    127146                $newOptions[$key] = (int)$newOptions[$key];
     
    165184        $this->save();
    166185    }
     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    }
    167225}
  • cryptx/trunk/classes/CryptX.php

    r3335910 r3339444  
    2525    {
    2626        $this->settingsTabs = new CryptXSettingsTabs($this);
    27         $this->config = new Config( get_option('cryptX', []) );
     27        $this->config = new Config(get_option('cryptX', []));
    2828        self::$cryptXOptions = $this->loadCryptXOptionsWithDefaults();
    2929    }
     
    6262    {
    6363        $this->checkAndUpdateVersion();
     64        $this->addUniversalWidgetFilters(); // Add this line
    6465        $this->initializePluginFilters();
    6566        $this->registerCoreHooks();
     
    8687     * @return void
    8788     */
    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);
    93106            }
    94107        }
     
    236249        }
    237250
    238         // Process content
     251        // Process content (inline the encryptAndLinkContent logic)
    239252        if (self::$cryptXOptions['autolink'] ?? false) {
    240253            $content = $this->addLinkToEmailAddresses($content, true);
    241254        }
    242255
    243         $processedContent = $this->encryptAndLinkContent($content, true);
     256        $content = $this->findEmailAddressesInContent($content, true);
     257        $processedContent = $this->replaceEmailInContent($content, true);
    244258
    245259        // Reset options to defaults
     
    263277    }
    264278
    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     */
    306284    private function getCurrentPostId(): int
    307285    {
     
    362340
    363341    /**
    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 void
    373      */
    374     private function addPluginFilters(string $filterName): void
    375     {
    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     /**
    388342     * Adds common filters to a given filter name.
    389343     *
     
    412366    private function addOtherFilters(string $filterName): void
    413367    {
    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        }
    416397    }
    417398
     
    442423        global $post;
    443424
    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);
    445430
    446431        $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)) {
    448435            $content = $this->replaceEmailWithLinkText($content);
    449436        }
     
    451438        return $content;
    452439    }
     440
    453441
    454442    /**
     
    618606        global $post;
    619607
    620         if (self::$cryptXOptions['disable_rss'] && $this->isRssFeed())  return $content;
     608        if (self::$cryptXOptions['disable_rss'] && $this->isRssFeed()) return $content;
    621609
    622610        if ($content === null) {
     
    624612        }
    625613
     614        // Check if current filter is a widget filter
     615        $widgetFilters = $this->config->getWidgetFilters();
     616        $isWidgetContext = in_array(current_filter(), $widgetFilters);
     617
    626618        $postId = (is_object($post)) ? $post->ID : -1;
    627619        $isIdExcluded = $this->isIdExcluded($postId);
    628620
    629         // FIXED: Added 's' modifier to handle multiline HTML (like Elementor buttons)
    630621        $mailtoRegex = '/<a\s+[^>]*href=(["\'])mailto:([^"\']+)\1[^>]*>(.*?)<\/a>/is';
    631622
    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);
    634628        }
    635629
    636630        return $content;
    637631    }
     632
    638633
    639634    /**
     
    659654
    660655        $return = $originalValue;
    661        
     656
    662657        // Apply JavaScript handler if enabled
    663658        if (!empty(self::$cryptXOptions['java'])) {
     
    682677
    683678    /**
    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.
    691688     */
    692689    private function encryptEmailAddressNew(array $searchResults): string
     
    708705        // Apply JavaScript handler if enabled
    709706        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
    711730            $return = str_replace('mailto:' . $emailAddress, $javaHandler, $originalValue);
    712731        } 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);
    715735        }
    716736
    717737        // Add CSS attributes if specified
    718738        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);
    720741        }
    721742
    722743        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);
    724746        }
    725747
     
    768790    {
    769791        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
    770797        $postID = is_object($post) ? $post->ID : -1;
    771798
    772         if ($this->isIdExcluded($postID) && !$shortcode) {
     799        // For widgets, always process; for other content, check exclusion rules
     800        if (!$isWidgetContext && $this->isIdExcluded($postID) && !$shortcode) {
    773801            return $content;
    774802        }
    775803
    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,})";
    777805        $linkPattern = "<a href=\"mailto:\\2\">\\2</a>";
    778806        $src = [
    779             "/([\s])($emailPattern)/si",
     807            "/([\\s])($emailPattern)/si",
    780808            "/(>)($emailPattern)(<)/si",
    781             "/(\()($emailPattern)(\))/si",
    782             "/(>)($emailPattern)([\s])/si",
    783             "/([\s])($emailPattern)(<)/si",
     809            "/(\\()($emailPattern)(\\))/si",
     810            "/(>)($emailPattern)([\\s])/si",
     811            "/([\\s])($emailPattern)(<)/si",
    784812            "/^($emailPattern)/si",
    785813            "/(<a[^>]*>)<a[^>]*>/",
    786             "/(<\/A>)<\/A>/i"
     814            "/(<\\/A>)<\\/A>/i"
    787815        ];
    788816        $tar = [
     
    11141142    }
    11151143
     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     */
    11161150    public function convertArrayToArgumentString(array $args = []): string
    11171151    {
     
    11401174     * Adds plugin action links to the WordPress plugin row
    11411175     *
    1142      * @param array  $links Existing plugin row links
    1143      * @param string $file  Plugin file path
     1176     * @param array $links Existing plugin row links
     1177     * @param string $file Plugin file path
    11441178     * @return array Modified plugin row links
    11451179     */
     
    11591193
    11601194    /**
    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.
    11621198     */
    11631199    private function create_settings_link(): string
     
    11711207
    11721208    /**
    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.
    11741212     */
    11751213    private function create_donation_link(): string
     
    11811219        );
    11821220    }
     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
    11831387}
  • cryptx/trunk/classes/CryptXSettingsTabs.php

    r3323475 r3339444  
    7979        );
    8080
     81        // Enqueue WordPress color picker assets
    8182        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
    9286        wp_enqueue_media();
    9387    }
     
    133127            $saveOptions = DataSanitizer::sanitize($_POST['cryptX_var']);
    134128
     129            // Handle reset for any tab
    135130            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;
    137135            }
    138136
     
    145143        }
    146144    }
    147 
    148145
    149146    /**
     
    173170            'cryptx_messages',
    174171            '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'),
    176186            'updated'
    177187        );
     
    294304
    295305            // 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'] ?? []);
    299309                }
    300310            }
     
    328338
    329339            // 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'] ?? []);
    333343                }
    334344            }
     
    375385        }
    376386    }
    377 
    378 
    379     /**
    380      * Parse and render changelog content from readme.txt
    381      */
    382     private function renderChangelogContent(): void
    383     {
    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.txt
    402      *
    403      * @param string $content
    404      * @return array
    405      */
    406     private function parseChangelog(string $content): array
    407     {
    408         $content = str_replace(["\r\n", "\r"], "\n", $content);
    409         $content = trim($content);
    410 
    411         // Split into sections
    412         $sections = $this->parseSections($content);
    413         if (!isset($sections['changelog'])) {
    414             return [];
    415         }
    416 
    417         // Parse changelog entries
    418         return $this->parseChangelogEntries($sections['changelog']['content']);
    419     }
    420 
    421     /**
    422      * Parse sections from readme content
    423      *
    424      * @param string $content
    425      * @return array
    426      */
    427     private function parseSections(string $content): array
    428     {
    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 entries
    445      *
    446      * @param string $content
    447      * @return array
    448      */
    449     private function parseChangelogEntries(string $content): array
    450     {
    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     }
    468387}
  • cryptx/trunk/cryptx.php

    r3335910 r3339444  
    11<?php
    2 /*
    3  * Plugin Name: CryptX
    4  * 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.2
     2/**
     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
    77 * 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/
    1012 * License:           GPL v2 or later
    1113 * 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 */
    1435
    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
     37if (!defined('ABSPATH')) {
     38    exit;
    2039}
    2140
    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
     42define('CRYPTX_VERSION', '4.0.0');
     43define('CRYPTX_PLUGIN_FILE', __FILE__);
     44define('CRYPTX_PLUGIN_BASENAME', plugin_basename(__FILE__));
     45define('CRYPTX_BASENAME', plugin_basename(__FILE__)); // Add this missing constant
     46define('CRYPTX_DIR_PATH', plugin_dir_path(__FILE__));
     47define('CRYPTX_DIR_URL', plugin_dir_url(__FILE__));
     48define('CRYPTX_BASEFOLDER', dirname(CRYPTX_PLUGIN_BASENAME));
    2949
    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
     51if (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}
    3664
    37         // Construct the full file path
    38         $file = CRYPTX_DIR_PATH . 'classes' . DIRECTORY_SEPARATOR . $file_path . '.php';
     65// WordPress version check
     66global $wp_version;
     67if (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}
    3980
    40         if (file_exists($file)) {
    41             require_once $file;
    42         }
     81// Autoloader for plugin classes
     82spl_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;
    43100    }
    44101});
    45102
     103// Initialize the plugin
     104add_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    ];
    46116
    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    }
    49123
    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    }
    52132
    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
     147register_activation_hook(__FILE__, function() {
     148    // Just flush rewrite rules
     149    flush_rewrite_rules();
     150});
     151
     152// Plugin deactivation hook
     153register_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
     161add_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
     6const 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 */
     16class 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 */
     92class 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 */
     210class 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 */
     320async 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}
    9354 */
    10355function 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 */
     368function 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 */
     390async 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 */
     412function 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}
    47432 */
    48433function 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
     443if (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)}')`}
     1const 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  
    22Contributors: d3395
    33Donate 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.0
     4Tags: antispam, email, mail, addresses, spam protection, email encryption, privacy
     5Requires at least: 6.7
    66Tested up to: 6.8
    7 Stable tag: 3.5.2
    8 Requires PHP: 8.0
     7Stable tag: 4.0.0
     8Requires PHP: 8.1
    99License: GPLv2 or later
    1010License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    1616No 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.
    1717
     18CryptX 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
    1831[Plugin Homepage](http://weber-nrw.de/wordpress/cryptx/ "Plugin Homepage")
    1932
    2033== Screenshots ==
    2134
    22 1. Plugin settings
    23 2. Template functions
     351. Plugin settings - General configuration options
     362. Email encryption methods and display options
     373. Advanced settings and whitelist configuration
     38
     39== Installation ==
     40
     411. Upload the CryptX folder to the `/wp-content/plugins/` directory
     422. Activate the plugin through the 'Plugins' menu in WordPress
     433. Configure the plugin settings under Settings > CryptX
     444. Your email addresses will now be automatically protected!
     45
     46== Frequently Asked Questions ==
     47
     48= How does CryptX protect my email addresses? =
     49
     50CryptX 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
     54CryptX 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
     58Yes, you can use the whitelist feature to exclude specific domains or email addresses from encryption.
     59
     60= Does it work with contact forms? =
     61
     62CryptX 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
     66Yes, you can enable the meta box feature to control encryption on individual posts and pages.
     67
     68For more information, visit the [Plugin Homepage](http://weber-nrw.de/wordpress/cryptx/ "Plugin Homepage")
    2469
    2570== 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
    2682= 3.5.2 =
    2783* Fixed a bug where activating CryptX for the first time caused a PHP Fatal error
     
    165221* Add Option to disable CryptX on single post/page
    166222
    167 == Installation ==
    168 
    169 1. Upload "cryptX folder" to the `/wp-content/plugins/` directory
    170 2. Activate the plugin through the 'Plugins' menu in WordPress
    171 3. Edit the Options under the Options Page.
    172 4. Look at your Blog and be happy.
    173 
    174223== 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 =
     226Major 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 =
     229Bug 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  
    1111?>
    1212
    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]");
    123143// Returns: javascript:DeCryptX('1A2B3C...')</code></pre>
    124144
    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');
    127147link.href = generateDeCryptXHandler('[email protected]');
    128148link.textContent = "Contact Us";
    129149document.body.appendChild(link);</code></pre>
    130150
    131     <p>Direct HTML usage:</p>
    132     <pre><code class="language-html">&lt;a href="javascript:generateDeCryptXHandler('[email protected]')"&gt;Contact Us&lt;/a&gt;</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) {
    136153    try {
    137154        // Input validation
     
    155172    }
    156173}</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>
    219234</div>
    220235
    221236<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 {
    243246        background: #f4f4f4;
    244247        padding: 2px 6px;
     
    247250    }
    248251
    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 {
    257253        background: none;
    258254        padding: 0;
    259255    }
    260256
    261     .cryptx-documentation ul {
     257    .cryptx-howto-settings ul {
    262258        margin-left: 20px;
    263259    }
    264260
    265     .cryptx-documentation li {
     261    .cryptx-howto-settings li {
    266262        margin-bottom: 8px;
    267263    }
    268264
    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 {
    277266        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;
    280278    }
    281279</style>
  • cryptx/trunk/templates/admin/tabs/presentation.php

    r3323475 r3339444  
    11<?php
    22/**
    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
    89 */
    9 
    10 defined('ABSPATH') || exit;
     10if (!defined('ABSPATH')) {
     11    exit;
     12}
    1113?>
    1214
    13     <h4><?php _e("Define CSS Options", 'cryptx'); ?></h4>
     15<div class="cryptx-tab-content cryptx-presentation-settings">
    1416    <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>
    1728            <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>
    2631            </td>
    2732        </tr>
    2833        <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>
    3035            <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>
    3638            </td>
    3739        </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>
    38199    </table>
    39200
    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.