Changeset 3323475
- Timestamp:
- 07/07/2025 11:17:17 AM (9 months ago)
- Location:
- cryptx
- Files:
-
- 30 added
- 10 edited
- 1 copied
-
tags/3.5.0 (copied) (copied from cryptx/trunk)
-
tags/3.5.0/classes/Admin (added)
-
tags/3.5.0/classes/Admin/ChangelogSettingsTab.php (added)
-
tags/3.5.0/classes/Admin/GeneralSettingsTab.php (added)
-
tags/3.5.0/classes/Admin/PresentationSettingsTab.php (added)
-
tags/3.5.0/classes/Config.php (added)
-
tags/3.5.0/classes/CryptX.php (modified) (1 diff)
-
tags/3.5.0/classes/CryptXSettingsTabs.php (added)
-
tags/3.5.0/classes/EmailProcessingConfig.php (added)
-
tags/3.5.0/classes/Util (added)
-
tags/3.5.0/classes/Util/DataSanitizer.php (added)
-
tags/3.5.0/cryptx.php (modified) (3 diffs)
-
tags/3.5.0/js/cryptx.js (modified) (1 diff)
-
tags/3.5.0/js/cryptx.min.js (modified) (1 diff)
-
tags/3.5.0/readme.txt (modified) (2 diffs)
-
tags/3.5.0/templates (added)
-
tags/3.5.0/templates/admin (added)
-
tags/3.5.0/templates/admin/tabs (added)
-
tags/3.5.0/templates/admin/tabs/general.php (added)
-
tags/3.5.0/templates/admin/tabs/howto.php (added)
-
tags/3.5.0/templates/admin/tabs/presentation.php (added)
-
trunk/classes/Admin (added)
-
trunk/classes/Admin/ChangelogSettingsTab.php (added)
-
trunk/classes/Admin/GeneralSettingsTab.php (added)
-
trunk/classes/Admin/PresentationSettingsTab.php (added)
-
trunk/classes/Config.php (added)
-
trunk/classes/CryptX.php (modified) (1 diff)
-
trunk/classes/CryptXSettingsTabs.php (added)
-
trunk/classes/EmailProcessingConfig.php (added)
-
trunk/classes/Util (added)
-
trunk/classes/Util/DataSanitizer.php (added)
-
trunk/cryptx.php (modified) (3 diffs)
-
trunk/js/cryptx.js (modified) (1 diff)
-
trunk/js/cryptx.min.js (modified) (1 diff)
-
trunk/readme.txt (modified) (2 diffs)
-
trunk/templates (added)
-
trunk/templates/admin (added)
-
trunk/templates/admin/tabs (added)
-
trunk/templates/admin/tabs/general.php (added)
-
trunk/templates/admin/tabs/howto.php (added)
-
trunk/templates/admin/tabs/presentation.php (added)
Legend:
- Unmodified
- Added
- Removed
-
cryptx/tags/3.5.0/classes/CryptX.php
r3103653 r3323475 3 3 namespace CryptX; 4 4 5 Final class CryptX { 6 7 const NOT_FOUND = false; 8 const MAIL_IDENTIFIER = 'mailto:'; 9 const SUBJECT_IDENTIFIER = "?subject="; 10 const INDEX_TO_CHECK = 4; 11 const PATTERN = '/(.*)(">)/i'; 12 const ASCII_VALUES_BLACKLIST = [ '32', '34', '39', '60', '62', '63', '92', '94', '96', '127' ]; 13 14 private static ?CryptX $_instance = null; 15 private static array $cryptXOptions = []; 16 private static array $defaults = array( 17 'version' => null, 18 'at' => ' [at] ', 19 'dot' => ' [dot] ', 20 'css_id' => '', 21 'css_class' => '', 22 'the_content' => 1, 23 'the_meta_key' => 1, 24 'the_excerpt' => 1, 25 'comment_text' => 1, 26 'widget_text' => 1, 27 'java' => 1, 28 'load_java' => 1, 29 'opt_linktext' => 0, 30 'autolink' => 1, 31 'alt_linktext' => '', 32 'alt_linkimage' => '', 33 'http_linkimage_title' => '', 34 'alt_linkimage_title' => '', 35 'excludedIDs' => '', 36 'metaBox' => 1, 37 'alt_uploadedimage' => '0', 38 'c2i_font' => null, 39 'c2i_fontSize' => 10, 40 'c2i_fontRGB' => '#000000', 41 'echo' => 1, 42 'filter' => array( 'the_content', 'the_meta_key', 'the_excerpt', 'comment_text', 'widget_text' ), 43 'whiteList' => 'jpeg,jpg,png,gif', 44 ); 45 private static int $imageCounter = 0; 46 47 private function __construct() { 48 self::$cryptXOptions = $this->loadCryptXOptionsWithDefaults(); 49 } 50 51 /** 52 * Get the instance of the CryptX class. 53 * 54 * This method returns the instance of the CryptX class. If an instance does not exist, 55 * it creates a new instance of the CryptX class and stores it in the static property. 56 * Subsequent calls to this method will return the previously created instance. 57 * 58 * @return CryptX The instance of the CryptX class. 59 */ 60 public static function getInstance(): CryptX { 61 if ( ! ( self::$_instance instanceof self ) ) { 62 self::$_instance = new self(); 63 } 64 65 return self::$_instance; 66 } 67 68 /** 69 * Starts the CryptX plugin. 70 * 71 * This method initializes and configures the CryptX plugin by performing the following actions: 72 * - Updates CryptX settings if a new version is available 73 * - Adds plugin filters based on the configured options 74 * - Adds action hooks for plugin activation, enqueueing JavaScript files, and handling meta box functionality 75 * - Adds a plugin row meta filter 76 * - Adds a filter for generating tiny URLs 77 * - Adds a shortcode for CryptX functionality 78 * 79 * @return void 80 */ 81 public function startCryptX(): void { 82 if ( isset( self::$cryptXOptions['version'] ) && version_compare( CRYPTX_VERSION, self::$cryptXOptions['version'] ) > 0 ) { 83 $this->updateCryptXSettings(); 84 } 85 foreach ( self::$cryptXOptions['filter'] as $filter ) { 86 if ( @self::$cryptXOptions[ $filter ] ) { 87 $this->addPluginFilters( $filter ); 88 } 89 } 90 add_action( 'activate_' . CRYPTX_BASENAME, [ $this, 'installCryptX' ] ); 91 add_action( 'wp_enqueue_scripts', [ $this, 'loadJavascriptFiles' ] ); 92 if ( @self::$cryptXOptions['metaBox'] ) { 93 add_action( 'admin_menu', [ $this, 'metaBox' ] ); 94 add_action( 'wp_insert_post', [ $this, 'addPostIdToExcludedList' ] ); 95 add_action( 'wp_update_post', [ $this, 'addPostIdToExcludedList' ] ); 96 } 97 add_filter( 'plugin_row_meta', 'rw_cryptx_init_row_meta', 10, 2 ); 98 add_filter( 'init', [ $this, 'cryptXtinyUrl' ] ); 99 add_shortcode( 'cryptx', [ $this, 'cryptXShortcode' ] ); 100 } 101 102 /** 103 * Returns an array of default options for CryptX. 104 * 105 * This function retrieves an array of default options for CryptX. The default options include 106 * the current version of CryptX and the first available TrueType font from the "fonts" directory. 107 * 108 * @return array The array of default options. 109 */ 110 public function getCryptXOptionsDefaults(): array { 111 $firstFont = $this->getFilesInDirectory( CRYPTX_DIR_PATH . 'fonts', [ "ttf" ] ); 112 113 return array_merge( self::$defaults, [ 'version' => CRYPTX_VERSION, 'c2i_font' => $firstFont[0] ] ); 114 } 115 116 /** 117 * Loads the cryptX options with default values. 118 * 119 * @return array The cryptX options array with default values. 120 */ 121 public function loadCryptXOptionsWithDefaults(): array { 122 $defaultValues = $this->getCryptXOptionsDefaults(); 123 $currentOptions = get_option( 'cryptX' ); 124 125 return wp_parse_args( $currentOptions, $defaultValues ); 126 } 127 128 /** 129 * Saves the cryptX options by updating the 'cryptX' option with the saved options merged with the default options. 130 * 131 * @param array $saveOptions The options to be saved. 132 * 133 * @return void 134 */ 135 public function saveCryptXOptions( array $saveOptions ): void { 136 update_option( 'cryptX', wp_parse_args( $saveOptions, $this->loadCryptXOptionsWithDefaults() ) ); 137 } 138 139 /** 140 * Generates a shortcode for encrypting email addresses in search results. 141 * 142 * @param array $atts An associative array of attributes for the shortcode. 143 * @param string $content The content inside the shortcode. 144 * @param string $tag The shortcode tag. 145 * 146 * @return string The encrypted search results content. 147 */ 148 public function cryptXShortcode( array $atts = [], string $content = '', string $tag = '' ): string { 149 if ( isset( $atts['encoded'] ) && $atts['encoded'] == "true" ) { 150 foreach ( $atts as $key => $value ) { 151 $atts[ $key ] = $this->decodeString( $value ); 152 } 153 unset( $atts['encoded'] ); 154 } 155 if(!empty($atts)) self::$cryptXOptions = shortcode_atts( $this->loadCryptXOptionsWithDefaults(), array_change_key_case( $atts, CASE_LOWER ), $tag ); 156 if ( @self::$cryptXOptions['autolink'] ) { 157 $content = $this->addLinkToEmailAddresses( $content, true ); 158 } 159 $content = $this->encryptAndLinkContent( $content, true ); 160 // reset CryptX options 161 self::$cryptXOptions = $this->loadCryptXOptionsWithDefaults(); 162 163 return $content; 164 } 165 166 /** 167 * Encrypts and links content. 168 * 169 * @param string $content The content to be encrypted and linked. 170 * 171 * @return string The encrypted and linked content. 172 */ 173 private function encryptAndLinkContent( string $content, bool $shortcode = false ): string { 174 $content = $this->findEmailAddressesInContent( $content, $shortcode ); 175 176 return $this->replaceEmailInContent( $content, $shortcode ); 177 } 178 179 /** 180 * Generates and returns a tiny URL image. 181 * 182 * @return void 183 */ 184 public function cryptXtinyUrl(): void { 185 $url = $_SERVER['REQUEST_URI']; 186 $params = explode( '/', $url ); 187 if ( count( $params ) > 1 ) { 188 $tiny_url = $params[ count( $params ) - 2 ]; 189 if ( $tiny_url == md5( get_bloginfo( 'url' ) ) ) { 190 $font = CRYPTX_DIR_PATH . 'fonts/' . self::$cryptXOptions['c2i_font']; 191 $msg = $params[ count( $params ) - 1 ]; 192 $size = self::$cryptXOptions['c2i_fontSize']; 193 $pad = 1; 194 $transparent = 1; 195 $rgb = str_replace( "#", "", self::$cryptXOptions['c2i_fontRGB'] ); 196 $red = hexdec( substr( $rgb, 0, 2 ) ); 197 $grn = hexdec( substr( $rgb, 2, 2 ) ); 198 $blu = hexdec( substr( $rgb, 4, 2 ) ); 199 $bg_red = 255 - $red; 200 $bg_grn = 255 - $grn; 201 $bg_blu = 255 - $blu; 202 $width = 0; 203 $height = 0; 204 $offset_x = 0; 205 $offset_y = 0; 206 $bounds = array(); 207 $image = ""; 208 $bounds = ImageTTFBBox( $size, 0, $font, "W" ); 209 $font_height = abs( $bounds[7] - $bounds[1] ); 210 $bounds = ImageTTFBBox( $size, 0, $font, $msg ); 211 $width = abs( $bounds[4] - $bounds[6] ); 212 $height = abs( $bounds[7] - $bounds[1] ); 213 $offset_y = $font_height + abs( ( $height - $font_height ) / 2 ) - 1; 214 $offset_x = 0; 215 $image = imagecreatetruecolor( $width + ( $pad * 2 ), $height + ( $pad * 2 ) ); 216 imagesavealpha( $image, true ); 217 $foreground = ImageColorAllocate( $image, $red, $grn, $blu ); 218 $background = imagecolorallocatealpha( $image, 0, 0, 0, 127 ); 219 imagefill( $image, 0, 0, $background ); 220 ImageTTFText( $image, $size, 0, round( $offset_x + $pad, 0 ), round( $offset_y + $pad, 0 ), $foreground, $font, $msg ); 221 Header( "Content-type: image/png" ); 222 imagePNG( $image ); 223 die; 224 } 225 } 226 } 227 228 /** 229 * Add plugin filters. 230 * 231 * This function adds the specified plugin filter if the 'autolink' key is present and its value is true in the global $cryptXOptions variable. 232 * It also adds the 'autolink' function as a filter to the $filterName if the global $shortcode_tags variable is not empty. 233 * Additionally, this function calls the addCommonFilters() and addOtherFilters() functions at specific points. 234 * 235 * @param string $filterName The name of the filter to add. 236 * 237 * @return void 238 */ 239 private function addPluginFilters( string $filterName ): void { 240 global $shortcode_tags; 241 if ( array_key_exists( 'autolink', self::$cryptXOptions ) && self::$cryptXOptions['autolink'] ) { 242 $this->addAutoLinkFilters( $filterName ); 243 if ( ! empty( $shortcode_tags ) ) { 244 $this->addAutoLinkFilters( $filterName, 11 ); 245 //add_filter($filterName, [$this,'autolink'], 11); 246 } 247 } 248 $this->addOtherFilters( $filterName ); 249 } 250 251 /** 252 * Adds common filters to a given filter name. 253 * 254 * This function adds the common filter 'autolink' to the provided $filterName. 255 * 256 * @param string $filterName The name of the filter to add common filters to. 257 * 258 * @return void 259 */ 260 private function addAutoLinkFilters( string $filterName, $prio = 5 ): void { 261 add_filter( $filterName, [ $this, 'addLinkToEmailAddresses' ], $prio ); 262 } 263 264 /** 265 * Adds additional filters to a given filter name. 266 * 267 * This function adds two additional filters, 'encryptx' and 'replaceEmailInContent', 268 * to the specified filter name. The 'encryptx' filter is added with a priority of 12, 269 * and the 'replaceEmailInContent' filter is added with a priority of 13. 270 * 271 * @param string $filterName The name of the filter to add the additional filters to. 272 * 273 * @return void 274 */ 275 private function addOtherFilters( string $filterName ): void { 276 add_filter( $filterName, [ $this, 'findEmailAddressesInContent' ], 12 ); 277 add_filter( $filterName, [ $this, 'replaceEmailInContent' ], 13 ); 278 } 279 280 /** 281 * Checks if a given ID is excluded based on the 'excludedIDs' variable. 282 * 283 * @param int $ID The ID to check if excluded. 284 * 285 * @return bool Returns true if the ID is excluded, false otherwise. 286 */ 287 private function isIdExcluded( int $ID ): bool { 288 $excludedIds = explode( ",", self::$cryptXOptions['excludedIDs'] ); 289 290 return in_array( $ID, $excludedIds ); 291 } 292 293 /** 294 * Replaces email addresses in content with link texts. 295 * 296 * @param string|null $content The content to replace the email addresses in. 297 * @param bool $isShortcode Flag indicating whether the method is called from a shortcode. 298 * 299 * @return string|null The content with replaced email addresses. 300 */ 301 public function replaceEmailInContent( ?string $content, bool $isShortcode = false ): ?string { 302 global $post; 303 $postId = ( is_object( $post ) ) ? $post->ID : - 1; 304 if (( ! $this->isIdExcluded( $postId ) || $isShortcode ) && !empty($content) ) { 305 $content = $this->replaceEmailWithLinkText( $content ); 306 } 307 308 return $content; 309 } 310 311 /** 312 * Replace email addresses in a given content with link text. 313 * 314 * @param string $content The content to search for email addresses. 315 * 316 * @return string The content with email addresses replaced with link text. 317 */ 318 private function replaceEmailWithLinkText( string $content ): string { 319 $emailPattern = "/([_a-zA-Z0-9-+]+(\.[_a-zA-Z0-9-+]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(\.[a-zA-Z]{2,}))/i"; 320 321 return preg_replace_callback( $emailPattern, [ $this, 'encodeEmailToLinkText' ], $content ); 322 } 323 324 /** 325 * Encode email address to link text. 326 * 327 * @param array $Match The matched email address. 328 * 329 * @return string The encoded link text. 330 */ 331 private function encodeEmailToLinkText( array $Match ): string { 332 if ( $this->inWhiteList( $Match ) ) { 333 return $Match[1]; 334 } 335 switch ( self::$cryptXOptions['opt_linktext'] ) { 336 case 1: 337 $text = $this->getLinkText(); 338 break; 339 case 2: 340 $text = $this->getLinkImage(); 341 break; 342 case 3: 343 $img_url = wp_get_attachment_url( self::$cryptXOptions['alt_uploadedimage'] ); 344 $text = $this->getUploadedImage( $img_url ); 345 self::$imageCounter ++; 346 break; 347 case 4: 348 $text = antispambot( $Match[1] ); 349 break; 350 case 5: 351 $text = $this->getImageFromText( $Match ); 352 self::$imageCounter ++; 353 break; 354 default: 355 $text = $this->getDefaultLinkText( $Match ); 356 } 357 358 return $text; 359 } 360 361 /** 362 * Check if the given match is in the whitelist. 363 * 364 * @param array $Match The match to check against the whitelist. 365 * 366 * @return bool True if the match is in the whitelist, false otherwise. 367 */ 368 private function inWhiteList( array $Match ): bool { 369 $whiteList = array_filter( array_map( 'trim', explode( ",", self::$cryptXOptions['whiteList'] ) ) ); 370 $tmp = explode( ".", $Match[0] ); 371 372 return in_array( end( $tmp ), $whiteList ); 373 } 374 375 /** 376 * Get the link text from cryptXOptions 377 * 378 * @return string The link text 379 */ 380 private function getLinkText(): string { 381 return self::$cryptXOptions['alt_linktext']; 382 } 383 384 /** 385 * Generate an HTML image tag with the link image URL as the source 386 * 387 * @return string The HTML image tag 388 */ 389 private function getLinkImage(): string { 390 return "<img src=\"" . self::$cryptXOptions['alt_linkimage'] . "\" class=\"cryptxImage\" alt=\"" . self::$cryptXOptions['alt_linkimage_title'] . "\" title=\"" . antispambot( self::$cryptXOptions['alt_linkimage_title'] ) . "\" />"; 391 } 392 393 /** 394 * Get the HTML tag for an uploaded image. 395 * 396 * @param string $img_url The URL of the image. 397 * 398 * @return string The HTML tag for the image. 399 */ 400 private function getUploadedImage( string $img_url ): string { 401 return "<img src=\"" . $img_url . "\" class=\"cryptxImage cryptxImage_" . self::$imageCounter . "\" alt=\"" . self::$cryptXOptions['http_linkimage_title'] . " title=\"" . antispambot( self::$cryptXOptions['http_linkimage_title'] ) . "\" />"; 402 } 403 404 /** 405 * Converts a matched image URL into an HTML image element with cryptX classes and attributes. 406 * 407 * @param array $Match The matched image URL and other related data. 408 * 409 * @return string Returns the HTML image element. 410 */ 411 private function getImageFromText( array $Match ): string { 412 return "<img src=\"" . get_bloginfo( 'url' ) . "/" . md5( get_bloginfo( 'url' ) ) . "/" . antispambot( $Match[1] ) . "\" class=\"cryptxImage cryptxImage_" . self::$imageCounter . "\" alt=\"" . antispambot( $Match[1] ) . "\" title=\"" . antispambot( $Match[1] ) . "\" />"; 413 } 414 415 /** 416 * Replaces specific characters with values from cryptX options in a given string. 417 * 418 * @param array $Match The array containing matches from a regular expression search. 419 * Array format: `[0 => string, 1 => string, ...]`. 420 * The first element is ignored, and the second element is used as input string. 421 * 422 * @return string The string with replaced characters or the original array if no matches were found. 423 * If the input string is an array, the function returns an array with replaced characters 424 * for each element. 425 */ 426 private function getDefaultLinkText( array $Match ): string { 427 $text = str_replace( "@", self::$cryptXOptions['at'], $Match[1] ); 428 429 return str_replace( ".", self::$cryptXOptions['dot'], $text ); 430 } 431 432 /** 433 * List all files in a directory that match the given filter. 434 * 435 * @param string $path The path of the directory to list files from. 436 * @param array $filter The file extensions to filter by. 437 * If it's a string, it will be converted to an array of a single element. 438 * 439 * @return array An array of file names that match the filter. 440 */ 441 public function getFilesInDirectory( string $path, array $filter ): array { 442 $directoryHandle = opendir( $path ); 443 $directoryContent = array(); 444 while ( $file = readdir( $directoryHandle ) ) { 445 $fileExtension = substr( strtolower( $file ), - 3 ); 446 if ( in_array( $fileExtension, $filter ) ) { 447 $directoryContent[] = $file; 448 } 449 } 450 451 return $directoryContent; 452 } 453 454 /** 455 * Finds and encrypts email addresses in content. 456 * 457 * @param string|null $content The content where email addresses will be searched and encrypted. 458 * @param bool $shortcode Specifies whether shortcodes should be processed or not. Default is false. 459 * 460 * @return string|null The content with encrypted email addresses, or null if $content is null. 461 */ 462 public function findEmailAddressesInContent( ?string $content, bool $shortcode = false ): ?string { 463 global $post; 464 465 if ( $content === null ) { 466 return null; 467 } 468 469 $postId = ( is_object( $post ) ) ? $post->ID : - 1; 470 471 $isIdExcluded = $this->isIdExcluded( $postId ); 472 $mailtoRegex = '/<a (.*?)(href=("|\')mailto:(.*?)("|\')(.*?)|)>\s*(.*?)\s*<\/a>/i'; 473 474 if ( ( ! $isIdExcluded || $shortcode !== null ) ) { 475 $content = preg_replace_callback( $mailtoRegex, [ $this, 'encryptEmailAddress' ], $content ); 476 } 477 478 return $content; 479 } 480 481 /** 482 * Encrypts email addresses in search results. 483 * 484 * @param array $searchResults The search results containing email addresses. 485 * 486 * @return string The search results with encrypted email addresses. 487 */ 488 private function encryptEmailAddress( array $searchResults ): string { 489 $originalValue = $searchResults[0]; 490 491 if ( strpos( $searchResults[ self::INDEX_TO_CHECK ], '@' ) === self::NOT_FOUND ) { 492 return $originalValue; 493 } 494 495 $mailReference = self::MAIL_IDENTIFIER . $searchResults[ self::INDEX_TO_CHECK ]; 496 497 if ( str_starts_with( $searchResults[ self::INDEX_TO_CHECK ], self::SUBJECT_IDENTIFIER ) ) { 498 return $originalValue; 499 } 500 501 $return = $originalValue; 502 if ( ! empty( self::$cryptXOptions['java'] ) ) { 503 $javaHandler = "javascript:DeCryptX('" . $this->generateHashFromString( $searchResults[ self::INDEX_TO_CHECK ] ) . "')"; 504 $return = str_replace( self::MAIL_IDENTIFIER . $searchResults[ self::INDEX_TO_CHECK ], $javaHandler, $originalValue ); 505 } 506 507 $return = str_replace( $mailReference, antispambot( $mailReference ), $return ); 508 509 if ( ! empty( self::$cryptXOptions['css_id'] ) ) { 510 $return = preg_replace( self::PATTERN, '$1" id="' . self::$cryptXOptions['css_id'] . '">', $return ); 511 } 512 513 if ( ! empty( self::$cryptXOptions['css_class'] ) ) { 514 $return = preg_replace( self::PATTERN, '$1" class="' . self::$cryptXOptions['css_class'] . '">', $return ); 515 } 516 517 return $return; 518 } 519 520 521 /** 522 * Generate a hash string for the given input string. 523 * 524 * @param string $inputString The input string to generate a hash for. 525 * 526 * @return string The generated hash string. 527 */ 528 private function generateHashFromString( string $inputString ): string { 529 $inputString = str_replace( "&", "&", $inputString ); 530 $crypt = ''; 531 532 for ( $i = 0; $i < strlen( $inputString ); $i ++ ) { 533 do { 534 $salt = mt_rand( 0, 3 ); 535 $asciiValue = ord( substr( $inputString, $i ) ) + $salt; 536 if ( 8364 <= $asciiValue ) { 537 $asciiValue = 128; 538 } 539 } while ( in_array( $asciiValue, self::ASCII_VALUES_BLACKLIST ) ); 540 541 $crypt .= $salt . chr( $asciiValue ); 542 } 543 544 return $crypt; 545 } 546 /** 547 * add link to email addresses 548 */ 549 /** 550 * Auto-link emails in the given content. 551 * 552 * @param string $content The content to process. 553 * @param bool $shortcode Whether the function is called from a shortcode or not. 554 * 555 * @return string The content with emails auto-linked. 556 */ 557 public function addLinkToEmailAddresses( string $content, bool $shortcode = false ): string { 558 global $post; 559 $postID = is_object( $post ) ? $post->ID : - 1; 560 561 if ( $this->isIdExcluded( $postID ) && ! $shortcode ) { 562 return $content; 563 } 564 565 $emailPattern = "[_a-zA-Z0-9-+]+(\.[_a-zA-Z0-9-+]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(\.[a-zA-Z]{2,})"; 566 $linkPattern = "<a href=\"mailto:\\2\">\\2</a>"; 567 $src = [ 568 "/([\s])($emailPattern)/si", 569 "/(>)($emailPattern)(<)/si", 570 "/(\()($emailPattern)(\))/si", 571 "/(>)($emailPattern)([\s])/si", 572 "/([\s])($emailPattern)(<)/si", 573 "/^($emailPattern)/si", 574 "/(<a[^>]*>)<a[^>]*>/", 575 "/(<\/A>)<\/A>/i" 576 ]; 577 $tar = [ 578 "\\1$linkPattern", 579 "\\1$linkPattern\\6", 580 "\\1$linkPattern\\6", 581 "\\1$linkPattern\\6", 582 "\\1$linkPattern\\6", 583 "<a href=\"mailto:\\0\">\\0</a>", 584 "\\1", 585 "\\1" 586 ]; 587 588 return preg_replace( $src, $tar, $content ); 589 } 590 591 /** 592 * Installs the CryptX plugin by updating its options and loading default values. 593 */ 594 public function installCryptX(): void { 595 global $wpdb; 596 self::$cryptXOptions['admin_notices_deprecated'] = true; 597 if ( self::$cryptXOptions['excludedIDs'] == "" ) { 598 $tmp = array(); 599 $excludes = $wpdb->get_results( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = 'cryptxoff' AND meta_value = 'true'" ); 600 if ( count( $excludes ) > 0 ) { 601 foreach ( $excludes as $exclude ) { 602 $tmp[] = $exclude->post_id; 603 } 604 sort( $tmp ); 605 self::$cryptXOptions['excludedIDs'] = implode( ",", $tmp ); 606 update_option( 'cryptX', self::$cryptXOptions ); 607 self::$cryptXOptions = $this->loadCryptXOptionsWithDefaults(); // reread Options 608 $wpdb->query( "DELETE FROM $wpdb->postmeta WHERE meta_key = 'cryptxoff'" ); 609 } 610 } 611 if ( empty( self::$cryptXOptions['c2i_font'] ) ) { 612 self::$cryptXOptions['c2i_font'] = CRYPTX_DIR_PATH . 'fonts/' . $firstFont[0]; 613 } 614 if ( empty( self::$cryptXOptions['c2i_fontSize'] ) ) { 615 self::$cryptXOptions['c2i_fontSize'] = 10; 616 } 617 if ( empty( self::$cryptXOptions['c2i_fontRGB'] ) ) { 618 self::$cryptXOptions['c2i_fontRGB'] = '000000'; 619 } 620 update_option( 'cryptX', self::$cryptXOptions ); 621 self::$cryptXOptions = $this->loadCryptXOptionsWithDefaults(); // reread Options 622 } 623 624 private function addHooksHelper( $function_name, $hook_name ): void { 625 if ( function_exists( $function_name ) ) { 626 call_user_func( $function_name, 'cryptx', 'CryptX', [ $this, 'metaCheckbox' ], $hook_name ); 627 } else { 628 add_action( "dbx_{$hook_name}_sidebar", [ $this, 'metaOptionFieldset' ] ); 629 } 630 } 631 632 public function metaBox(): void { 633 $this->addHooksHelper( 'add_meta_box', 'post' ); 634 $this->addHooksHelper( 'add_meta_box', 'page' ); 635 } 636 637 /** 638 * Displays a checkbox to disable CryptX for the current post or page. 639 * 640 * This function outputs HTML code for a checkbox that allows the user to disable CryptX 641 * functionality for the current post or page. If the current post or page ID is excluded 642 **/ 643 public function metaCheckbox(): void { 644 global $post; 645 ?> 646 <label><input type="checkbox" name="disable_cryptx_pageid" <?php if ( $this->isIdExcluded( $post->ID ) ) { 647 echo 'checked="checked"'; 648 } ?>/> 5 final class CryptX 6 { 7 8 const NOT_FOUND = false; 9 const MAIL_IDENTIFIER = 'mailto:'; 10 const SUBJECT_IDENTIFIER = "?subject="; 11 const INDEX_TO_CHECK = 4; 12 const PATTERN = '/(.*)(">)/i'; 13 const ASCII_VALUES_BLACKLIST = ['32', '34', '39', '60', '62', '63', '92', '94', '96', '127']; 14 private static ?self $instance = null; 15 private static array $cryptXOptions = []; 16 private static int $imageCounter = 0; 17 private const FONT_EXTENSION = 'ttf'; 18 private CryptXSettingsTabs $settingsTabs; 19 private Config $config; 20 21 private function __construct() 22 { 23 $this->settingsTabs = new CryptXSettingsTabs($this); 24 $this->config = new Config( get_option('cryptX') ); 25 self::$cryptXOptions = $this->loadCryptXOptionsWithDefaults(); 26 } 27 28 /** 29 * Retrieves the singleton instance of the class. 30 * 31 * @return self The singleton instance of the class. 32 */ 33 public static function get_instance(): self 34 { 35 $needs_initialization = !(self::$instance instanceof self); 36 37 if ($needs_initialization) { 38 self::$instance = new self(); 39 } 40 41 return self::$instance; 42 } 43 44 45 /** 46 * @return Config 47 */ 48 public function getConfig(): Config 49 { 50 return $this->config; 51 } 52 53 /** 54 * Initializes the CryptX plugin by setting up version checks, applying filters, registering core hooks, initializing meta boxes (if enabled), and adding additional hooks. 55 * 56 * @return void 57 */ 58 public function startCryptX(): void 59 { 60 $this->checkAndUpdateVersion(); 61 $this->initializePluginFilters(); 62 $this->registerCoreHooks(); 63 $this->initializeMetaBoxIfEnabled(); 64 $this->registerAdditionalHooks(); 65 } 66 67 /** 68 * Checks the current version of the application against the stored version and updates settings if the application version is newer. 69 * 70 * @return void 71 */ 72 private function checkAndUpdateVersion(): void 73 { 74 $currentVersion = self::$cryptXOptions['version'] ?? null; 75 if ($currentVersion && version_compare(CRYPTX_VERSION, $currentVersion) > 0) { 76 $this->updateCryptXSettings(); 77 } 78 } 79 80 /** 81 * Initializes and applies plugin filters based on the defined configuration options. 82 * 83 * @return void 84 */ 85 private function initializePluginFilters(): void 86 { 87 foreach (self::$cryptXOptions['filter'] as $filter) { 88 if (isset(self::$cryptXOptions[$filter]) && self::$cryptXOptions[$filter]) { 89 $this->addPluginFilters($filter); 90 } 91 } 92 } 93 94 /** 95 * Registers core hooks for the plugin's functionality. 96 * 97 * @return void 98 */ 99 private function registerCoreHooks(): void 100 { 101 add_action('activate_' . CRYPTX_BASENAME, [$this, 'installCryptX']); 102 add_action('wp_enqueue_scripts', [$this, 'loadJavascriptFiles']); 103 } 104 105 /** 106 * Initializes the meta box functionality if enabled in the configuration. 107 * 108 * This method checks whether the meta box feature is enabled in the cryptX options. 109 * If enabled, it adds the necessary actions for administering the meta box and managing the posts' exclusion list. 110 * 111 * @return void 112 */ 113 private function initializeMetaBoxIfEnabled(): void 114 { 115 if (!isset(self::$cryptXOptions['metaBox']) || !self::$cryptXOptions['metaBox']) { 116 return; 117 } 118 119 add_action('admin_menu', [$this, 'metaBox']); 120 add_action('wp_insert_post', [$this, 'addPostIdToExcludedList']); 121 add_action('wp_update_post', [$this, 'addPostIdToExcludedList']); 122 } 123 124 /** 125 * Registers additional WordPress hooks and shortcodes. 126 * 127 * @return void 128 */ 129 private function registerAdditionalHooks(): void 130 { 131 add_filter('plugin_row_meta', 'rw_cryptx_init_row_meta', 10, 2); 132 add_filter('init', [$this, 'cryptXtinyUrl']); 133 add_shortcode('cryptx', [$this, 'cryptXShortcode']); 134 } 135 136 /** 137 * Retrieves the default options for CryptX configuration. 138 * 139 * @return array The default CryptX options, including version and font settings. 140 */ 141 public function getCryptXOptionsDefaults(): array 142 { 143 return array_merge( 144 $this->config->getAll(), 145 [ 146 'version' => CRYPTX_VERSION, 147 'c2i_font' => $this->getDefaultFont() 148 ] 149 ); 150 } 151 152 /** 153 * Retrieves the default font from the available fonts directory. 154 * 155 * @return string|null Returns the name of the default font found, or null if no fonts are available. 156 */ 157 private function getDefaultFont(): ?string 158 { 159 $availableFonts = $this->getFilesInDirectory( 160 CRYPTX_DIR_PATH . 'fonts', 161 [self::FONT_EXTENSION] 162 ); 163 164 return $availableFonts[0] ?? null; 165 } 166 167 /** 168 * Loads the cryptX options with default values. 169 * 170 * @return array The cryptX options array with default values. 171 */ 172 public function loadCryptXOptionsWithDefaults(): array 173 { 174 $defaultValues = $this->getCryptXOptionsDefaults(); 175 $currentOptions = get_option('cryptX'); 176 177 return wp_parse_args($currentOptions, $defaultValues); 178 } 179 180 /** 181 * Saves the cryptX options by updating the 'cryptX' option with the saved options merged with the default options. 182 * 183 * @param array $saveOptions The options to be saved. 184 * 185 * @return void 186 */ 187 public function saveCryptXOptions(array $saveOptions): void 188 { 189 update_option('cryptX', wp_parse_args($saveOptions, $this->loadCryptXOptionsWithDefaults())); 190 } 191 192 /** 193 * Decodes attributes from their encoded state and returns the decoded array. 194 * 195 * @param array $attributes The array of attributes, potentially encoded. 196 * @return array The array of decoded attributes with the 'encoded' key removed if present. 197 */ 198 private function decodeAttributes(array $attributes): array 199 { 200 if (($attributes['encoded'] ?? '') !== 'true') { 201 return $attributes; 202 } 203 204 $decodedAttributes = array_map( 205 fn($value) => $this->decodeString($value), 206 $attributes 207 ); 208 unset($decodedAttributes['encoded']); 209 210 return $decodedAttributes; 211 } 212 213 /** 214 * Processes the provided shortcode attributes and content, encrypts content, and optionally creates links for email addresses. 215 * 216 * @param array $atts Attributes passed to the shortcode. Defaults to an empty array. 217 * @param string $content The content enclosed within the shortcode. Defaults to an empty string. 218 * @param string $tag The name of the shortcode tag. Defaults to an empty string. 219 * @return string The processed and encrypted content, optionally including links for email addresses. 220 */ 221 public function cryptXShortcode(array $atts = [], string $content = '', string $tag = ''): string 222 { 223 // Decode attributes if needed 224 $attributes = $this->decodeAttributes($atts); 225 226 // Update options if attributes provided 227 if (!empty($attributes)) { 228 self::$cryptXOptions = shortcode_atts( 229 $this->loadCryptXOptionsWithDefaults(), 230 array_change_key_case($attributes, CASE_LOWER), 231 $tag 232 ); 233 } 234 235 // Process content 236 if (self::$cryptXOptions['autolink'] ?? false) { 237 $content = $this->addLinkToEmailAddresses($content, true); 238 } 239 240 $processedContent = $this->encryptAndLinkContent($content, true); 241 242 // Reset options to defaults 243 self::$cryptXOptions = $this->loadCryptXOptionsWithDefaults(); 244 245 return $processedContent; 246 } 247 248 /** 249 * Encrypts and links content. 250 * 251 * @param string $content The content to be encrypted and linked. 252 * 253 * @return string The encrypted and linked content. 254 */ 255 private function encryptAndLinkContent(string $content, bool $shortcode = false): string 256 { 257 $content = $this->findEmailAddressesInContent($content, $shortcode); 258 259 return $this->replaceEmailInContent($content, $shortcode); 260 } 261 262 263 private const MAILTO_PATTERN = '/<a (.*?)(href=("|\')mailto:(.*?)("|\')(.*?)|)>\s*(.*?)\s*<\/a>/i'; 264 private const EMAIL_PATTERN = "/([_a-zA-Z0-9-+]+(\.[_a-zA-Z0-9-+]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(\.[a-zA-Z]{2,}))/i"; 265 266 private function processAndEncryptEmails(EmailProcessingConfig $config): string 267 { 268 $content = $this->encryptMailtoLinks($config); 269 return $this->encryptPlainEmails($content, $config); 270 } 271 272 private function encryptMailtoLinks(EmailProcessingConfig $config): ?string 273 { 274 $content = $config->getContent(); 275 if ($content === null) { 276 return null; 277 } 278 279 $postId = $config->getPostId() ?? $this->getCurrentPostId(); 280 281 if (!$this->isIdExcluded($postId) || $config->isShortcode()) { 282 return preg_replace_callback( 283 self::MAILTO_PATTERN, 284 [$this, 'encryptEmailAddress'], 285 $content 286 ); 287 } 288 289 return $content; 290 } 291 292 private function encryptPlainEmails(string $content, EmailProcessingConfig $config): string 293 { 294 $postId = $config->getPostId() ?? $this->getCurrentPostId(); 295 296 if ((!$this->isIdExcluded($postId) || $config->isShortcode()) && !empty($content)) { 297 return preg_replace_callback( 298 self::EMAIL_PATTERN, 299 [$this, 'encodeEmailToLinkText'], 300 $content 301 ); 302 } 303 304 return $content; 305 } 306 307 private function getCurrentPostId(): int 308 { 309 global $post; 310 return (is_object($post)) ? $post->ID : -1; 311 } 312 313 314 /** 315 * Generates and returns a tiny URL image. 316 * 317 * @return void 318 */ 319 public function cryptXtinyUrl(): void 320 { 321 $url = $_SERVER['REQUEST_URI']; 322 $params = explode('/', $url); 323 if (count($params) > 1) { 324 $tiny_url = $params[count($params) - 2]; 325 if ($tiny_url == md5(get_bloginfo('url'))) { 326 $font = CRYPTX_DIR_PATH . 'fonts/' . self::$cryptXOptions['c2i_font']; 327 $msg = $params[count($params) - 1]; 328 $size = self::$cryptXOptions['c2i_fontSize']; 329 $pad = 1; 330 $transparent = 1; 331 $rgb = str_replace("#", "", self::$cryptXOptions['c2i_fontRGB']); 332 $red = hexdec(substr($rgb, 0, 2)); 333 $grn = hexdec(substr($rgb, 2, 2)); 334 $blu = hexdec(substr($rgb, 4, 2)); 335 $bg_red = 255 - $red; 336 $bg_grn = 255 - $grn; 337 $bg_blu = 255 - $blu; 338 $width = 0; 339 $height = 0; 340 $offset_x = 0; 341 $offset_y = 0; 342 $bounds = array(); 343 $image = ""; 344 $bounds = ImageTTFBBox($size, 0, $font, "W"); 345 $font_height = abs($bounds[7] - $bounds[1]); 346 $bounds = ImageTTFBBox($size, 0, $font, $msg); 347 $width = abs($bounds[4] - $bounds[6]); 348 $height = abs($bounds[7] - $bounds[1]); 349 $offset_y = $font_height + abs(($height - $font_height) / 2) - 1; 350 $offset_x = 0; 351 $image = imagecreatetruecolor($width + ($pad * 2), $height + ($pad * 2)); 352 imagesavealpha($image, true); 353 $foreground = ImageColorAllocate($image, $red, $grn, $blu); 354 $background = imagecolorallocatealpha($image, 0, 0, 0, 127); 355 imagefill($image, 0, 0, $background); 356 ImageTTFText($image, $size, 0, round($offset_x + $pad, 0), round($offset_y + $pad, 0), $foreground, $font, $msg); 357 Header("Content-type: image/png"); 358 imagePNG($image); 359 die; 360 } 361 } 362 } 363 364 /** 365 * Add plugin filters. 366 * 367 * This function adds the specified plugin filter if the 'autolink' key is present and its value is true in the global $cryptXOptions variable. 368 * It also adds the 'autolink' function as a filter to the $filterName if the global $shortcode_tags variable is not empty. 369 * Additionally, this function calls the addCommonFilters() and addOtherFilters() functions at specific points. 370 * 371 * @param string $filterName The name of the filter to add. 372 * 373 * @return void 374 */ 375 private function addPluginFilters(string $filterName): void 376 { 377 global $shortcode_tags; 378 379 if (array_key_exists('autolink', self::$cryptXOptions) && self::$cryptXOptions['autolink']) { 380 $this->addAutoLinkFilters($filterName); 381 if (!empty($shortcode_tags)) { 382 $this->addAutoLinkFilters($filterName, 11); 383 } 384 } 385 $this->addOtherFilters($filterName); 386 } 387 388 /** 389 * Adds common filters to a given filter name. 390 * 391 * This function adds the common filter 'autolink' to the provided $filterName. 392 * 393 * @param string $filterName The name of the filter to add common filters to. 394 * 395 * @return void 396 */ 397 private function addAutoLinkFilters(string $filterName, $prio = 5): void 398 { 399 add_filter($filterName, [$this, 'addLinkToEmailAddresses'], $prio); 400 } 401 402 /** 403 * Adds additional filters to a given filter name. 404 * 405 * This function adds two additional filters, 'encryptx' and 'replaceEmailInContent', 406 * to the specified filter name. The 'encryptx' filter is added with a priority of 12, 407 * and the 'replaceEmailInContent' filter is added with a priority of 13. 408 * 409 * @param string $filterName The name of the filter to add the additional filters to. 410 * 411 * @return void 412 */ 413 private function addOtherFilters(string $filterName): void 414 { 415 add_filter($filterName, [$this, 'findEmailAddressesInContent'], 12); 416 add_filter($filterName, [$this, 'replaceEmailInContent'], 13); 417 } 418 419 /** 420 * Checks if a given ID is excluded based on the 'excludedIDs' variable. 421 * 422 * @param int $ID The ID to check if excluded. 423 * 424 * @return bool Returns true if the ID is excluded, false otherwise. 425 */ 426 private function isIdExcluded(int $ID): bool 427 { 428 $excludedIds = explode(",", self::$cryptXOptions['excludedIDs']); 429 430 return in_array($ID, $excludedIds); 431 } 432 433 /** 434 * Replaces email addresses in content with link texts. 435 * 436 * @param string|null $content The content to replace the email addresses in. 437 * @param bool $isShortcode Flag indicating whether the method is called from a shortcode. 438 * 439 * @return string|null The content with replaced email addresses. 440 */ 441 public function replaceEmailInContent(?string $content, bool $isShortcode = false): ?string 442 { 443 global $post; 444 445 if (self::$cryptXOptions['disable_rss'] && $this->isRssFeed()) return $content; 446 447 $postId = (is_object($post)) ? $post->ID : -1; 448 if ((!$this->isIdExcluded($postId) || $isShortcode) && !empty($content)) { 449 $content = $this->replaceEmailWithLinkText($content); 450 } 451 452 return $content; 453 } 454 455 /** 456 * Replace email addresses in a given content with link text. 457 * 458 * @param string $content The content to search for email addresses. 459 * 460 * @return string The content with email addresses replaced with link text. 461 */ 462 private function replaceEmailWithLinkText(string $content): string 463 { 464 $emailPattern = "/([_a-zA-Z0-9-+]+(\.[_a-zA-Z0-9-+]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(\.[a-zA-Z]{2,}))/i"; 465 466 return preg_replace_callback($emailPattern, [$this, 'encodeEmailToLinkText'], $content); 467 } 468 469 /** 470 * Encode email address to link text. 471 * 472 * @param array $Match The matched email address. 473 * 474 * @return string The encoded link text. 475 */ 476 private function encodeEmailToLinkText(array $Match): string 477 { 478 if ($this->inWhiteList($Match)) { 479 return $Match[1]; 480 } 481 switch (self::$cryptXOptions['opt_linktext']) { 482 case 1: 483 $text = $this->getLinkText(); 484 break; 485 case 2: 486 $text = $this->getLinkImage(); 487 break; 488 case 3: 489 $img_url = wp_get_attachment_url(self::$cryptXOptions['alt_uploadedimage']); 490 $text = $this->getUploadedImage($img_url); 491 self::$imageCounter++; 492 break; 493 case 4: 494 $text = antispambot($Match[1]); 495 break; 496 case 5: 497 $text = $this->getImageFromText($Match); 498 self::$imageCounter++; 499 break; 500 default: 501 $text = $this->getDefaultLinkText($Match); 502 } 503 504 return $text; 505 } 506 507 /** 508 * Check if the given match is in the whitelist. 509 * 510 * @param array $Match The match to check against the whitelist. 511 * 512 * @return bool True if the match is in the whitelist, false otherwise. 513 */ 514 private function inWhiteList(array $Match): bool 515 { 516 $whiteList = array_filter(array_map('trim', explode(",", self::$cryptXOptions['whiteList']))); 517 $tmp = explode(".", $Match[0]); 518 519 return in_array(end($tmp), $whiteList); 520 } 521 522 /** 523 * Get the link text from cryptXOptions 524 * 525 * @return string The link text 526 */ 527 private function getLinkText(): string 528 { 529 return self::$cryptXOptions['alt_linktext']; 530 } 531 532 /** 533 * Generate an HTML image tag with the link image URL as the source 534 * 535 * @return string The HTML image tag 536 */ 537 private function getLinkImage(): string 538 { 539 return "<img src=\"" . self::$cryptXOptions['alt_linkimage'] . "\" class=\"cryptxImage\" alt=\"" . self::$cryptXOptions['alt_linkimage_title'] . "\" title=\"" . antispambot(self::$cryptXOptions['alt_linkimage_title']) . "\" />"; 540 } 541 542 /** 543 * Get the HTML tag for an uploaded image. 544 * 545 * @param string $img_url The URL of the image. 546 * 547 * @return string The HTML tag for the image. 548 */ 549 private function getUploadedImage(string $img_url): string 550 { 551 return "<img src=\"" . $img_url . "\" class=\"cryptxImage cryptxImage_" . self::$imageCounter . "\" alt=\"" . self::$cryptXOptions['http_linkimage_title'] . " title=\"" . antispambot(self::$cryptXOptions['http_linkimage_title']) . "\" />"; 552 } 553 554 /** 555 * Converts a matched image URL into an HTML image element with cryptX classes and attributes. 556 * 557 * @param array $Match The matched image URL and other related data. 558 * 559 * @return string Returns the HTML image element. 560 */ 561 private function getImageFromText(array $Match): string 562 { 563 return "<img src=\"" . get_bloginfo('url') . "/" . md5(get_bloginfo('url')) . "/" . antispambot($Match[1]) . "\" class=\"cryptxImage cryptxImage_" . self::$imageCounter . "\" alt=\"" . antispambot($Match[1]) . "\" title=\"" . antispambot($Match[1]) . "\" />"; 564 } 565 566 /** 567 * Replaces specific characters with values from cryptX options in a given string. 568 * 569 * @param array $Match The array containing matches from a regular expression search. 570 * Array format: `[0 => string, 1 => string, ...]`. 571 * The first element is ignored, and the second element is used as input string. 572 * 573 * @return string The string with replaced characters or the original array if no matches were found. 574 * If the input string is an array, the function returns an array with replaced characters 575 * for each element. 576 */ 577 private function getDefaultLinkText(array $Match): string 578 { 579 $text = str_replace("@", self::$cryptXOptions['at'], $Match[1]); 580 581 return str_replace(".", self::$cryptXOptions['dot'], $text); 582 } 583 584 /** 585 * List all files in a directory that match the given filter. 586 * 587 * @param string $path The path of the directory to list files from. 588 * @param array $filter The file extensions to filter by. 589 * If it's a string, it will be converted to an array of a single element. 590 * 591 * @return array An array of file names that match the filter. 592 */ 593 public function getFilesInDirectory(string $path, array $filter): array 594 { 595 $directoryHandle = opendir($path); 596 $directoryContent = array(); 597 while ($file = readdir($directoryHandle)) { 598 $fileExtension = substr(strtolower($file), -3); 599 if (in_array($fileExtension, $filter)) { 600 $directoryContent[] = $file; 601 } 602 } 603 604 return $directoryContent; 605 } 606 607 /** 608 * Finds and encrypts email addresses in content. 609 * 610 * @param string|null $content The content where email addresses will be searched and encrypted. 611 * @param bool $shortcode Specifies whether shortcodes should be processed or not. Default is false. 612 * 613 * @return string|null The content with encrypted email addresses, or null if $content is null. 614 */ 615 public function findEmailAddressesInContent(?string $content, bool $shortcode = false): ?string 616 { 617 global $post; 618 619 if (self::$cryptXOptions['disable_rss'] && $this->isRssFeed()) return $content; 620 621 if ($content === null) { 622 return null; 623 } 624 625 // Skip processing for RSS feeds if the option is enabled 626 /* if (self::$cryptXOptions['disable_rss'] && $this->isRssFeed()) { 627 return $content; 628 }*/ 629 630 $postId = (is_object($post)) ? $post->ID : -1; 631 632 $isIdExcluded = $this->isIdExcluded($postId); 633 $mailtoRegex = '/<a (.*?)(href=("|\')mailto:(.*?)("|\')(.*?)|)>\s*(.*?)\s*<\/a>/i'; 634 635 if ((!$isIdExcluded || $shortcode !== null)) { 636 $content = preg_replace_callback($mailtoRegex, [$this, 'encryptEmailAddress'], $content); 637 } 638 639 return $content; 640 } 641 642 /** 643 * Encrypts email addresses in search results. 644 * 645 * @param array $searchResults The search results containing email addresses. 646 * 647 * @return string The search results with encrypted email addresses. 648 */ 649 private function encryptEmailAddress(array $searchResults): string 650 { 651 $originalValue = $searchResults[0]; 652 653 if (strpos($searchResults[self::INDEX_TO_CHECK], '@') === self::NOT_FOUND) { 654 return $originalValue; 655 } 656 657 $mailReference = self::MAIL_IDENTIFIER . $searchResults[self::INDEX_TO_CHECK]; 658 659 if (str_starts_with($searchResults[self::INDEX_TO_CHECK], self::SUBJECT_IDENTIFIER)) { 660 return $originalValue; 661 } 662 663 $return = $originalValue; 664 if (!empty(self::$cryptXOptions['java'])) { 665 $javaHandler = "javascript:DeCryptX('" . $this->generateHashFromString($searchResults[self::INDEX_TO_CHECK]) . "')"; 666 $return = str_replace(self::MAIL_IDENTIFIER . $searchResults[self::INDEX_TO_CHECK], $javaHandler, $originalValue); 667 } 668 669 $return = str_replace($mailReference, antispambot($mailReference), $return); 670 671 if (!empty(self::$cryptXOptions['css_id'])) { 672 $return = preg_replace(self::PATTERN, '$1" id="' . self::$cryptXOptions['css_id'] . '">', $return); 673 } 674 675 if (!empty(self::$cryptXOptions['css_class'])) { 676 $return = preg_replace(self::PATTERN, '$1" class="' . self::$cryptXOptions['css_class'] . '">', $return); 677 } 678 679 return $return; 680 } 681 682 /** 683 * Generate a hash string for the given input string. 684 * 685 * @param string $inputString The input string to generate a hash for. 686 * 687 * @return string The generated hash string. 688 */ 689 private function generateHashFromString(string $inputString): string 690 { 691 $inputString = str_replace("&", "&", $inputString); 692 $crypt = ''; 693 694 for ($i = 0; $i < strlen($inputString); $i++) { 695 do { 696 $salt = mt_rand(0, 3); 697 $asciiValue = ord(substr($inputString, $i)) + $salt; 698 if (8364 <= $asciiValue) { 699 $asciiValue = 128; 700 } 701 } while (in_array($asciiValue, self::ASCII_VALUES_BLACKLIST)); 702 703 $crypt .= $salt . chr($asciiValue); 704 } 705 706 return $crypt; 707 } 708 709 /** 710 * add link to email addresses 711 */ 712 /** 713 * Auto-link emails in the given content. 714 * 715 * @param string $content The content to process. 716 * @param bool $shortcode Whether the function is called from a shortcode or not. 717 * 718 * @return string The content with emails auto-linked. 719 */ 720 public function addLinkToEmailAddresses(string $content, bool $shortcode = false): string 721 { 722 global $post; 723 $postID = is_object($post) ? $post->ID : -1; 724 725 if ($this->isIdExcluded($postID) && !$shortcode) { 726 return $content; 727 } 728 729 $emailPattern = "[_a-zA-Z0-9-+]+(\.[_a-zA-Z0-9-+]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(\.[a-zA-Z]{2,})"; 730 $linkPattern = "<a href=\"mailto:\\2\">\\2</a>"; 731 $src = [ 732 "/([\s])($emailPattern)/si", 733 "/(>)($emailPattern)(<)/si", 734 "/(\()($emailPattern)(\))/si", 735 "/(>)($emailPattern)([\s])/si", 736 "/([\s])($emailPattern)(<)/si", 737 "/^($emailPattern)/si", 738 "/(<a[^>]*>)<a[^>]*>/", 739 "/(<\/A>)<\/A>/i" 740 ]; 741 $tar = [ 742 "\\1$linkPattern", 743 "\\1$linkPattern\\6", 744 "\\1$linkPattern\\6", 745 "\\1$linkPattern\\6", 746 "\\1$linkPattern\\6", 747 "<a href=\"mailto:\\0\">\\0</a>", 748 "\\1", 749 "\\1" 750 ]; 751 752 return preg_replace($src, $tar, $content); 753 } 754 755 /** 756 * Installs the CryptX plugin by updating its options and loading default values. 757 */ 758 public function installCryptX(): void 759 { 760 global $wpdb; 761 self::$cryptXOptions['admin_notices_deprecated'] = true; 762 if (self::$cryptXOptions['excludedIDs'] == "") { 763 $tmp = array(); 764 $excludes = $wpdb->get_results("SELECT post_id FROM $wpdb->postmeta WHERE meta_key = 'cryptxoff' AND meta_value = 'true'"); 765 if (count($excludes) > 0) { 766 foreach ($excludes as $exclude) { 767 $tmp[] = $exclude->post_id; 768 } 769 sort($tmp); 770 self::$cryptXOptions['excludedIDs'] = implode(",", $tmp); 771 update_option('cryptX', self::$cryptXOptions); 772 self::$cryptXOptions = $this->loadCryptXOptionsWithDefaults(); // reread Options 773 $wpdb->query("DELETE FROM $wpdb->postmeta WHERE meta_key = 'cryptxoff'"); 774 } 775 } 776 if (empty(self::$cryptXOptions['c2i_font'])) { 777 self::$cryptXOptions['c2i_font'] = CRYPTX_DIR_PATH . 'fonts/' . $firstFont[0]; 778 } 779 if (empty(self::$cryptXOptions['c2i_fontSize'])) { 780 self::$cryptXOptions['c2i_fontSize'] = 10; 781 } 782 if (empty(self::$cryptXOptions['c2i_fontRGB'])) { 783 self::$cryptXOptions['c2i_fontRGB'] = '000000'; 784 } 785 update_option('cryptX', self::$cryptXOptions); 786 self::$cryptXOptions = $this->loadCryptXOptionsWithDefaults(); // reread Options 787 } 788 789 private function addHooksHelper($function_name, $hook_name): void 790 { 791 if (function_exists($function_name)) { 792 call_user_func($function_name, 'cryptx', 'CryptX', [$this, 'metaCheckbox'], $hook_name); 793 } else { 794 add_action("dbx_{$hook_name}_sidebar", [$this, 'metaOptionFieldset']); 795 } 796 } 797 798 public function metaBox(): void 799 { 800 $this->addHooksHelper('add_meta_box', 'post'); 801 $this->addHooksHelper('add_meta_box', 'page'); 802 } 803 804 /** 805 * Displays a checkbox to disable CryptX for the current post or page. 806 * 807 * This function outputs HTML code for a checkbox that allows the user to disable CryptX 808 * functionality for the current post or page. If the current post or page ID is excluded 809 **/ 810 public function metaCheckbox(): void 811 { 812 global $post; 813 ?> 814 <label><input type="checkbox" name="disable_cryptx_pageid" <?php if ($this->isIdExcluded($post->ID)) { 815 echo 'checked="checked"'; 816 } ?>/> 649 817 Disable CryptX for this post/page</label> 650 <?php 651 } 652 653 /** 654 * Renders the CryptX option fieldset for the current post/page if the user has permission to edit posts. 655 * This fieldset allows the user to enable or disable CryptX for the current post/page. 656 * 657 * @return void 658 */ 659 public function metaOptionFieldset(): void { 660 global $post; 661 if ( current_user_can( 'edit_posts' ) ) { ?> 818 <?php 819 } 820 821 /** 822 * Renders the CryptX option fieldset for the current post/page if the user has permission to edit posts. 823 * This fieldset allows the user to enable or disable CryptX for the current post/page. 824 * 825 * @return void 826 */ 827 public function metaOptionFieldset(): void 828 { 829 global $post; 830 if (current_user_can('edit_posts')) { ?> 662 831 <fieldset id="cryptxoption" class="dbx-box"> 663 832 <h3 class="dbx-handle">CryptX</h3> 664 833 <div class="dbx-content"> 665 834 <label><input type="checkbox" 666 name="disable_cryptx_pageid" <?php if ( $this->isIdExcluded( $post->ID )) {667 echo 'checked="checked"';668 } ?>/> Disable CryptX for this post/page</label>835 name="disable_cryptx_pageid" <?php if ($this->isIdExcluded($post->ID)) { 836 echo 'checked="checked"'; 837 } ?>/> Disable CryptX for this post/page</label> 669 838 </div> 670 839 </fieldset> 671 <?php 672 } 673 } 674 675 /** 676 * Adds a post ID to the excluded list in the cryptX options. 677 * 678 * @param int $postId The post ID to be added to the excluded list. 679 * 680 * @return void 681 */ 682 public function addPostIdToExcludedList( int $postId ): void { 683 $postId = wp_is_post_revision( $postId ) ?: $postId; 684 $excludedIds = $this->updateExcludedIdsList( self::$cryptXOptions['excludedIDs'], $postId ); 685 self::$cryptXOptions['excludedIDs'] = implode( ",", array_filter( $excludedIds ) ); 686 update_option( 'cryptX', self::$cryptXOptions ); 687 } 688 689 /** 690 * Updates the excluded IDs list based on a given ID and the current list. 691 * 692 * @param string $excludedIds The current excluded IDs list, separated by commas. 693 * @param int $postId The ID to be updated in the excluded IDs list. 694 * 695 * @return array The updated excluded IDs list as an array, with the ID removed if it existed and added if necessary. 696 */ 697 private function updateExcludedIdsList( string $excludedIds, int $postId ): array { 698 $excludedIdsArray = explode( ",", $excludedIds ); 699 $excludedIdsArray = $this->removePostIdFromExcludedIds( $excludedIdsArray, $postId ); 700 $excludedIdsArray = $this->addPostIdToExcludedIdsIfNecessary( $excludedIdsArray, $postId ); 701 702 return $this->makeExcludedIdsUniqueAndSorted( $excludedIdsArray ); 703 } 704 705 /** 706 * Removes a specific post ID from the array of excluded IDs. 707 * 708 * @param array $excludedIds The array of excluded IDs. 709 * @param int $postId The ID of the post to be removed from the excluded IDs. 710 * 711 * @return array The updated array of excluded IDs without the specified post ID. 712 */ 713 private function removePostIdFromExcludedIds( array $excludedIds, int $postId ): array { 714 foreach ( $excludedIds as $key => $id ) { 715 if ( $id == $postId ) { 716 unset( $excludedIds[ $key ] ); 717 break; 718 } 719 } 720 721 return $excludedIds; 722 } 723 724 /** 725 * Adds the post ID to the list of excluded IDs if necessary. 726 * 727 * @param array $excludedIds The array of excluded IDs. 728 * @param int $postId The post ID to be added to the excluded IDs. 729 * 730 * @return array The updated array of excluded IDs. 731 */ 732 private function addPostIdToExcludedIdsIfNecessary( array $excludedIds, int $postId ): array { 733 if ( isset( $_POST['disable_cryptx_pageid'] ) ) { 734 $excludedIds[] = $postId; 735 } 736 737 return $excludedIds; 738 } 739 740 /** 741 * Makes the excluded IDs unique and sorted. 742 * 743 * @param array $excludedIds The array of excluded IDs. 744 * 745 * @return array The array of excluded IDs with duplicate values removed and sorted in ascending order. 746 */ 747 private function makeExcludedIdsUniqueAndSorted( array $excludedIds ): array { 748 $excludedIds = array_unique( $excludedIds ); 749 sort( $excludedIds ); 750 751 return $excludedIds; 752 } 753 754 /** 755 * Displays a message in a styled div. 756 * 757 * @param string $message The message to be displayed. 758 * @param bool $errormsg Optional. Indicates whether the message is an error message. Default is false. 759 * 760 * @return void 761 */ 762 private function showMessage( string $message, bool $errormsg = false ): void { 763 if ( $errormsg ) { 764 echo '<div id="message" class="error">'; 765 } else { 766 echo '<div id="message" class="updated fade">'; 767 } 768 769 echo "$message</div>"; 770 } 771 772 /** 773 * Retrieves the domain from the current site URL. 774 * 775 * @return string The domain of the current site URL. 776 */ 777 public function getDomain(): string { 778 return $this->trimSlashFromDomain( $this->removeProtocolFromUrl( $this->getSiteUrl() ) ); 779 } 780 781 /** 782 * Retrieves the site URL. 783 * 784 * @return string The site URL. 785 */ 786 private function getSiteUrl(): string { 787 return get_option( 'siteurl' ); 788 } 789 790 /** 791 * Removes the protocol from a URL. 792 * 793 * @param string $url The URL string to remove the protocol from. 794 * 795 * @return string The URL string without the protocol. 796 */ 797 private function removeProtocolFromUrl( string $url ): string { 798 return preg_replace( '|https?://|', '', $url ); 799 } 800 801 /** 802 * Trims the trailing slash from a domain. 803 * 804 * @param string $domain The domain to trim the slash from. 805 * 806 * @return string The domain with the trailing slash removed. 807 */ 808 private function trimSlashFromDomain( string $domain ): string { 809 if ( $slashPosition = strpos( $domain, '/' ) ) { 810 $domain = substr( $domain, 0, $slashPosition ); 811 } 812 813 return $domain; 814 } 815 816 /** 817 * Loads Javascript files required for CryptX functionality. 818 * 819 * @return void 820 */ 821 public function loadJavascriptFiles(): void { 822 wp_enqueue_script( 'cryptx-js', CRYPTX_DIR_URL . 'js/cryptx.min.js', false, false, self::$cryptXOptions['load_java'] ); 823 wp_enqueue_style( 'cryptx-styles', CRYPTX_DIR_URL . 'css/cryptx.css' ); 824 } 825 826 /** 827 * Updates the CryptX settings. 828 * 829 * This method retrieves the current CryptX options from the database and checks if the version of CryptX 830 * stored in the options is less than the current version of CryptX. If the version is outdated, the method 831 * updates the necessary settings and saves the updated options back to the database. 832 * 833 * @return void 834 */ 835 private function updateCryptXSettings(): void { 836 self::$cryptXOptions = get_option( 'cryptX' ); 837 if ( isset( self::$cryptXOptions['version'] ) && version_compare( CRYPTX_VERSION, self::$cryptXOptions['version'] ) > 0 ) { 838 if ( isset( self::$cryptXOptions['version'] ) ) { 839 unset( self::$cryptXOptions['version'] ); 840 } 841 if ( isset( self::$cryptXOptions['c2i_font'] ) ) { 842 unset( self::$cryptXOptions['c2i_font'] ); 843 } 844 if ( isset( self::$cryptXOptions['c2i_fontRGB'] ) ) { 845 self::$cryptXOptions['c2i_fontRGB'] = "#" . self::$cryptXOptions['c2i_fontRGB']; 846 } 847 if ( isset( self::$cryptXOptions['alt_uploadedimage'] ) && ! is_int( self::$cryptXOptions['alt_uploadedimage'] ) ) { 848 unset( self::$cryptXOptions['alt_uploadedimage'] ); 849 if ( self::$cryptXOptions['opt_linktext'] == 3 ) { 850 unset( self::$cryptXOptions['opt_linktext'] ); 851 } 852 } 853 self::$cryptXOptions = wp_parse_args( self::$cryptXOptions, $this->getCryptXOptionsDefaults() ); 854 update_option( 'cryptX', self::$cryptXOptions ); 855 } 856 } 857 858 /** 859 * Encodes a string by replacing special characters with their corresponding HTML entities. 860 * 861 * @param string|null $str The string to be encoded. 862 * 863 * @return string The encoded string, or an array of encoded strings if an array was passed. 864 */ 865 private function encodeString( ?string $str ): string { 866 $str = htmlentities( $str, ENT_QUOTES, 'UTF-8' ); 867 $special = array( 868 '[' => '[', 869 ']' => ']', 870 ); 871 872 return str_replace( array_keys( $special ), array_values( $special ), $str ); 873 } 874 875 /** 876 * Decodes a string that has been HTML entity encoded. 877 * 878 * @param string|null $str The string to decode. If null, an empty string is returned. 879 * 880 * @return string The decoded string. 881 */ 882 private function decodeString( ?string $str ): string { 883 return html_entity_decode( $str, ENT_QUOTES, 'UTF-8' ); 884 } 885 886 public function convertArrayToArgumentString( array $args = [] ): string { 887 $string = ""; 888 if ( ! empty( $args ) ) { 889 foreach ( $args as $key => $value ) { 890 $string .= sprintf( " %s=\"%s\"", $key, $this->encodeString( $value ) ); 891 } 892 $string .= " encoded=\"true\""; 893 } 894 895 return $string; 896 } 840 <?php 841 } 842 } 843 844 /** 845 * Adds a post ID to the excluded list in the cryptX options. 846 * 847 * @param int $postId The post ID to be added to the excluded list. 848 * 849 * @return void 850 */ 851 public function addPostIdToExcludedList(int $postId): void 852 { 853 $postId = wp_is_post_revision($postId) ?: $postId; 854 $excludedIds = $this->updateExcludedIdsList(self::$cryptXOptions['excludedIDs'], $postId); 855 self::$cryptXOptions['excludedIDs'] = implode(",", array_filter($excludedIds)); 856 update_option('cryptX', self::$cryptXOptions); 857 } 858 859 /** 860 * Updates the excluded IDs list based on a given ID and the current list. 861 * 862 * @param string $excludedIds The current excluded IDs list, separated by commas. 863 * @param int $postId The ID to be updated in the excluded IDs list. 864 * 865 * @return array The updated excluded IDs list as an array, with the ID removed if it existed and added if necessary. 866 */ 867 private function updateExcludedIdsList(string $excludedIds, int $postId): array 868 { 869 $excludedIdsArray = explode(",", $excludedIds); 870 $excludedIdsArray = $this->removePostIdFromExcludedIds($excludedIdsArray, $postId); 871 $excludedIdsArray = $this->addPostIdToExcludedIdsIfNecessary($excludedIdsArray, $postId); 872 873 return $this->makeExcludedIdsUniqueAndSorted($excludedIdsArray); 874 } 875 876 /** 877 * Removes a specific post ID from the array of excluded IDs. 878 * 879 * @param array $excludedIds The array of excluded IDs. 880 * @param int $postId The ID of the post to be removed from the excluded IDs. 881 * 882 * @return array The updated array of excluded IDs without the specified post ID. 883 */ 884 private function removePostIdFromExcludedIds(array $excludedIds, int $postId): array 885 { 886 foreach ($excludedIds as $key => $id) { 887 if ($id == $postId) { 888 unset($excludedIds[$key]); 889 break; 890 } 891 } 892 893 return $excludedIds; 894 } 895 896 /** 897 * Adds the post ID to the list of excluded IDs if necessary. 898 * 899 * @param array $excludedIds The array of excluded IDs. 900 * @param int $postId The post ID to be added to the excluded IDs. 901 * 902 * @return array The updated array of excluded IDs. 903 */ 904 private function addPostIdToExcludedIdsIfNecessary(array $excludedIds, int $postId): array 905 { 906 if (isset($_POST['disable_cryptx_pageid'])) { 907 $excludedIds[] = $postId; 908 } 909 910 return $excludedIds; 911 } 912 913 /** 914 * Makes the excluded IDs unique and sorted. 915 * 916 * @param array $excludedIds The array of excluded IDs. 917 * 918 * @return array The array of excluded IDs with duplicate values removed and sorted in ascending order. 919 */ 920 private function makeExcludedIdsUniqueAndSorted(array $excludedIds): array 921 { 922 $excludedIds = array_unique($excludedIds); 923 sort($excludedIds); 924 925 return $excludedIds; 926 } 927 928 /** 929 * Displays a message in a styled div. 930 * 931 * @param string $message The message to be displayed. 932 * @param bool $errormsg Optional. Indicates whether the message is an error message. Default is false. 933 * 934 * @return void 935 */ 936 private function showMessage(string $message, bool $errormsg = false): void 937 { 938 if ($errormsg) { 939 echo '<div id="message" class="error">'; 940 } else { 941 echo '<div id="message" class="updated fade">'; 942 } 943 944 echo "$message</div>"; 945 } 946 947 /** 948 * Retrieves the domain from the current site URL. 949 * 950 * @return string The domain of the current site URL. 951 */ 952 public function getDomain(): string 953 { 954 return $this->trimSlashFromDomain($this->removeProtocolFromUrl($this->getSiteUrl())); 955 } 956 957 /** 958 * Retrieves the site URL. 959 * 960 * @return string The site URL. 961 */ 962 private function getSiteUrl(): string 963 { 964 return get_option('siteurl'); 965 } 966 967 /** 968 * Removes the protocol from a URL. 969 * 970 * @param string $url The URL string to remove the protocol from. 971 * 972 * @return string The URL string without the protocol. 973 */ 974 private function removeProtocolFromUrl(string $url): string 975 { 976 return preg_replace('|https?://|', '', $url); 977 } 978 979 /** 980 * Trims the trailing slash from a domain. 981 * 982 * @param string $domain The domain to trim the slash from. 983 * 984 * @return string The domain with the trailing slash removed. 985 */ 986 private function trimSlashFromDomain(string $domain): string 987 { 988 if ($slashPosition = strpos($domain, '/')) { 989 $domain = substr($domain, 0, $slashPosition); 990 } 991 992 return $domain; 993 } 994 995 /** 996 * Loads Javascript files required for CryptX functionality. 997 * 998 * @return void 999 */ 1000 public function loadJavascriptFiles(): void 1001 { 1002 wp_enqueue_script('cryptx-js', CRYPTX_DIR_URL . 'js/cryptx.min.js', false, false, self::$cryptXOptions['load_java']); 1003 wp_enqueue_style('cryptx-styles', CRYPTX_DIR_URL . 'css/cryptx.css'); 1004 } 1005 1006 /** 1007 * Updates the CryptX settings. 1008 * 1009 * This method retrieves the current CryptX options from the database and checks if the version of CryptX 1010 * stored in the options is less than the current version of CryptX. If the version is outdated, the method 1011 * updates the necessary settings and saves the updated options back to the database. 1012 * 1013 * @return void 1014 */ 1015 private function updateCryptXSettings(): void 1016 { 1017 self::$cryptXOptions = get_option('cryptX'); 1018 if (isset(self::$cryptXOptions['version']) && version_compare(CRYPTX_VERSION, self::$cryptXOptions['version']) > 0) { 1019 if (isset(self::$cryptXOptions['version'])) { 1020 unset(self::$cryptXOptions['version']); 1021 } 1022 if (isset(self::$cryptXOptions['c2i_font'])) { 1023 unset(self::$cryptXOptions['c2i_font']); 1024 } 1025 if (isset(self::$cryptXOptions['c2i_fontRGB'])) { 1026 self::$cryptXOptions['c2i_fontRGB'] = "#" . self::$cryptXOptions['c2i_fontRGB']; 1027 } 1028 if (isset(self::$cryptXOptions['alt_uploadedimage']) && !is_int(self::$cryptXOptions['alt_uploadedimage'])) { 1029 unset(self::$cryptXOptions['alt_uploadedimage']); 1030 if (self::$cryptXOptions['opt_linktext'] == 3) { 1031 unset(self::$cryptXOptions['opt_linktext']); 1032 } 1033 } 1034 self::$cryptXOptions = wp_parse_args(self::$cryptXOptions, $this->getCryptXOptionsDefaults()); 1035 update_option('cryptX', self::$cryptXOptions); 1036 } 1037 } 1038 1039 /** 1040 * Encodes a string by replacing special characters with their corresponding HTML entities. 1041 * 1042 * @param string|null $str The string to be encoded. 1043 * 1044 * @return string The encoded string, or an array of encoded strings if an array was passed. 1045 */ 1046 private function encodeString(?string $str): string 1047 { 1048 $str = htmlentities($str, ENT_QUOTES, 'UTF-8'); 1049 $special = array( 1050 '[' => '[', 1051 ']' => ']', 1052 ); 1053 1054 return str_replace(array_keys($special), array_values($special), $str); 1055 } 1056 1057 /** 1058 * Decodes a string that has been HTML entity encoded. 1059 * 1060 * @param string|null $str The string to decode. If null, an empty string is returned. 1061 * 1062 * @return string The decoded string. 1063 */ 1064 private function decodeString(?string $str): string 1065 { 1066 return html_entity_decode($str, ENT_QUOTES, 'UTF-8'); 1067 } 1068 1069 public function convertArrayToArgumentString(array $args = []): string 1070 { 1071 $string = ""; 1072 if (!empty($args)) { 1073 foreach ($args as $key => $value) { 1074 $string .= sprintf(" %s=\"%s\"", $key, $this->encodeString($value)); 1075 } 1076 $string .= " encoded=\"true\""; 1077 } 1078 1079 return $string; 1080 } 1081 1082 /** 1083 * Check if current request is for an RSS feed 1084 * 1085 * @return bool True if current request is for an RSS feed, false otherwise 1086 */ 1087 private function isRssFeed(): bool 1088 { 1089 return is_feed(); 1090 } 1091 897 1092 } -
cryptx/tags/3.5.0/cryptx.php
r3103653 r3323475 4 4 * Plugin URI: http://weber-nrw.de/wordpress/cryptx/ 5 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. 4.5.37 * Requires at least: 6. 06 * Version: 3.5.0 7 * Requires at least: 6.7 8 8 * Author: Ralf Weber 9 9 * Author URI: http://weber-nrw.de/ … … 28 28 define( 'CRYPTX_FILENAME', str_replace( CRYPTX_BASEFOLDER . '/', '', plugin_basename( __FILE__ ) ) ); 29 29 30 require_once( CRYPTX_DIR_PATH . 'classes/CryptX.php' ); 31 require_once( CRYPTX_DIR_PATH . 'include/admin_option_page.php' ); 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); 32 36 33 $CryptX_instance = Cryptx\CryptX::getInstance(); 37 // Construct the full file path 38 $file = CRYPTX_DIR_PATH . 'classes' . DIRECTORY_SEPARATOR . $file_path . '.php'; 39 40 if (file_exists($file)) { 41 require_once $file; 42 } 43 } 44 }); 45 46 47 //require_once( CRYPTX_DIR_PATH . 'classes/CryptX.php' ); 48 //require_once( CRYPTX_DIR_PATH . 'include/admin_option_page.php' ); 49 50 $CryptX_instance = CryptX\CryptX::get_instance(); 34 51 $CryptX_instance->startCryptX(); 35 52 … … 43 60 */ 44 61 function encryptx( ?string $content, ?array $args = [] ): string { 45 $CryptX_instance = Cryptx\CryptX::get Instance();62 $CryptX_instance = Cryptx\CryptX::get_instance(); 46 63 return do_shortcode( '[cryptx'. $CryptX_instance->convertArrayToArgumentString( $args ).']' . $content . '[/cryptx]' ); 47 64 } -
cryptx/tags/3.5.0/js/cryptx.js
r3102439 r3323475 37 37 location.href=DeCryptString( encryptedUrl ); 38 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. 47 */ 48 function generateHashFromString(inputString) { 49 // Replace & with itself (this line seems redundant in the original PHP code) 50 inputString = inputString.replace("&", "&"); 51 let crypt = ''; 52 53 // ASCII values blacklist (taken from the PHP constant) 54 const ASCII_VALUES_BLACKLIST = ['32', '34', '39', '60', '62', '63', '92', '94', '96', '127']; 55 56 for (let i = 0; i < inputString.length; i++) { 57 let salt, asciiValue; 58 do { 59 // Generate random number between 0 and 3 60 salt = Math.floor(Math.random() * 4); 61 // Get ASCII value and add salt 62 asciiValue = inputString.charCodeAt(i) + salt; 63 64 // Check if value exceeds limit 65 if (8364 <= asciiValue) { 66 asciiValue = 128; 67 } 68 } while (ASCII_VALUES_BLACKLIST.includes(asciiValue.toString())); 69 70 // Append salt and character to result 71 crypt += salt + String.fromCharCode(asciiValue); 72 } 73 74 return crypt; 75 } 76 77 /** 78 * Generates a DeCryptX handler URL with a hashed email address. 79 * 80 * @param {string} emailAddress - The email address to be hashed and included in the handler URL. 81 * @return {string} A string representing the JavaScript DeCryptX handler with the hashed email address. 82 */ 83 function generateDeCryptXHandler(emailAddress) { 84 return `javascript:DeCryptX('${generateHashFromString(emailAddress)}')`; 85 } -
cryptx/tags/3.5.0/js/cryptx.min.js
r1111020 r3323475 1 function DeCryptString(r){for(var t=0,n="mailto:",o=0,e=0;e<r.length/2;e++)o=r.substr(2*e,1),t=r.charCodeAt(2*e+1),t>=8364&&(t=128),n+=String.fromCharCode(t-o);return n}function DeCryptX(r){location.href=DeCryptString(r)}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)}')`} -
cryptx/tags/3.5.0/readme.txt
r3321843 r3323475 5 5 Requires at least: 6.0 6 6 Tested up to: 6.8 7 Stable tag: 3. 4.5.38 Requires PHP: 7.47 Stable tag: 3.5.0 8 Requires PHP: 8.0 9 9 License: GPLv2 or later 10 10 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 24 24 25 25 == Changelog == 26 = 3.5.0 = 27 * Parts of the code have been rewritten to make the plugin more maintainable. 28 * fixed some bugs 29 * added option to disable CryptX on RSS feeds (requested: https://wordpress.org/support/topic/cryptx-should-be-disabled-for-rss-content/) 30 * Added new Javascript function to add CryptX mailto links via javascript on client side (requested: https://wordpress.org/support/topic/javascript-function-to-encrypt-emails/) 26 31 = 3.4.5.3 = 27 32 * fixed a Critical error in combination with WPML -
cryptx/trunk/classes/CryptX.php
r3103653 r3323475 3 3 namespace CryptX; 4 4 5 Final class CryptX { 6 7 const NOT_FOUND = false; 8 const MAIL_IDENTIFIER = 'mailto:'; 9 const SUBJECT_IDENTIFIER = "?subject="; 10 const INDEX_TO_CHECK = 4; 11 const PATTERN = '/(.*)(">)/i'; 12 const ASCII_VALUES_BLACKLIST = [ '32', '34', '39', '60', '62', '63', '92', '94', '96', '127' ]; 13 14 private static ?CryptX $_instance = null; 15 private static array $cryptXOptions = []; 16 private static array $defaults = array( 17 'version' => null, 18 'at' => ' [at] ', 19 'dot' => ' [dot] ', 20 'css_id' => '', 21 'css_class' => '', 22 'the_content' => 1, 23 'the_meta_key' => 1, 24 'the_excerpt' => 1, 25 'comment_text' => 1, 26 'widget_text' => 1, 27 'java' => 1, 28 'load_java' => 1, 29 'opt_linktext' => 0, 30 'autolink' => 1, 31 'alt_linktext' => '', 32 'alt_linkimage' => '', 33 'http_linkimage_title' => '', 34 'alt_linkimage_title' => '', 35 'excludedIDs' => '', 36 'metaBox' => 1, 37 'alt_uploadedimage' => '0', 38 'c2i_font' => null, 39 'c2i_fontSize' => 10, 40 'c2i_fontRGB' => '#000000', 41 'echo' => 1, 42 'filter' => array( 'the_content', 'the_meta_key', 'the_excerpt', 'comment_text', 'widget_text' ), 43 'whiteList' => 'jpeg,jpg,png,gif', 44 ); 45 private static int $imageCounter = 0; 46 47 private function __construct() { 48 self::$cryptXOptions = $this->loadCryptXOptionsWithDefaults(); 49 } 50 51 /** 52 * Get the instance of the CryptX class. 53 * 54 * This method returns the instance of the CryptX class. If an instance does not exist, 55 * it creates a new instance of the CryptX class and stores it in the static property. 56 * Subsequent calls to this method will return the previously created instance. 57 * 58 * @return CryptX The instance of the CryptX class. 59 */ 60 public static function getInstance(): CryptX { 61 if ( ! ( self::$_instance instanceof self ) ) { 62 self::$_instance = new self(); 63 } 64 65 return self::$_instance; 66 } 67 68 /** 69 * Starts the CryptX plugin. 70 * 71 * This method initializes and configures the CryptX plugin by performing the following actions: 72 * - Updates CryptX settings if a new version is available 73 * - Adds plugin filters based on the configured options 74 * - Adds action hooks for plugin activation, enqueueing JavaScript files, and handling meta box functionality 75 * - Adds a plugin row meta filter 76 * - Adds a filter for generating tiny URLs 77 * - Adds a shortcode for CryptX functionality 78 * 79 * @return void 80 */ 81 public function startCryptX(): void { 82 if ( isset( self::$cryptXOptions['version'] ) && version_compare( CRYPTX_VERSION, self::$cryptXOptions['version'] ) > 0 ) { 83 $this->updateCryptXSettings(); 84 } 85 foreach ( self::$cryptXOptions['filter'] as $filter ) { 86 if ( @self::$cryptXOptions[ $filter ] ) { 87 $this->addPluginFilters( $filter ); 88 } 89 } 90 add_action( 'activate_' . CRYPTX_BASENAME, [ $this, 'installCryptX' ] ); 91 add_action( 'wp_enqueue_scripts', [ $this, 'loadJavascriptFiles' ] ); 92 if ( @self::$cryptXOptions['metaBox'] ) { 93 add_action( 'admin_menu', [ $this, 'metaBox' ] ); 94 add_action( 'wp_insert_post', [ $this, 'addPostIdToExcludedList' ] ); 95 add_action( 'wp_update_post', [ $this, 'addPostIdToExcludedList' ] ); 96 } 97 add_filter( 'plugin_row_meta', 'rw_cryptx_init_row_meta', 10, 2 ); 98 add_filter( 'init', [ $this, 'cryptXtinyUrl' ] ); 99 add_shortcode( 'cryptx', [ $this, 'cryptXShortcode' ] ); 100 } 101 102 /** 103 * Returns an array of default options for CryptX. 104 * 105 * This function retrieves an array of default options for CryptX. The default options include 106 * the current version of CryptX and the first available TrueType font from the "fonts" directory. 107 * 108 * @return array The array of default options. 109 */ 110 public function getCryptXOptionsDefaults(): array { 111 $firstFont = $this->getFilesInDirectory( CRYPTX_DIR_PATH . 'fonts', [ "ttf" ] ); 112 113 return array_merge( self::$defaults, [ 'version' => CRYPTX_VERSION, 'c2i_font' => $firstFont[0] ] ); 114 } 115 116 /** 117 * Loads the cryptX options with default values. 118 * 119 * @return array The cryptX options array with default values. 120 */ 121 public function loadCryptXOptionsWithDefaults(): array { 122 $defaultValues = $this->getCryptXOptionsDefaults(); 123 $currentOptions = get_option( 'cryptX' ); 124 125 return wp_parse_args( $currentOptions, $defaultValues ); 126 } 127 128 /** 129 * Saves the cryptX options by updating the 'cryptX' option with the saved options merged with the default options. 130 * 131 * @param array $saveOptions The options to be saved. 132 * 133 * @return void 134 */ 135 public function saveCryptXOptions( array $saveOptions ): void { 136 update_option( 'cryptX', wp_parse_args( $saveOptions, $this->loadCryptXOptionsWithDefaults() ) ); 137 } 138 139 /** 140 * Generates a shortcode for encrypting email addresses in search results. 141 * 142 * @param array $atts An associative array of attributes for the shortcode. 143 * @param string $content The content inside the shortcode. 144 * @param string $tag The shortcode tag. 145 * 146 * @return string The encrypted search results content. 147 */ 148 public function cryptXShortcode( array $atts = [], string $content = '', string $tag = '' ): string { 149 if ( isset( $atts['encoded'] ) && $atts['encoded'] == "true" ) { 150 foreach ( $atts as $key => $value ) { 151 $atts[ $key ] = $this->decodeString( $value ); 152 } 153 unset( $atts['encoded'] ); 154 } 155 if(!empty($atts)) self::$cryptXOptions = shortcode_atts( $this->loadCryptXOptionsWithDefaults(), array_change_key_case( $atts, CASE_LOWER ), $tag ); 156 if ( @self::$cryptXOptions['autolink'] ) { 157 $content = $this->addLinkToEmailAddresses( $content, true ); 158 } 159 $content = $this->encryptAndLinkContent( $content, true ); 160 // reset CryptX options 161 self::$cryptXOptions = $this->loadCryptXOptionsWithDefaults(); 162 163 return $content; 164 } 165 166 /** 167 * Encrypts and links content. 168 * 169 * @param string $content The content to be encrypted and linked. 170 * 171 * @return string The encrypted and linked content. 172 */ 173 private function encryptAndLinkContent( string $content, bool $shortcode = false ): string { 174 $content = $this->findEmailAddressesInContent( $content, $shortcode ); 175 176 return $this->replaceEmailInContent( $content, $shortcode ); 177 } 178 179 /** 180 * Generates and returns a tiny URL image. 181 * 182 * @return void 183 */ 184 public function cryptXtinyUrl(): void { 185 $url = $_SERVER['REQUEST_URI']; 186 $params = explode( '/', $url ); 187 if ( count( $params ) > 1 ) { 188 $tiny_url = $params[ count( $params ) - 2 ]; 189 if ( $tiny_url == md5( get_bloginfo( 'url' ) ) ) { 190 $font = CRYPTX_DIR_PATH . 'fonts/' . self::$cryptXOptions['c2i_font']; 191 $msg = $params[ count( $params ) - 1 ]; 192 $size = self::$cryptXOptions['c2i_fontSize']; 193 $pad = 1; 194 $transparent = 1; 195 $rgb = str_replace( "#", "", self::$cryptXOptions['c2i_fontRGB'] ); 196 $red = hexdec( substr( $rgb, 0, 2 ) ); 197 $grn = hexdec( substr( $rgb, 2, 2 ) ); 198 $blu = hexdec( substr( $rgb, 4, 2 ) ); 199 $bg_red = 255 - $red; 200 $bg_grn = 255 - $grn; 201 $bg_blu = 255 - $blu; 202 $width = 0; 203 $height = 0; 204 $offset_x = 0; 205 $offset_y = 0; 206 $bounds = array(); 207 $image = ""; 208 $bounds = ImageTTFBBox( $size, 0, $font, "W" ); 209 $font_height = abs( $bounds[7] - $bounds[1] ); 210 $bounds = ImageTTFBBox( $size, 0, $font, $msg ); 211 $width = abs( $bounds[4] - $bounds[6] ); 212 $height = abs( $bounds[7] - $bounds[1] ); 213 $offset_y = $font_height + abs( ( $height - $font_height ) / 2 ) - 1; 214 $offset_x = 0; 215 $image = imagecreatetruecolor( $width + ( $pad * 2 ), $height + ( $pad * 2 ) ); 216 imagesavealpha( $image, true ); 217 $foreground = ImageColorAllocate( $image, $red, $grn, $blu ); 218 $background = imagecolorallocatealpha( $image, 0, 0, 0, 127 ); 219 imagefill( $image, 0, 0, $background ); 220 ImageTTFText( $image, $size, 0, round( $offset_x + $pad, 0 ), round( $offset_y + $pad, 0 ), $foreground, $font, $msg ); 221 Header( "Content-type: image/png" ); 222 imagePNG( $image ); 223 die; 224 } 225 } 226 } 227 228 /** 229 * Add plugin filters. 230 * 231 * This function adds the specified plugin filter if the 'autolink' key is present and its value is true in the global $cryptXOptions variable. 232 * It also adds the 'autolink' function as a filter to the $filterName if the global $shortcode_tags variable is not empty. 233 * Additionally, this function calls the addCommonFilters() and addOtherFilters() functions at specific points. 234 * 235 * @param string $filterName The name of the filter to add. 236 * 237 * @return void 238 */ 239 private function addPluginFilters( string $filterName ): void { 240 global $shortcode_tags; 241 if ( array_key_exists( 'autolink', self::$cryptXOptions ) && self::$cryptXOptions['autolink'] ) { 242 $this->addAutoLinkFilters( $filterName ); 243 if ( ! empty( $shortcode_tags ) ) { 244 $this->addAutoLinkFilters( $filterName, 11 ); 245 //add_filter($filterName, [$this,'autolink'], 11); 246 } 247 } 248 $this->addOtherFilters( $filterName ); 249 } 250 251 /** 252 * Adds common filters to a given filter name. 253 * 254 * This function adds the common filter 'autolink' to the provided $filterName. 255 * 256 * @param string $filterName The name of the filter to add common filters to. 257 * 258 * @return void 259 */ 260 private function addAutoLinkFilters( string $filterName, $prio = 5 ): void { 261 add_filter( $filterName, [ $this, 'addLinkToEmailAddresses' ], $prio ); 262 } 263 264 /** 265 * Adds additional filters to a given filter name. 266 * 267 * This function adds two additional filters, 'encryptx' and 'replaceEmailInContent', 268 * to the specified filter name. The 'encryptx' filter is added with a priority of 12, 269 * and the 'replaceEmailInContent' filter is added with a priority of 13. 270 * 271 * @param string $filterName The name of the filter to add the additional filters to. 272 * 273 * @return void 274 */ 275 private function addOtherFilters( string $filterName ): void { 276 add_filter( $filterName, [ $this, 'findEmailAddressesInContent' ], 12 ); 277 add_filter( $filterName, [ $this, 'replaceEmailInContent' ], 13 ); 278 } 279 280 /** 281 * Checks if a given ID is excluded based on the 'excludedIDs' variable. 282 * 283 * @param int $ID The ID to check if excluded. 284 * 285 * @return bool Returns true if the ID is excluded, false otherwise. 286 */ 287 private function isIdExcluded( int $ID ): bool { 288 $excludedIds = explode( ",", self::$cryptXOptions['excludedIDs'] ); 289 290 return in_array( $ID, $excludedIds ); 291 } 292 293 /** 294 * Replaces email addresses in content with link texts. 295 * 296 * @param string|null $content The content to replace the email addresses in. 297 * @param bool $isShortcode Flag indicating whether the method is called from a shortcode. 298 * 299 * @return string|null The content with replaced email addresses. 300 */ 301 public function replaceEmailInContent( ?string $content, bool $isShortcode = false ): ?string { 302 global $post; 303 $postId = ( is_object( $post ) ) ? $post->ID : - 1; 304 if (( ! $this->isIdExcluded( $postId ) || $isShortcode ) && !empty($content) ) { 305 $content = $this->replaceEmailWithLinkText( $content ); 306 } 307 308 return $content; 309 } 310 311 /** 312 * Replace email addresses in a given content with link text. 313 * 314 * @param string $content The content to search for email addresses. 315 * 316 * @return string The content with email addresses replaced with link text. 317 */ 318 private function replaceEmailWithLinkText( string $content ): string { 319 $emailPattern = "/([_a-zA-Z0-9-+]+(\.[_a-zA-Z0-9-+]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(\.[a-zA-Z]{2,}))/i"; 320 321 return preg_replace_callback( $emailPattern, [ $this, 'encodeEmailToLinkText' ], $content ); 322 } 323 324 /** 325 * Encode email address to link text. 326 * 327 * @param array $Match The matched email address. 328 * 329 * @return string The encoded link text. 330 */ 331 private function encodeEmailToLinkText( array $Match ): string { 332 if ( $this->inWhiteList( $Match ) ) { 333 return $Match[1]; 334 } 335 switch ( self::$cryptXOptions['opt_linktext'] ) { 336 case 1: 337 $text = $this->getLinkText(); 338 break; 339 case 2: 340 $text = $this->getLinkImage(); 341 break; 342 case 3: 343 $img_url = wp_get_attachment_url( self::$cryptXOptions['alt_uploadedimage'] ); 344 $text = $this->getUploadedImage( $img_url ); 345 self::$imageCounter ++; 346 break; 347 case 4: 348 $text = antispambot( $Match[1] ); 349 break; 350 case 5: 351 $text = $this->getImageFromText( $Match ); 352 self::$imageCounter ++; 353 break; 354 default: 355 $text = $this->getDefaultLinkText( $Match ); 356 } 357 358 return $text; 359 } 360 361 /** 362 * Check if the given match is in the whitelist. 363 * 364 * @param array $Match The match to check against the whitelist. 365 * 366 * @return bool True if the match is in the whitelist, false otherwise. 367 */ 368 private function inWhiteList( array $Match ): bool { 369 $whiteList = array_filter( array_map( 'trim', explode( ",", self::$cryptXOptions['whiteList'] ) ) ); 370 $tmp = explode( ".", $Match[0] ); 371 372 return in_array( end( $tmp ), $whiteList ); 373 } 374 375 /** 376 * Get the link text from cryptXOptions 377 * 378 * @return string The link text 379 */ 380 private function getLinkText(): string { 381 return self::$cryptXOptions['alt_linktext']; 382 } 383 384 /** 385 * Generate an HTML image tag with the link image URL as the source 386 * 387 * @return string The HTML image tag 388 */ 389 private function getLinkImage(): string { 390 return "<img src=\"" . self::$cryptXOptions['alt_linkimage'] . "\" class=\"cryptxImage\" alt=\"" . self::$cryptXOptions['alt_linkimage_title'] . "\" title=\"" . antispambot( self::$cryptXOptions['alt_linkimage_title'] ) . "\" />"; 391 } 392 393 /** 394 * Get the HTML tag for an uploaded image. 395 * 396 * @param string $img_url The URL of the image. 397 * 398 * @return string The HTML tag for the image. 399 */ 400 private function getUploadedImage( string $img_url ): string { 401 return "<img src=\"" . $img_url . "\" class=\"cryptxImage cryptxImage_" . self::$imageCounter . "\" alt=\"" . self::$cryptXOptions['http_linkimage_title'] . " title=\"" . antispambot( self::$cryptXOptions['http_linkimage_title'] ) . "\" />"; 402 } 403 404 /** 405 * Converts a matched image URL into an HTML image element with cryptX classes and attributes. 406 * 407 * @param array $Match The matched image URL and other related data. 408 * 409 * @return string Returns the HTML image element. 410 */ 411 private function getImageFromText( array $Match ): string { 412 return "<img src=\"" . get_bloginfo( 'url' ) . "/" . md5( get_bloginfo( 'url' ) ) . "/" . antispambot( $Match[1] ) . "\" class=\"cryptxImage cryptxImage_" . self::$imageCounter . "\" alt=\"" . antispambot( $Match[1] ) . "\" title=\"" . antispambot( $Match[1] ) . "\" />"; 413 } 414 415 /** 416 * Replaces specific characters with values from cryptX options in a given string. 417 * 418 * @param array $Match The array containing matches from a regular expression search. 419 * Array format: `[0 => string, 1 => string, ...]`. 420 * The first element is ignored, and the second element is used as input string. 421 * 422 * @return string The string with replaced characters or the original array if no matches were found. 423 * If the input string is an array, the function returns an array with replaced characters 424 * for each element. 425 */ 426 private function getDefaultLinkText( array $Match ): string { 427 $text = str_replace( "@", self::$cryptXOptions['at'], $Match[1] ); 428 429 return str_replace( ".", self::$cryptXOptions['dot'], $text ); 430 } 431 432 /** 433 * List all files in a directory that match the given filter. 434 * 435 * @param string $path The path of the directory to list files from. 436 * @param array $filter The file extensions to filter by. 437 * If it's a string, it will be converted to an array of a single element. 438 * 439 * @return array An array of file names that match the filter. 440 */ 441 public function getFilesInDirectory( string $path, array $filter ): array { 442 $directoryHandle = opendir( $path ); 443 $directoryContent = array(); 444 while ( $file = readdir( $directoryHandle ) ) { 445 $fileExtension = substr( strtolower( $file ), - 3 ); 446 if ( in_array( $fileExtension, $filter ) ) { 447 $directoryContent[] = $file; 448 } 449 } 450 451 return $directoryContent; 452 } 453 454 /** 455 * Finds and encrypts email addresses in content. 456 * 457 * @param string|null $content The content where email addresses will be searched and encrypted. 458 * @param bool $shortcode Specifies whether shortcodes should be processed or not. Default is false. 459 * 460 * @return string|null The content with encrypted email addresses, or null if $content is null. 461 */ 462 public function findEmailAddressesInContent( ?string $content, bool $shortcode = false ): ?string { 463 global $post; 464 465 if ( $content === null ) { 466 return null; 467 } 468 469 $postId = ( is_object( $post ) ) ? $post->ID : - 1; 470 471 $isIdExcluded = $this->isIdExcluded( $postId ); 472 $mailtoRegex = '/<a (.*?)(href=("|\')mailto:(.*?)("|\')(.*?)|)>\s*(.*?)\s*<\/a>/i'; 473 474 if ( ( ! $isIdExcluded || $shortcode !== null ) ) { 475 $content = preg_replace_callback( $mailtoRegex, [ $this, 'encryptEmailAddress' ], $content ); 476 } 477 478 return $content; 479 } 480 481 /** 482 * Encrypts email addresses in search results. 483 * 484 * @param array $searchResults The search results containing email addresses. 485 * 486 * @return string The search results with encrypted email addresses. 487 */ 488 private function encryptEmailAddress( array $searchResults ): string { 489 $originalValue = $searchResults[0]; 490 491 if ( strpos( $searchResults[ self::INDEX_TO_CHECK ], '@' ) === self::NOT_FOUND ) { 492 return $originalValue; 493 } 494 495 $mailReference = self::MAIL_IDENTIFIER . $searchResults[ self::INDEX_TO_CHECK ]; 496 497 if ( str_starts_with( $searchResults[ self::INDEX_TO_CHECK ], self::SUBJECT_IDENTIFIER ) ) { 498 return $originalValue; 499 } 500 501 $return = $originalValue; 502 if ( ! empty( self::$cryptXOptions['java'] ) ) { 503 $javaHandler = "javascript:DeCryptX('" . $this->generateHashFromString( $searchResults[ self::INDEX_TO_CHECK ] ) . "')"; 504 $return = str_replace( self::MAIL_IDENTIFIER . $searchResults[ self::INDEX_TO_CHECK ], $javaHandler, $originalValue ); 505 } 506 507 $return = str_replace( $mailReference, antispambot( $mailReference ), $return ); 508 509 if ( ! empty( self::$cryptXOptions['css_id'] ) ) { 510 $return = preg_replace( self::PATTERN, '$1" id="' . self::$cryptXOptions['css_id'] . '">', $return ); 511 } 512 513 if ( ! empty( self::$cryptXOptions['css_class'] ) ) { 514 $return = preg_replace( self::PATTERN, '$1" class="' . self::$cryptXOptions['css_class'] . '">', $return ); 515 } 516 517 return $return; 518 } 519 520 521 /** 522 * Generate a hash string for the given input string. 523 * 524 * @param string $inputString The input string to generate a hash for. 525 * 526 * @return string The generated hash string. 527 */ 528 private function generateHashFromString( string $inputString ): string { 529 $inputString = str_replace( "&", "&", $inputString ); 530 $crypt = ''; 531 532 for ( $i = 0; $i < strlen( $inputString ); $i ++ ) { 533 do { 534 $salt = mt_rand( 0, 3 ); 535 $asciiValue = ord( substr( $inputString, $i ) ) + $salt; 536 if ( 8364 <= $asciiValue ) { 537 $asciiValue = 128; 538 } 539 } while ( in_array( $asciiValue, self::ASCII_VALUES_BLACKLIST ) ); 540 541 $crypt .= $salt . chr( $asciiValue ); 542 } 543 544 return $crypt; 545 } 546 /** 547 * add link to email addresses 548 */ 549 /** 550 * Auto-link emails in the given content. 551 * 552 * @param string $content The content to process. 553 * @param bool $shortcode Whether the function is called from a shortcode or not. 554 * 555 * @return string The content with emails auto-linked. 556 */ 557 public function addLinkToEmailAddresses( string $content, bool $shortcode = false ): string { 558 global $post; 559 $postID = is_object( $post ) ? $post->ID : - 1; 560 561 if ( $this->isIdExcluded( $postID ) && ! $shortcode ) { 562 return $content; 563 } 564 565 $emailPattern = "[_a-zA-Z0-9-+]+(\.[_a-zA-Z0-9-+]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(\.[a-zA-Z]{2,})"; 566 $linkPattern = "<a href=\"mailto:\\2\">\\2</a>"; 567 $src = [ 568 "/([\s])($emailPattern)/si", 569 "/(>)($emailPattern)(<)/si", 570 "/(\()($emailPattern)(\))/si", 571 "/(>)($emailPattern)([\s])/si", 572 "/([\s])($emailPattern)(<)/si", 573 "/^($emailPattern)/si", 574 "/(<a[^>]*>)<a[^>]*>/", 575 "/(<\/A>)<\/A>/i" 576 ]; 577 $tar = [ 578 "\\1$linkPattern", 579 "\\1$linkPattern\\6", 580 "\\1$linkPattern\\6", 581 "\\1$linkPattern\\6", 582 "\\1$linkPattern\\6", 583 "<a href=\"mailto:\\0\">\\0</a>", 584 "\\1", 585 "\\1" 586 ]; 587 588 return preg_replace( $src, $tar, $content ); 589 } 590 591 /** 592 * Installs the CryptX plugin by updating its options and loading default values. 593 */ 594 public function installCryptX(): void { 595 global $wpdb; 596 self::$cryptXOptions['admin_notices_deprecated'] = true; 597 if ( self::$cryptXOptions['excludedIDs'] == "" ) { 598 $tmp = array(); 599 $excludes = $wpdb->get_results( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = 'cryptxoff' AND meta_value = 'true'" ); 600 if ( count( $excludes ) > 0 ) { 601 foreach ( $excludes as $exclude ) { 602 $tmp[] = $exclude->post_id; 603 } 604 sort( $tmp ); 605 self::$cryptXOptions['excludedIDs'] = implode( ",", $tmp ); 606 update_option( 'cryptX', self::$cryptXOptions ); 607 self::$cryptXOptions = $this->loadCryptXOptionsWithDefaults(); // reread Options 608 $wpdb->query( "DELETE FROM $wpdb->postmeta WHERE meta_key = 'cryptxoff'" ); 609 } 610 } 611 if ( empty( self::$cryptXOptions['c2i_font'] ) ) { 612 self::$cryptXOptions['c2i_font'] = CRYPTX_DIR_PATH . 'fonts/' . $firstFont[0]; 613 } 614 if ( empty( self::$cryptXOptions['c2i_fontSize'] ) ) { 615 self::$cryptXOptions['c2i_fontSize'] = 10; 616 } 617 if ( empty( self::$cryptXOptions['c2i_fontRGB'] ) ) { 618 self::$cryptXOptions['c2i_fontRGB'] = '000000'; 619 } 620 update_option( 'cryptX', self::$cryptXOptions ); 621 self::$cryptXOptions = $this->loadCryptXOptionsWithDefaults(); // reread Options 622 } 623 624 private function addHooksHelper( $function_name, $hook_name ): void { 625 if ( function_exists( $function_name ) ) { 626 call_user_func( $function_name, 'cryptx', 'CryptX', [ $this, 'metaCheckbox' ], $hook_name ); 627 } else { 628 add_action( "dbx_{$hook_name}_sidebar", [ $this, 'metaOptionFieldset' ] ); 629 } 630 } 631 632 public function metaBox(): void { 633 $this->addHooksHelper( 'add_meta_box', 'post' ); 634 $this->addHooksHelper( 'add_meta_box', 'page' ); 635 } 636 637 /** 638 * Displays a checkbox to disable CryptX for the current post or page. 639 * 640 * This function outputs HTML code for a checkbox that allows the user to disable CryptX 641 * functionality for the current post or page. If the current post or page ID is excluded 642 **/ 643 public function metaCheckbox(): void { 644 global $post; 645 ?> 646 <label><input type="checkbox" name="disable_cryptx_pageid" <?php if ( $this->isIdExcluded( $post->ID ) ) { 647 echo 'checked="checked"'; 648 } ?>/> 5 final class CryptX 6 { 7 8 const NOT_FOUND = false; 9 const MAIL_IDENTIFIER = 'mailto:'; 10 const SUBJECT_IDENTIFIER = "?subject="; 11 const INDEX_TO_CHECK = 4; 12 const PATTERN = '/(.*)(">)/i'; 13 const ASCII_VALUES_BLACKLIST = ['32', '34', '39', '60', '62', '63', '92', '94', '96', '127']; 14 private static ?self $instance = null; 15 private static array $cryptXOptions = []; 16 private static int $imageCounter = 0; 17 private const FONT_EXTENSION = 'ttf'; 18 private CryptXSettingsTabs $settingsTabs; 19 private Config $config; 20 21 private function __construct() 22 { 23 $this->settingsTabs = new CryptXSettingsTabs($this); 24 $this->config = new Config( get_option('cryptX') ); 25 self::$cryptXOptions = $this->loadCryptXOptionsWithDefaults(); 26 } 27 28 /** 29 * Retrieves the singleton instance of the class. 30 * 31 * @return self The singleton instance of the class. 32 */ 33 public static function get_instance(): self 34 { 35 $needs_initialization = !(self::$instance instanceof self); 36 37 if ($needs_initialization) { 38 self::$instance = new self(); 39 } 40 41 return self::$instance; 42 } 43 44 45 /** 46 * @return Config 47 */ 48 public function getConfig(): Config 49 { 50 return $this->config; 51 } 52 53 /** 54 * Initializes the CryptX plugin by setting up version checks, applying filters, registering core hooks, initializing meta boxes (if enabled), and adding additional hooks. 55 * 56 * @return void 57 */ 58 public function startCryptX(): void 59 { 60 $this->checkAndUpdateVersion(); 61 $this->initializePluginFilters(); 62 $this->registerCoreHooks(); 63 $this->initializeMetaBoxIfEnabled(); 64 $this->registerAdditionalHooks(); 65 } 66 67 /** 68 * Checks the current version of the application against the stored version and updates settings if the application version is newer. 69 * 70 * @return void 71 */ 72 private function checkAndUpdateVersion(): void 73 { 74 $currentVersion = self::$cryptXOptions['version'] ?? null; 75 if ($currentVersion && version_compare(CRYPTX_VERSION, $currentVersion) > 0) { 76 $this->updateCryptXSettings(); 77 } 78 } 79 80 /** 81 * Initializes and applies plugin filters based on the defined configuration options. 82 * 83 * @return void 84 */ 85 private function initializePluginFilters(): void 86 { 87 foreach (self::$cryptXOptions['filter'] as $filter) { 88 if (isset(self::$cryptXOptions[$filter]) && self::$cryptXOptions[$filter]) { 89 $this->addPluginFilters($filter); 90 } 91 } 92 } 93 94 /** 95 * Registers core hooks for the plugin's functionality. 96 * 97 * @return void 98 */ 99 private function registerCoreHooks(): void 100 { 101 add_action('activate_' . CRYPTX_BASENAME, [$this, 'installCryptX']); 102 add_action('wp_enqueue_scripts', [$this, 'loadJavascriptFiles']); 103 } 104 105 /** 106 * Initializes the meta box functionality if enabled in the configuration. 107 * 108 * This method checks whether the meta box feature is enabled in the cryptX options. 109 * If enabled, it adds the necessary actions for administering the meta box and managing the posts' exclusion list. 110 * 111 * @return void 112 */ 113 private function initializeMetaBoxIfEnabled(): void 114 { 115 if (!isset(self::$cryptXOptions['metaBox']) || !self::$cryptXOptions['metaBox']) { 116 return; 117 } 118 119 add_action('admin_menu', [$this, 'metaBox']); 120 add_action('wp_insert_post', [$this, 'addPostIdToExcludedList']); 121 add_action('wp_update_post', [$this, 'addPostIdToExcludedList']); 122 } 123 124 /** 125 * Registers additional WordPress hooks and shortcodes. 126 * 127 * @return void 128 */ 129 private function registerAdditionalHooks(): void 130 { 131 add_filter('plugin_row_meta', 'rw_cryptx_init_row_meta', 10, 2); 132 add_filter('init', [$this, 'cryptXtinyUrl']); 133 add_shortcode('cryptx', [$this, 'cryptXShortcode']); 134 } 135 136 /** 137 * Retrieves the default options for CryptX configuration. 138 * 139 * @return array The default CryptX options, including version and font settings. 140 */ 141 public function getCryptXOptionsDefaults(): array 142 { 143 return array_merge( 144 $this->config->getAll(), 145 [ 146 'version' => CRYPTX_VERSION, 147 'c2i_font' => $this->getDefaultFont() 148 ] 149 ); 150 } 151 152 /** 153 * Retrieves the default font from the available fonts directory. 154 * 155 * @return string|null Returns the name of the default font found, or null if no fonts are available. 156 */ 157 private function getDefaultFont(): ?string 158 { 159 $availableFonts = $this->getFilesInDirectory( 160 CRYPTX_DIR_PATH . 'fonts', 161 [self::FONT_EXTENSION] 162 ); 163 164 return $availableFonts[0] ?? null; 165 } 166 167 /** 168 * Loads the cryptX options with default values. 169 * 170 * @return array The cryptX options array with default values. 171 */ 172 public function loadCryptXOptionsWithDefaults(): array 173 { 174 $defaultValues = $this->getCryptXOptionsDefaults(); 175 $currentOptions = get_option('cryptX'); 176 177 return wp_parse_args($currentOptions, $defaultValues); 178 } 179 180 /** 181 * Saves the cryptX options by updating the 'cryptX' option with the saved options merged with the default options. 182 * 183 * @param array $saveOptions The options to be saved. 184 * 185 * @return void 186 */ 187 public function saveCryptXOptions(array $saveOptions): void 188 { 189 update_option('cryptX', wp_parse_args($saveOptions, $this->loadCryptXOptionsWithDefaults())); 190 } 191 192 /** 193 * Decodes attributes from their encoded state and returns the decoded array. 194 * 195 * @param array $attributes The array of attributes, potentially encoded. 196 * @return array The array of decoded attributes with the 'encoded' key removed if present. 197 */ 198 private function decodeAttributes(array $attributes): array 199 { 200 if (($attributes['encoded'] ?? '') !== 'true') { 201 return $attributes; 202 } 203 204 $decodedAttributes = array_map( 205 fn($value) => $this->decodeString($value), 206 $attributes 207 ); 208 unset($decodedAttributes['encoded']); 209 210 return $decodedAttributes; 211 } 212 213 /** 214 * Processes the provided shortcode attributes and content, encrypts content, and optionally creates links for email addresses. 215 * 216 * @param array $atts Attributes passed to the shortcode. Defaults to an empty array. 217 * @param string $content The content enclosed within the shortcode. Defaults to an empty string. 218 * @param string $tag The name of the shortcode tag. Defaults to an empty string. 219 * @return string The processed and encrypted content, optionally including links for email addresses. 220 */ 221 public function cryptXShortcode(array $atts = [], string $content = '', string $tag = ''): string 222 { 223 // Decode attributes if needed 224 $attributes = $this->decodeAttributes($atts); 225 226 // Update options if attributes provided 227 if (!empty($attributes)) { 228 self::$cryptXOptions = shortcode_atts( 229 $this->loadCryptXOptionsWithDefaults(), 230 array_change_key_case($attributes, CASE_LOWER), 231 $tag 232 ); 233 } 234 235 // Process content 236 if (self::$cryptXOptions['autolink'] ?? false) { 237 $content = $this->addLinkToEmailAddresses($content, true); 238 } 239 240 $processedContent = $this->encryptAndLinkContent($content, true); 241 242 // Reset options to defaults 243 self::$cryptXOptions = $this->loadCryptXOptionsWithDefaults(); 244 245 return $processedContent; 246 } 247 248 /** 249 * Encrypts and links content. 250 * 251 * @param string $content The content to be encrypted and linked. 252 * 253 * @return string The encrypted and linked content. 254 */ 255 private function encryptAndLinkContent(string $content, bool $shortcode = false): string 256 { 257 $content = $this->findEmailAddressesInContent($content, $shortcode); 258 259 return $this->replaceEmailInContent($content, $shortcode); 260 } 261 262 263 private const MAILTO_PATTERN = '/<a (.*?)(href=("|\')mailto:(.*?)("|\')(.*?)|)>\s*(.*?)\s*<\/a>/i'; 264 private const EMAIL_PATTERN = "/([_a-zA-Z0-9-+]+(\.[_a-zA-Z0-9-+]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(\.[a-zA-Z]{2,}))/i"; 265 266 private function processAndEncryptEmails(EmailProcessingConfig $config): string 267 { 268 $content = $this->encryptMailtoLinks($config); 269 return $this->encryptPlainEmails($content, $config); 270 } 271 272 private function encryptMailtoLinks(EmailProcessingConfig $config): ?string 273 { 274 $content = $config->getContent(); 275 if ($content === null) { 276 return null; 277 } 278 279 $postId = $config->getPostId() ?? $this->getCurrentPostId(); 280 281 if (!$this->isIdExcluded($postId) || $config->isShortcode()) { 282 return preg_replace_callback( 283 self::MAILTO_PATTERN, 284 [$this, 'encryptEmailAddress'], 285 $content 286 ); 287 } 288 289 return $content; 290 } 291 292 private function encryptPlainEmails(string $content, EmailProcessingConfig $config): string 293 { 294 $postId = $config->getPostId() ?? $this->getCurrentPostId(); 295 296 if ((!$this->isIdExcluded($postId) || $config->isShortcode()) && !empty($content)) { 297 return preg_replace_callback( 298 self::EMAIL_PATTERN, 299 [$this, 'encodeEmailToLinkText'], 300 $content 301 ); 302 } 303 304 return $content; 305 } 306 307 private function getCurrentPostId(): int 308 { 309 global $post; 310 return (is_object($post)) ? $post->ID : -1; 311 } 312 313 314 /** 315 * Generates and returns a tiny URL image. 316 * 317 * @return void 318 */ 319 public function cryptXtinyUrl(): void 320 { 321 $url = $_SERVER['REQUEST_URI']; 322 $params = explode('/', $url); 323 if (count($params) > 1) { 324 $tiny_url = $params[count($params) - 2]; 325 if ($tiny_url == md5(get_bloginfo('url'))) { 326 $font = CRYPTX_DIR_PATH . 'fonts/' . self::$cryptXOptions['c2i_font']; 327 $msg = $params[count($params) - 1]; 328 $size = self::$cryptXOptions['c2i_fontSize']; 329 $pad = 1; 330 $transparent = 1; 331 $rgb = str_replace("#", "", self::$cryptXOptions['c2i_fontRGB']); 332 $red = hexdec(substr($rgb, 0, 2)); 333 $grn = hexdec(substr($rgb, 2, 2)); 334 $blu = hexdec(substr($rgb, 4, 2)); 335 $bg_red = 255 - $red; 336 $bg_grn = 255 - $grn; 337 $bg_blu = 255 - $blu; 338 $width = 0; 339 $height = 0; 340 $offset_x = 0; 341 $offset_y = 0; 342 $bounds = array(); 343 $image = ""; 344 $bounds = ImageTTFBBox($size, 0, $font, "W"); 345 $font_height = abs($bounds[7] - $bounds[1]); 346 $bounds = ImageTTFBBox($size, 0, $font, $msg); 347 $width = abs($bounds[4] - $bounds[6]); 348 $height = abs($bounds[7] - $bounds[1]); 349 $offset_y = $font_height + abs(($height - $font_height) / 2) - 1; 350 $offset_x = 0; 351 $image = imagecreatetruecolor($width + ($pad * 2), $height + ($pad * 2)); 352 imagesavealpha($image, true); 353 $foreground = ImageColorAllocate($image, $red, $grn, $blu); 354 $background = imagecolorallocatealpha($image, 0, 0, 0, 127); 355 imagefill($image, 0, 0, $background); 356 ImageTTFText($image, $size, 0, round($offset_x + $pad, 0), round($offset_y + $pad, 0), $foreground, $font, $msg); 357 Header("Content-type: image/png"); 358 imagePNG($image); 359 die; 360 } 361 } 362 } 363 364 /** 365 * Add plugin filters. 366 * 367 * This function adds the specified plugin filter if the 'autolink' key is present and its value is true in the global $cryptXOptions variable. 368 * It also adds the 'autolink' function as a filter to the $filterName if the global $shortcode_tags variable is not empty. 369 * Additionally, this function calls the addCommonFilters() and addOtherFilters() functions at specific points. 370 * 371 * @param string $filterName The name of the filter to add. 372 * 373 * @return void 374 */ 375 private function addPluginFilters(string $filterName): void 376 { 377 global $shortcode_tags; 378 379 if (array_key_exists('autolink', self::$cryptXOptions) && self::$cryptXOptions['autolink']) { 380 $this->addAutoLinkFilters($filterName); 381 if (!empty($shortcode_tags)) { 382 $this->addAutoLinkFilters($filterName, 11); 383 } 384 } 385 $this->addOtherFilters($filterName); 386 } 387 388 /** 389 * Adds common filters to a given filter name. 390 * 391 * This function adds the common filter 'autolink' to the provided $filterName. 392 * 393 * @param string $filterName The name of the filter to add common filters to. 394 * 395 * @return void 396 */ 397 private function addAutoLinkFilters(string $filterName, $prio = 5): void 398 { 399 add_filter($filterName, [$this, 'addLinkToEmailAddresses'], $prio); 400 } 401 402 /** 403 * Adds additional filters to a given filter name. 404 * 405 * This function adds two additional filters, 'encryptx' and 'replaceEmailInContent', 406 * to the specified filter name. The 'encryptx' filter is added with a priority of 12, 407 * and the 'replaceEmailInContent' filter is added with a priority of 13. 408 * 409 * @param string $filterName The name of the filter to add the additional filters to. 410 * 411 * @return void 412 */ 413 private function addOtherFilters(string $filterName): void 414 { 415 add_filter($filterName, [$this, 'findEmailAddressesInContent'], 12); 416 add_filter($filterName, [$this, 'replaceEmailInContent'], 13); 417 } 418 419 /** 420 * Checks if a given ID is excluded based on the 'excludedIDs' variable. 421 * 422 * @param int $ID The ID to check if excluded. 423 * 424 * @return bool Returns true if the ID is excluded, false otherwise. 425 */ 426 private function isIdExcluded(int $ID): bool 427 { 428 $excludedIds = explode(",", self::$cryptXOptions['excludedIDs']); 429 430 return in_array($ID, $excludedIds); 431 } 432 433 /** 434 * Replaces email addresses in content with link texts. 435 * 436 * @param string|null $content The content to replace the email addresses in. 437 * @param bool $isShortcode Flag indicating whether the method is called from a shortcode. 438 * 439 * @return string|null The content with replaced email addresses. 440 */ 441 public function replaceEmailInContent(?string $content, bool $isShortcode = false): ?string 442 { 443 global $post; 444 445 if (self::$cryptXOptions['disable_rss'] && $this->isRssFeed()) return $content; 446 447 $postId = (is_object($post)) ? $post->ID : -1; 448 if ((!$this->isIdExcluded($postId) || $isShortcode) && !empty($content)) { 449 $content = $this->replaceEmailWithLinkText($content); 450 } 451 452 return $content; 453 } 454 455 /** 456 * Replace email addresses in a given content with link text. 457 * 458 * @param string $content The content to search for email addresses. 459 * 460 * @return string The content with email addresses replaced with link text. 461 */ 462 private function replaceEmailWithLinkText(string $content): string 463 { 464 $emailPattern = "/([_a-zA-Z0-9-+]+(\.[_a-zA-Z0-9-+]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(\.[a-zA-Z]{2,}))/i"; 465 466 return preg_replace_callback($emailPattern, [$this, 'encodeEmailToLinkText'], $content); 467 } 468 469 /** 470 * Encode email address to link text. 471 * 472 * @param array $Match The matched email address. 473 * 474 * @return string The encoded link text. 475 */ 476 private function encodeEmailToLinkText(array $Match): string 477 { 478 if ($this->inWhiteList($Match)) { 479 return $Match[1]; 480 } 481 switch (self::$cryptXOptions['opt_linktext']) { 482 case 1: 483 $text = $this->getLinkText(); 484 break; 485 case 2: 486 $text = $this->getLinkImage(); 487 break; 488 case 3: 489 $img_url = wp_get_attachment_url(self::$cryptXOptions['alt_uploadedimage']); 490 $text = $this->getUploadedImage($img_url); 491 self::$imageCounter++; 492 break; 493 case 4: 494 $text = antispambot($Match[1]); 495 break; 496 case 5: 497 $text = $this->getImageFromText($Match); 498 self::$imageCounter++; 499 break; 500 default: 501 $text = $this->getDefaultLinkText($Match); 502 } 503 504 return $text; 505 } 506 507 /** 508 * Check if the given match is in the whitelist. 509 * 510 * @param array $Match The match to check against the whitelist. 511 * 512 * @return bool True if the match is in the whitelist, false otherwise. 513 */ 514 private function inWhiteList(array $Match): bool 515 { 516 $whiteList = array_filter(array_map('trim', explode(",", self::$cryptXOptions['whiteList']))); 517 $tmp = explode(".", $Match[0]); 518 519 return in_array(end($tmp), $whiteList); 520 } 521 522 /** 523 * Get the link text from cryptXOptions 524 * 525 * @return string The link text 526 */ 527 private function getLinkText(): string 528 { 529 return self::$cryptXOptions['alt_linktext']; 530 } 531 532 /** 533 * Generate an HTML image tag with the link image URL as the source 534 * 535 * @return string The HTML image tag 536 */ 537 private function getLinkImage(): string 538 { 539 return "<img src=\"" . self::$cryptXOptions['alt_linkimage'] . "\" class=\"cryptxImage\" alt=\"" . self::$cryptXOptions['alt_linkimage_title'] . "\" title=\"" . antispambot(self::$cryptXOptions['alt_linkimage_title']) . "\" />"; 540 } 541 542 /** 543 * Get the HTML tag for an uploaded image. 544 * 545 * @param string $img_url The URL of the image. 546 * 547 * @return string The HTML tag for the image. 548 */ 549 private function getUploadedImage(string $img_url): string 550 { 551 return "<img src=\"" . $img_url . "\" class=\"cryptxImage cryptxImage_" . self::$imageCounter . "\" alt=\"" . self::$cryptXOptions['http_linkimage_title'] . " title=\"" . antispambot(self::$cryptXOptions['http_linkimage_title']) . "\" />"; 552 } 553 554 /** 555 * Converts a matched image URL into an HTML image element with cryptX classes and attributes. 556 * 557 * @param array $Match The matched image URL and other related data. 558 * 559 * @return string Returns the HTML image element. 560 */ 561 private function getImageFromText(array $Match): string 562 { 563 return "<img src=\"" . get_bloginfo('url') . "/" . md5(get_bloginfo('url')) . "/" . antispambot($Match[1]) . "\" class=\"cryptxImage cryptxImage_" . self::$imageCounter . "\" alt=\"" . antispambot($Match[1]) . "\" title=\"" . antispambot($Match[1]) . "\" />"; 564 } 565 566 /** 567 * Replaces specific characters with values from cryptX options in a given string. 568 * 569 * @param array $Match The array containing matches from a regular expression search. 570 * Array format: `[0 => string, 1 => string, ...]`. 571 * The first element is ignored, and the second element is used as input string. 572 * 573 * @return string The string with replaced characters or the original array if no matches were found. 574 * If the input string is an array, the function returns an array with replaced characters 575 * for each element. 576 */ 577 private function getDefaultLinkText(array $Match): string 578 { 579 $text = str_replace("@", self::$cryptXOptions['at'], $Match[1]); 580 581 return str_replace(".", self::$cryptXOptions['dot'], $text); 582 } 583 584 /** 585 * List all files in a directory that match the given filter. 586 * 587 * @param string $path The path of the directory to list files from. 588 * @param array $filter The file extensions to filter by. 589 * If it's a string, it will be converted to an array of a single element. 590 * 591 * @return array An array of file names that match the filter. 592 */ 593 public function getFilesInDirectory(string $path, array $filter): array 594 { 595 $directoryHandle = opendir($path); 596 $directoryContent = array(); 597 while ($file = readdir($directoryHandle)) { 598 $fileExtension = substr(strtolower($file), -3); 599 if (in_array($fileExtension, $filter)) { 600 $directoryContent[] = $file; 601 } 602 } 603 604 return $directoryContent; 605 } 606 607 /** 608 * Finds and encrypts email addresses in content. 609 * 610 * @param string|null $content The content where email addresses will be searched and encrypted. 611 * @param bool $shortcode Specifies whether shortcodes should be processed or not. Default is false. 612 * 613 * @return string|null The content with encrypted email addresses, or null if $content is null. 614 */ 615 public function findEmailAddressesInContent(?string $content, bool $shortcode = false): ?string 616 { 617 global $post; 618 619 if (self::$cryptXOptions['disable_rss'] && $this->isRssFeed()) return $content; 620 621 if ($content === null) { 622 return null; 623 } 624 625 // Skip processing for RSS feeds if the option is enabled 626 /* if (self::$cryptXOptions['disable_rss'] && $this->isRssFeed()) { 627 return $content; 628 }*/ 629 630 $postId = (is_object($post)) ? $post->ID : -1; 631 632 $isIdExcluded = $this->isIdExcluded($postId); 633 $mailtoRegex = '/<a (.*?)(href=("|\')mailto:(.*?)("|\')(.*?)|)>\s*(.*?)\s*<\/a>/i'; 634 635 if ((!$isIdExcluded || $shortcode !== null)) { 636 $content = preg_replace_callback($mailtoRegex, [$this, 'encryptEmailAddress'], $content); 637 } 638 639 return $content; 640 } 641 642 /** 643 * Encrypts email addresses in search results. 644 * 645 * @param array $searchResults The search results containing email addresses. 646 * 647 * @return string The search results with encrypted email addresses. 648 */ 649 private function encryptEmailAddress(array $searchResults): string 650 { 651 $originalValue = $searchResults[0]; 652 653 if (strpos($searchResults[self::INDEX_TO_CHECK], '@') === self::NOT_FOUND) { 654 return $originalValue; 655 } 656 657 $mailReference = self::MAIL_IDENTIFIER . $searchResults[self::INDEX_TO_CHECK]; 658 659 if (str_starts_with($searchResults[self::INDEX_TO_CHECK], self::SUBJECT_IDENTIFIER)) { 660 return $originalValue; 661 } 662 663 $return = $originalValue; 664 if (!empty(self::$cryptXOptions['java'])) { 665 $javaHandler = "javascript:DeCryptX('" . $this->generateHashFromString($searchResults[self::INDEX_TO_CHECK]) . "')"; 666 $return = str_replace(self::MAIL_IDENTIFIER . $searchResults[self::INDEX_TO_CHECK], $javaHandler, $originalValue); 667 } 668 669 $return = str_replace($mailReference, antispambot($mailReference), $return); 670 671 if (!empty(self::$cryptXOptions['css_id'])) { 672 $return = preg_replace(self::PATTERN, '$1" id="' . self::$cryptXOptions['css_id'] . '">', $return); 673 } 674 675 if (!empty(self::$cryptXOptions['css_class'])) { 676 $return = preg_replace(self::PATTERN, '$1" class="' . self::$cryptXOptions['css_class'] . '">', $return); 677 } 678 679 return $return; 680 } 681 682 /** 683 * Generate a hash string for the given input string. 684 * 685 * @param string $inputString The input string to generate a hash for. 686 * 687 * @return string The generated hash string. 688 */ 689 private function generateHashFromString(string $inputString): string 690 { 691 $inputString = str_replace("&", "&", $inputString); 692 $crypt = ''; 693 694 for ($i = 0; $i < strlen($inputString); $i++) { 695 do { 696 $salt = mt_rand(0, 3); 697 $asciiValue = ord(substr($inputString, $i)) + $salt; 698 if (8364 <= $asciiValue) { 699 $asciiValue = 128; 700 } 701 } while (in_array($asciiValue, self::ASCII_VALUES_BLACKLIST)); 702 703 $crypt .= $salt . chr($asciiValue); 704 } 705 706 return $crypt; 707 } 708 709 /** 710 * add link to email addresses 711 */ 712 /** 713 * Auto-link emails in the given content. 714 * 715 * @param string $content The content to process. 716 * @param bool $shortcode Whether the function is called from a shortcode or not. 717 * 718 * @return string The content with emails auto-linked. 719 */ 720 public function addLinkToEmailAddresses(string $content, bool $shortcode = false): string 721 { 722 global $post; 723 $postID = is_object($post) ? $post->ID : -1; 724 725 if ($this->isIdExcluded($postID) && !$shortcode) { 726 return $content; 727 } 728 729 $emailPattern = "[_a-zA-Z0-9-+]+(\.[_a-zA-Z0-9-+]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(\.[a-zA-Z]{2,})"; 730 $linkPattern = "<a href=\"mailto:\\2\">\\2</a>"; 731 $src = [ 732 "/([\s])($emailPattern)/si", 733 "/(>)($emailPattern)(<)/si", 734 "/(\()($emailPattern)(\))/si", 735 "/(>)($emailPattern)([\s])/si", 736 "/([\s])($emailPattern)(<)/si", 737 "/^($emailPattern)/si", 738 "/(<a[^>]*>)<a[^>]*>/", 739 "/(<\/A>)<\/A>/i" 740 ]; 741 $tar = [ 742 "\\1$linkPattern", 743 "\\1$linkPattern\\6", 744 "\\1$linkPattern\\6", 745 "\\1$linkPattern\\6", 746 "\\1$linkPattern\\6", 747 "<a href=\"mailto:\\0\">\\0</a>", 748 "\\1", 749 "\\1" 750 ]; 751 752 return preg_replace($src, $tar, $content); 753 } 754 755 /** 756 * Installs the CryptX plugin by updating its options and loading default values. 757 */ 758 public function installCryptX(): void 759 { 760 global $wpdb; 761 self::$cryptXOptions['admin_notices_deprecated'] = true; 762 if (self::$cryptXOptions['excludedIDs'] == "") { 763 $tmp = array(); 764 $excludes = $wpdb->get_results("SELECT post_id FROM $wpdb->postmeta WHERE meta_key = 'cryptxoff' AND meta_value = 'true'"); 765 if (count($excludes) > 0) { 766 foreach ($excludes as $exclude) { 767 $tmp[] = $exclude->post_id; 768 } 769 sort($tmp); 770 self::$cryptXOptions['excludedIDs'] = implode(",", $tmp); 771 update_option('cryptX', self::$cryptXOptions); 772 self::$cryptXOptions = $this->loadCryptXOptionsWithDefaults(); // reread Options 773 $wpdb->query("DELETE FROM $wpdb->postmeta WHERE meta_key = 'cryptxoff'"); 774 } 775 } 776 if (empty(self::$cryptXOptions['c2i_font'])) { 777 self::$cryptXOptions['c2i_font'] = CRYPTX_DIR_PATH . 'fonts/' . $firstFont[0]; 778 } 779 if (empty(self::$cryptXOptions['c2i_fontSize'])) { 780 self::$cryptXOptions['c2i_fontSize'] = 10; 781 } 782 if (empty(self::$cryptXOptions['c2i_fontRGB'])) { 783 self::$cryptXOptions['c2i_fontRGB'] = '000000'; 784 } 785 update_option('cryptX', self::$cryptXOptions); 786 self::$cryptXOptions = $this->loadCryptXOptionsWithDefaults(); // reread Options 787 } 788 789 private function addHooksHelper($function_name, $hook_name): void 790 { 791 if (function_exists($function_name)) { 792 call_user_func($function_name, 'cryptx', 'CryptX', [$this, 'metaCheckbox'], $hook_name); 793 } else { 794 add_action("dbx_{$hook_name}_sidebar", [$this, 'metaOptionFieldset']); 795 } 796 } 797 798 public function metaBox(): void 799 { 800 $this->addHooksHelper('add_meta_box', 'post'); 801 $this->addHooksHelper('add_meta_box', 'page'); 802 } 803 804 /** 805 * Displays a checkbox to disable CryptX for the current post or page. 806 * 807 * This function outputs HTML code for a checkbox that allows the user to disable CryptX 808 * functionality for the current post or page. If the current post or page ID is excluded 809 **/ 810 public function metaCheckbox(): void 811 { 812 global $post; 813 ?> 814 <label><input type="checkbox" name="disable_cryptx_pageid" <?php if ($this->isIdExcluded($post->ID)) { 815 echo 'checked="checked"'; 816 } ?>/> 649 817 Disable CryptX for this post/page</label> 650 <?php 651 } 652 653 /** 654 * Renders the CryptX option fieldset for the current post/page if the user has permission to edit posts. 655 * This fieldset allows the user to enable or disable CryptX for the current post/page. 656 * 657 * @return void 658 */ 659 public function metaOptionFieldset(): void { 660 global $post; 661 if ( current_user_can( 'edit_posts' ) ) { ?> 818 <?php 819 } 820 821 /** 822 * Renders the CryptX option fieldset for the current post/page if the user has permission to edit posts. 823 * This fieldset allows the user to enable or disable CryptX for the current post/page. 824 * 825 * @return void 826 */ 827 public function metaOptionFieldset(): void 828 { 829 global $post; 830 if (current_user_can('edit_posts')) { ?> 662 831 <fieldset id="cryptxoption" class="dbx-box"> 663 832 <h3 class="dbx-handle">CryptX</h3> 664 833 <div class="dbx-content"> 665 834 <label><input type="checkbox" 666 name="disable_cryptx_pageid" <?php if ( $this->isIdExcluded( $post->ID )) {667 echo 'checked="checked"';668 } ?>/> Disable CryptX for this post/page</label>835 name="disable_cryptx_pageid" <?php if ($this->isIdExcluded($post->ID)) { 836 echo 'checked="checked"'; 837 } ?>/> Disable CryptX for this post/page</label> 669 838 </div> 670 839 </fieldset> 671 <?php 672 } 673 } 674 675 /** 676 * Adds a post ID to the excluded list in the cryptX options. 677 * 678 * @param int $postId The post ID to be added to the excluded list. 679 * 680 * @return void 681 */ 682 public function addPostIdToExcludedList( int $postId ): void { 683 $postId = wp_is_post_revision( $postId ) ?: $postId; 684 $excludedIds = $this->updateExcludedIdsList( self::$cryptXOptions['excludedIDs'], $postId ); 685 self::$cryptXOptions['excludedIDs'] = implode( ",", array_filter( $excludedIds ) ); 686 update_option( 'cryptX', self::$cryptXOptions ); 687 } 688 689 /** 690 * Updates the excluded IDs list based on a given ID and the current list. 691 * 692 * @param string $excludedIds The current excluded IDs list, separated by commas. 693 * @param int $postId The ID to be updated in the excluded IDs list. 694 * 695 * @return array The updated excluded IDs list as an array, with the ID removed if it existed and added if necessary. 696 */ 697 private function updateExcludedIdsList( string $excludedIds, int $postId ): array { 698 $excludedIdsArray = explode( ",", $excludedIds ); 699 $excludedIdsArray = $this->removePostIdFromExcludedIds( $excludedIdsArray, $postId ); 700 $excludedIdsArray = $this->addPostIdToExcludedIdsIfNecessary( $excludedIdsArray, $postId ); 701 702 return $this->makeExcludedIdsUniqueAndSorted( $excludedIdsArray ); 703 } 704 705 /** 706 * Removes a specific post ID from the array of excluded IDs. 707 * 708 * @param array $excludedIds The array of excluded IDs. 709 * @param int $postId The ID of the post to be removed from the excluded IDs. 710 * 711 * @return array The updated array of excluded IDs without the specified post ID. 712 */ 713 private function removePostIdFromExcludedIds( array $excludedIds, int $postId ): array { 714 foreach ( $excludedIds as $key => $id ) { 715 if ( $id == $postId ) { 716 unset( $excludedIds[ $key ] ); 717 break; 718 } 719 } 720 721 return $excludedIds; 722 } 723 724 /** 725 * Adds the post ID to the list of excluded IDs if necessary. 726 * 727 * @param array $excludedIds The array of excluded IDs. 728 * @param int $postId The post ID to be added to the excluded IDs. 729 * 730 * @return array The updated array of excluded IDs. 731 */ 732 private function addPostIdToExcludedIdsIfNecessary( array $excludedIds, int $postId ): array { 733 if ( isset( $_POST['disable_cryptx_pageid'] ) ) { 734 $excludedIds[] = $postId; 735 } 736 737 return $excludedIds; 738 } 739 740 /** 741 * Makes the excluded IDs unique and sorted. 742 * 743 * @param array $excludedIds The array of excluded IDs. 744 * 745 * @return array The array of excluded IDs with duplicate values removed and sorted in ascending order. 746 */ 747 private function makeExcludedIdsUniqueAndSorted( array $excludedIds ): array { 748 $excludedIds = array_unique( $excludedIds ); 749 sort( $excludedIds ); 750 751 return $excludedIds; 752 } 753 754 /** 755 * Displays a message in a styled div. 756 * 757 * @param string $message The message to be displayed. 758 * @param bool $errormsg Optional. Indicates whether the message is an error message. Default is false. 759 * 760 * @return void 761 */ 762 private function showMessage( string $message, bool $errormsg = false ): void { 763 if ( $errormsg ) { 764 echo '<div id="message" class="error">'; 765 } else { 766 echo '<div id="message" class="updated fade">'; 767 } 768 769 echo "$message</div>"; 770 } 771 772 /** 773 * Retrieves the domain from the current site URL. 774 * 775 * @return string The domain of the current site URL. 776 */ 777 public function getDomain(): string { 778 return $this->trimSlashFromDomain( $this->removeProtocolFromUrl( $this->getSiteUrl() ) ); 779 } 780 781 /** 782 * Retrieves the site URL. 783 * 784 * @return string The site URL. 785 */ 786 private function getSiteUrl(): string { 787 return get_option( 'siteurl' ); 788 } 789 790 /** 791 * Removes the protocol from a URL. 792 * 793 * @param string $url The URL string to remove the protocol from. 794 * 795 * @return string The URL string without the protocol. 796 */ 797 private function removeProtocolFromUrl( string $url ): string { 798 return preg_replace( '|https?://|', '', $url ); 799 } 800 801 /** 802 * Trims the trailing slash from a domain. 803 * 804 * @param string $domain The domain to trim the slash from. 805 * 806 * @return string The domain with the trailing slash removed. 807 */ 808 private function trimSlashFromDomain( string $domain ): string { 809 if ( $slashPosition = strpos( $domain, '/' ) ) { 810 $domain = substr( $domain, 0, $slashPosition ); 811 } 812 813 return $domain; 814 } 815 816 /** 817 * Loads Javascript files required for CryptX functionality. 818 * 819 * @return void 820 */ 821 public function loadJavascriptFiles(): void { 822 wp_enqueue_script( 'cryptx-js', CRYPTX_DIR_URL . 'js/cryptx.min.js', false, false, self::$cryptXOptions['load_java'] ); 823 wp_enqueue_style( 'cryptx-styles', CRYPTX_DIR_URL . 'css/cryptx.css' ); 824 } 825 826 /** 827 * Updates the CryptX settings. 828 * 829 * This method retrieves the current CryptX options from the database and checks if the version of CryptX 830 * stored in the options is less than the current version of CryptX. If the version is outdated, the method 831 * updates the necessary settings and saves the updated options back to the database. 832 * 833 * @return void 834 */ 835 private function updateCryptXSettings(): void { 836 self::$cryptXOptions = get_option( 'cryptX' ); 837 if ( isset( self::$cryptXOptions['version'] ) && version_compare( CRYPTX_VERSION, self::$cryptXOptions['version'] ) > 0 ) { 838 if ( isset( self::$cryptXOptions['version'] ) ) { 839 unset( self::$cryptXOptions['version'] ); 840 } 841 if ( isset( self::$cryptXOptions['c2i_font'] ) ) { 842 unset( self::$cryptXOptions['c2i_font'] ); 843 } 844 if ( isset( self::$cryptXOptions['c2i_fontRGB'] ) ) { 845 self::$cryptXOptions['c2i_fontRGB'] = "#" . self::$cryptXOptions['c2i_fontRGB']; 846 } 847 if ( isset( self::$cryptXOptions['alt_uploadedimage'] ) && ! is_int( self::$cryptXOptions['alt_uploadedimage'] ) ) { 848 unset( self::$cryptXOptions['alt_uploadedimage'] ); 849 if ( self::$cryptXOptions['opt_linktext'] == 3 ) { 850 unset( self::$cryptXOptions['opt_linktext'] ); 851 } 852 } 853 self::$cryptXOptions = wp_parse_args( self::$cryptXOptions, $this->getCryptXOptionsDefaults() ); 854 update_option( 'cryptX', self::$cryptXOptions ); 855 } 856 } 857 858 /** 859 * Encodes a string by replacing special characters with their corresponding HTML entities. 860 * 861 * @param string|null $str The string to be encoded. 862 * 863 * @return string The encoded string, or an array of encoded strings if an array was passed. 864 */ 865 private function encodeString( ?string $str ): string { 866 $str = htmlentities( $str, ENT_QUOTES, 'UTF-8' ); 867 $special = array( 868 '[' => '[', 869 ']' => ']', 870 ); 871 872 return str_replace( array_keys( $special ), array_values( $special ), $str ); 873 } 874 875 /** 876 * Decodes a string that has been HTML entity encoded. 877 * 878 * @param string|null $str The string to decode. If null, an empty string is returned. 879 * 880 * @return string The decoded string. 881 */ 882 private function decodeString( ?string $str ): string { 883 return html_entity_decode( $str, ENT_QUOTES, 'UTF-8' ); 884 } 885 886 public function convertArrayToArgumentString( array $args = [] ): string { 887 $string = ""; 888 if ( ! empty( $args ) ) { 889 foreach ( $args as $key => $value ) { 890 $string .= sprintf( " %s=\"%s\"", $key, $this->encodeString( $value ) ); 891 } 892 $string .= " encoded=\"true\""; 893 } 894 895 return $string; 896 } 840 <?php 841 } 842 } 843 844 /** 845 * Adds a post ID to the excluded list in the cryptX options. 846 * 847 * @param int $postId The post ID to be added to the excluded list. 848 * 849 * @return void 850 */ 851 public function addPostIdToExcludedList(int $postId): void 852 { 853 $postId = wp_is_post_revision($postId) ?: $postId; 854 $excludedIds = $this->updateExcludedIdsList(self::$cryptXOptions['excludedIDs'], $postId); 855 self::$cryptXOptions['excludedIDs'] = implode(",", array_filter($excludedIds)); 856 update_option('cryptX', self::$cryptXOptions); 857 } 858 859 /** 860 * Updates the excluded IDs list based on a given ID and the current list. 861 * 862 * @param string $excludedIds The current excluded IDs list, separated by commas. 863 * @param int $postId The ID to be updated in the excluded IDs list. 864 * 865 * @return array The updated excluded IDs list as an array, with the ID removed if it existed and added if necessary. 866 */ 867 private function updateExcludedIdsList(string $excludedIds, int $postId): array 868 { 869 $excludedIdsArray = explode(",", $excludedIds); 870 $excludedIdsArray = $this->removePostIdFromExcludedIds($excludedIdsArray, $postId); 871 $excludedIdsArray = $this->addPostIdToExcludedIdsIfNecessary($excludedIdsArray, $postId); 872 873 return $this->makeExcludedIdsUniqueAndSorted($excludedIdsArray); 874 } 875 876 /** 877 * Removes a specific post ID from the array of excluded IDs. 878 * 879 * @param array $excludedIds The array of excluded IDs. 880 * @param int $postId The ID of the post to be removed from the excluded IDs. 881 * 882 * @return array The updated array of excluded IDs without the specified post ID. 883 */ 884 private function removePostIdFromExcludedIds(array $excludedIds, int $postId): array 885 { 886 foreach ($excludedIds as $key => $id) { 887 if ($id == $postId) { 888 unset($excludedIds[$key]); 889 break; 890 } 891 } 892 893 return $excludedIds; 894 } 895 896 /** 897 * Adds the post ID to the list of excluded IDs if necessary. 898 * 899 * @param array $excludedIds The array of excluded IDs. 900 * @param int $postId The post ID to be added to the excluded IDs. 901 * 902 * @return array The updated array of excluded IDs. 903 */ 904 private function addPostIdToExcludedIdsIfNecessary(array $excludedIds, int $postId): array 905 { 906 if (isset($_POST['disable_cryptx_pageid'])) { 907 $excludedIds[] = $postId; 908 } 909 910 return $excludedIds; 911 } 912 913 /** 914 * Makes the excluded IDs unique and sorted. 915 * 916 * @param array $excludedIds The array of excluded IDs. 917 * 918 * @return array The array of excluded IDs with duplicate values removed and sorted in ascending order. 919 */ 920 private function makeExcludedIdsUniqueAndSorted(array $excludedIds): array 921 { 922 $excludedIds = array_unique($excludedIds); 923 sort($excludedIds); 924 925 return $excludedIds; 926 } 927 928 /** 929 * Displays a message in a styled div. 930 * 931 * @param string $message The message to be displayed. 932 * @param bool $errormsg Optional. Indicates whether the message is an error message. Default is false. 933 * 934 * @return void 935 */ 936 private function showMessage(string $message, bool $errormsg = false): void 937 { 938 if ($errormsg) { 939 echo '<div id="message" class="error">'; 940 } else { 941 echo '<div id="message" class="updated fade">'; 942 } 943 944 echo "$message</div>"; 945 } 946 947 /** 948 * Retrieves the domain from the current site URL. 949 * 950 * @return string The domain of the current site URL. 951 */ 952 public function getDomain(): string 953 { 954 return $this->trimSlashFromDomain($this->removeProtocolFromUrl($this->getSiteUrl())); 955 } 956 957 /** 958 * Retrieves the site URL. 959 * 960 * @return string The site URL. 961 */ 962 private function getSiteUrl(): string 963 { 964 return get_option('siteurl'); 965 } 966 967 /** 968 * Removes the protocol from a URL. 969 * 970 * @param string $url The URL string to remove the protocol from. 971 * 972 * @return string The URL string without the protocol. 973 */ 974 private function removeProtocolFromUrl(string $url): string 975 { 976 return preg_replace('|https?://|', '', $url); 977 } 978 979 /** 980 * Trims the trailing slash from a domain. 981 * 982 * @param string $domain The domain to trim the slash from. 983 * 984 * @return string The domain with the trailing slash removed. 985 */ 986 private function trimSlashFromDomain(string $domain): string 987 { 988 if ($slashPosition = strpos($domain, '/')) { 989 $domain = substr($domain, 0, $slashPosition); 990 } 991 992 return $domain; 993 } 994 995 /** 996 * Loads Javascript files required for CryptX functionality. 997 * 998 * @return void 999 */ 1000 public function loadJavascriptFiles(): void 1001 { 1002 wp_enqueue_script('cryptx-js', CRYPTX_DIR_URL . 'js/cryptx.min.js', false, false, self::$cryptXOptions['load_java']); 1003 wp_enqueue_style('cryptx-styles', CRYPTX_DIR_URL . 'css/cryptx.css'); 1004 } 1005 1006 /** 1007 * Updates the CryptX settings. 1008 * 1009 * This method retrieves the current CryptX options from the database and checks if the version of CryptX 1010 * stored in the options is less than the current version of CryptX. If the version is outdated, the method 1011 * updates the necessary settings and saves the updated options back to the database. 1012 * 1013 * @return void 1014 */ 1015 private function updateCryptXSettings(): void 1016 { 1017 self::$cryptXOptions = get_option('cryptX'); 1018 if (isset(self::$cryptXOptions['version']) && version_compare(CRYPTX_VERSION, self::$cryptXOptions['version']) > 0) { 1019 if (isset(self::$cryptXOptions['version'])) { 1020 unset(self::$cryptXOptions['version']); 1021 } 1022 if (isset(self::$cryptXOptions['c2i_font'])) { 1023 unset(self::$cryptXOptions['c2i_font']); 1024 } 1025 if (isset(self::$cryptXOptions['c2i_fontRGB'])) { 1026 self::$cryptXOptions['c2i_fontRGB'] = "#" . self::$cryptXOptions['c2i_fontRGB']; 1027 } 1028 if (isset(self::$cryptXOptions['alt_uploadedimage']) && !is_int(self::$cryptXOptions['alt_uploadedimage'])) { 1029 unset(self::$cryptXOptions['alt_uploadedimage']); 1030 if (self::$cryptXOptions['opt_linktext'] == 3) { 1031 unset(self::$cryptXOptions['opt_linktext']); 1032 } 1033 } 1034 self::$cryptXOptions = wp_parse_args(self::$cryptXOptions, $this->getCryptXOptionsDefaults()); 1035 update_option('cryptX', self::$cryptXOptions); 1036 } 1037 } 1038 1039 /** 1040 * Encodes a string by replacing special characters with their corresponding HTML entities. 1041 * 1042 * @param string|null $str The string to be encoded. 1043 * 1044 * @return string The encoded string, or an array of encoded strings if an array was passed. 1045 */ 1046 private function encodeString(?string $str): string 1047 { 1048 $str = htmlentities($str, ENT_QUOTES, 'UTF-8'); 1049 $special = array( 1050 '[' => '[', 1051 ']' => ']', 1052 ); 1053 1054 return str_replace(array_keys($special), array_values($special), $str); 1055 } 1056 1057 /** 1058 * Decodes a string that has been HTML entity encoded. 1059 * 1060 * @param string|null $str The string to decode. If null, an empty string is returned. 1061 * 1062 * @return string The decoded string. 1063 */ 1064 private function decodeString(?string $str): string 1065 { 1066 return html_entity_decode($str, ENT_QUOTES, 'UTF-8'); 1067 } 1068 1069 public function convertArrayToArgumentString(array $args = []): string 1070 { 1071 $string = ""; 1072 if (!empty($args)) { 1073 foreach ($args as $key => $value) { 1074 $string .= sprintf(" %s=\"%s\"", $key, $this->encodeString($value)); 1075 } 1076 $string .= " encoded=\"true\""; 1077 } 1078 1079 return $string; 1080 } 1081 1082 /** 1083 * Check if current request is for an RSS feed 1084 * 1085 * @return bool True if current request is for an RSS feed, false otherwise 1086 */ 1087 private function isRssFeed(): bool 1088 { 1089 return is_feed(); 1090 } 1091 897 1092 } -
cryptx/trunk/cryptx.php
r3103653 r3323475 4 4 * Plugin URI: http://weber-nrw.de/wordpress/cryptx/ 5 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. 4.5.37 * Requires at least: 6. 06 * Version: 3.5.0 7 * Requires at least: 6.7 8 8 * Author: Ralf Weber 9 9 * Author URI: http://weber-nrw.de/ … … 28 28 define( 'CRYPTX_FILENAME', str_replace( CRYPTX_BASEFOLDER . '/', '', plugin_basename( __FILE__ ) ) ); 29 29 30 require_once( CRYPTX_DIR_PATH . 'classes/CryptX.php' ); 31 require_once( CRYPTX_DIR_PATH . 'include/admin_option_page.php' ); 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); 32 36 33 $CryptX_instance = Cryptx\CryptX::getInstance(); 37 // Construct the full file path 38 $file = CRYPTX_DIR_PATH . 'classes' . DIRECTORY_SEPARATOR . $file_path . '.php'; 39 40 if (file_exists($file)) { 41 require_once $file; 42 } 43 } 44 }); 45 46 47 //require_once( CRYPTX_DIR_PATH . 'classes/CryptX.php' ); 48 //require_once( CRYPTX_DIR_PATH . 'include/admin_option_page.php' ); 49 50 $CryptX_instance = CryptX\CryptX::get_instance(); 34 51 $CryptX_instance->startCryptX(); 35 52 … … 43 60 */ 44 61 function encryptx( ?string $content, ?array $args = [] ): string { 45 $CryptX_instance = Cryptx\CryptX::get Instance();62 $CryptX_instance = Cryptx\CryptX::get_instance(); 46 63 return do_shortcode( '[cryptx'. $CryptX_instance->convertArrayToArgumentString( $args ).']' . $content . '[/cryptx]' ); 47 64 } -
cryptx/trunk/js/cryptx.js
r3102439 r3323475 37 37 location.href=DeCryptString( encryptedUrl ); 38 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. 47 */ 48 function generateHashFromString(inputString) { 49 // Replace & with itself (this line seems redundant in the original PHP code) 50 inputString = inputString.replace("&", "&"); 51 let crypt = ''; 52 53 // ASCII values blacklist (taken from the PHP constant) 54 const ASCII_VALUES_BLACKLIST = ['32', '34', '39', '60', '62', '63', '92', '94', '96', '127']; 55 56 for (let i = 0; i < inputString.length; i++) { 57 let salt, asciiValue; 58 do { 59 // Generate random number between 0 and 3 60 salt = Math.floor(Math.random() * 4); 61 // Get ASCII value and add salt 62 asciiValue = inputString.charCodeAt(i) + salt; 63 64 // Check if value exceeds limit 65 if (8364 <= asciiValue) { 66 asciiValue = 128; 67 } 68 } while (ASCII_VALUES_BLACKLIST.includes(asciiValue.toString())); 69 70 // Append salt and character to result 71 crypt += salt + String.fromCharCode(asciiValue); 72 } 73 74 return crypt; 75 } 76 77 /** 78 * Generates a DeCryptX handler URL with a hashed email address. 79 * 80 * @param {string} emailAddress - The email address to be hashed and included in the handler URL. 81 * @return {string} A string representing the JavaScript DeCryptX handler with the hashed email address. 82 */ 83 function generateDeCryptXHandler(emailAddress) { 84 return `javascript:DeCryptX('${generateHashFromString(emailAddress)}')`; 85 } -
cryptx/trunk/js/cryptx.min.js
r1111020 r3323475 1 function DeCryptString(r){for(var t=0,n="mailto:",o=0,e=0;e<r.length/2;e++)o=r.substr(2*e,1),t=r.charCodeAt(2*e+1),t>=8364&&(t=128),n+=String.fromCharCode(t-o);return n}function DeCryptX(r){location.href=DeCryptString(r)}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)}')`} -
cryptx/trunk/readme.txt
r3321843 r3323475 5 5 Requires at least: 6.0 6 6 Tested up to: 6.8 7 Stable tag: 3. 4.5.38 Requires PHP: 7.47 Stable tag: 3.5.0 8 Requires PHP: 8.0 9 9 License: GPLv2 or later 10 10 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 24 24 25 25 == Changelog == 26 = 3.5.0 = 27 * Parts of the code have been rewritten to make the plugin more maintainable. 28 * fixed some bugs 29 * added option to disable CryptX on RSS feeds (requested: https://wordpress.org/support/topic/cryptx-should-be-disabled-for-rss-content/) 30 * Added new Javascript function to add CryptX mailto links via javascript on client side (requested: https://wordpress.org/support/topic/javascript-function-to-encrypt-emails/) 26 31 = 3.4.5.3 = 27 32 * fixed a Critical error in combination with WPML
Note: See TracChangeset
for help on using the changeset viewer.