PluginProbe
Sharing Image / 3.7
Sharing Image v3.7
3.10 trunk 2.0 2.0.0 2.0.1 2.0.10 2.0.11 2.0.12 2.0.13 2.0.14 2.0.15 2.0.16 2.0.17 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 3.0 3.1 3.2 3.3 All 29 releases
← All changes | classes/class-generator.php +6 -440 trunk3.7 View file →
@@ -34,13 +34,13 @@
34 34
35 35 // Count intersected values.
36 36 $prepared = count( self::prepare_args( $template, $required ) );
37 37
38 - if ( count( $required ) !== $prepared ) {
39 - return false;
38 + if ( count( $required ) === $prepared ) {
39 + return true;
40 40 }
41 41
42 - return ! is_wp_error( self::validate_template( $template ) );
42 + return false;
43 43 }
44 44
45 45 /**
46 46 * Prepare template before creating poster.
@@ -104,14 +104,8 @@
104 104 * @return void|WP_Error WP_Error on failure.
105 105 */
106 106 public static function create_poster( $template, $path = null ) {
107 107 try {
108 - $validation = self::validate_template( $template );
109 -
110 - if ( is_wp_error( $validation ) ) {
111 - return $validation;
112 - }
113 -
114 108 $poster = new PosterEditor();
115 109
116 110 // Set color canvas options.
117 111 $options = array(
@@ -162,99 +156,8 @@
162 156 return apply_filters( 'sharing_image_get_upload_file', $file, $name );
163 157 }
164 158
165 159 /**
166 - * Get generator safety limit.
167 - *
168 - * @param string $name Limit name.
169 - *
170 - * @return int Limit value.
171 - */
172 - public static function get_limit( $name ) {
173 - $limits = array(
174 - 'max_width' => 4096,
175 - 'max_height' => 4096,
176 - 'max_pixels' => 12000000,
177 - 'max_layers' => 100,
178 - 'max_text' => 5000,
179 - 'max_fontsize' => 512,
180 - 'max_dimension' => 4096,
181 - );
182 -
183 - /**
184 - * Filters generator safety limits.
185 - *
186 - * @param array $limits List of generator limits.
187 - */
188 - $limits = apply_filters( 'sharing_image_generator_limits', $limits );
189 -
190 - if ( ! isset( $limits[ $name ] ) ) {
191 - return 0;
192 - }
193 -
194 - return absint( $limits[ $name ] );
195 - }
196 -
197 - /**
198 - * Limit text length before storing or rendering.
199 - *
200 - * @param mixed $text Text value.
201 - *
202 - * @return mixed Limited text value.
203 - */
204 - public static function limit_text( $text ) {
205 - if ( ! is_string( $text ) ) {
206 - return $text;
207 - }
208 -
209 - $max = self::get_limit( 'max_text' );
210 -
211 - if ( ! $max ) {
212 - return $text;
213 - }
214 -
215 - if ( function_exists( 'mb_strlen' ) && function_exists( 'mb_substr' ) ) {
216 - if ( mb_strlen( $text, 'UTF-8' ) > $max ) {
217 - return mb_substr( $text, 0, $max, 'UTF-8' );
218 - }
219 -
220 - return $text;
221 - }
222 -
223 - if ( strlen( $text ) > $max ) {
224 - return substr( $text, 0, $max );
225 - }
226 -
227 - return $text;
228 - }
229 -
230 - /**
231 - * Normalize template dimensions to configured safety limits.
232 - *
233 - * @param int $width Template width.
234 - * @param int $height Template height.
235 - *
236 - * @return array Normalized width and height.
237 - */
238 - public static function normalize_dimensions( $width, $height ) {
239 - $width = max( 1, min( absint( $width ), self::get_limit( 'max_width' ) ) );
240 - $height = max( 1, min( absint( $height ), self::get_limit( 'max_height' ) ) );
241 -
242 - $max_pixels = self::get_limit( 'max_pixels' );
243 -
244 - if ( $max_pixels && $width * $height > $max_pixels ) {
245 - $ratio = sqrt( $max_pixels / ( $width * $height ) );
246 - $width = max( 1, (int) floor( $width * $ratio ) );
247 - $height = max( 1, (int) floor( $height * $ratio ) );
248 - }
249 -
250 - return array(
251 - 'width' => $width,
252 - 'height' => $height,
253 - );
254 - }
255 -
256 - /**
257 160 * Append layers to image.
258 161 *
259 162 * @param PosterEditor $poster Instance of PosterEditor class.
260 163 * @param array $layers List of layers options.
@@ -333,21 +236,18 @@
333 236 if ( isset( $fieldset[ $key ] ) ) {
334 237 $layer['content'] = $fieldset[ $key ];
335 238 }
336 239
337 - $layer['content'] = self::normalize_text( $layer['content'] );
338 - $layer['content'] = self::limit_text( $layer['content'] );
339 -
340 240 if ( isset( $layer['textconvert'] ) && 'default' !== $layer['textconvert'] ) {
341 241 if ( 'uppercase' === $layer['textconvert'] ) {
342 242 if ( function_exists( 'mb_strtoupper' ) ) {
343 - $layer['content'] = mb_strtoupper( $layer['content'], 'UTF-8' );
243 + $layer['content'] = mb_strtoupper( $layer['content'] );
344 244 } else {
345 245 $layer['content'] = strtoupper( $layer['content'] );
346 246 }
347 247 } elseif ( 'lowercase' === $layer['textconvert'] ) {
348 248 if ( function_exists( 'mb_strtolower' ) ) {
349 - $layer['content'] = mb_strtolower( $layer['content'], 'UTF-8' );
249 + $layer['content'] = mb_strtolower( $layer['content'] );
350 250 } else {
351 251 $layer['content'] = strtolower( $layer['content'] );
352 252 }
353 253 }
@@ -510,254 +410,8 @@
510 410 return $poster;
511 411 }
512 412
513 413 /**
514 - * Normalize text before rendering it into GD.
515 - *
516 - * WordPress stores strings as UTF-8, but older saved meta or imported
517 - * templates can contain HTML entities or mojibake markers instead of
518 - * the original characters.
519 - *
520 - * @param mixed $text Text layer content.
521 - *
522 - * @return mixed Normalized text.
523 - */
524 - public static function normalize_text( $text ) {
525 - if ( ! is_string( $text ) ) {
526 - return $text;
527 - }
528 -
529 - $text = html_entity_decode( $text, ENT_QUOTES | ENT_HTML5, 'UTF-8' );
530 -
531 - if ( function_exists( 'wp_check_invalid_utf8' ) ) {
532 - $text = wp_check_invalid_utf8( $text, true );
533 - }
534 -
535 - /**
536 - * Whether to attempt heuristic mojibake repair.
537 - *
538 - * Detects common UTF-8 sequences that were once decoded as
539 - * Windows-1252 (e.g. "Résumé" → "Résumé"). Disable this filter
540 - * if it produces false positives on legitimate accented text.
541 - *
542 - * @param bool $enabled Whether repair is enabled. Default true.
543 - * @param string $text Text being normalized.
544 - */
545 - if ( apply_filters( 'sharing_image_repair_mojibake', true, $text ) ) {
546 - $text = self::repair_mojibake( $text );
547 - }
548 -
549 - return $text;
550 - }
551 -
552 - /**
553 - * Repair UTF-8 text that was previously decoded as Windows-1252/ISO-8859-1.
554 - *
555 - * @param string $text Text layer content.
556 - *
557 - * @return string Repaired text when mojibake is detected.
558 - */
559 - private static function repair_mojibake( $text ) {
560 - for ( $attempt = 0; $attempt < 2; $attempt++ ) {
561 - $score = self::mojibake_score( $text );
562 -
563 - if ( 0 === $score ) {
564 - return $text;
565 - }
566 -
567 - $fixed = self::convert_mojibake_candidate( $text );
568 -
569 - if ( ! is_string( $fixed ) || $fixed === $text || self::mojibake_score( $fixed ) >= $score ) {
570 - $fixed = self::repair_mojibake_segments( $text );
571 - }
572 -
573 - if ( ! is_string( $fixed ) || $fixed === $text || self::mojibake_score( $fixed ) >= $score ) {
574 - return $text;
575 - }
576 -
577 - $text = $fixed;
578 - }
579 -
580 - return $text;
581 - }
582 -
583 - /**
584 - * Count common mojibake markers without matching normal accented text.
585 - *
586 - * @param string $text Text layer content.
587 - *
588 - * @return int Mojibake marker count.
589 - */
590 - private static function mojibake_score( $text ) {
591 - $score = 0;
592 - $markers = array(
593 - "\xc3\x83",
594 - "\xc3\x82",
595 - "\xc3\xa2",
596 - "\xc3\x90",
597 - "\xc3\x91",
598 - );
599 -
600 - foreach ( $markers as $marker ) {
601 - $score += substr_count( $text, $marker );
602 - }
603 -
604 - return $score;
605 - }
606 -
607 - /**
608 - * Repair mojibake one whitespace-delimited segment at a time.
609 - *
610 - * @param string $text Text layer content.
611 - *
612 - * @return string Repaired text when a segment can be converted safely.
613 - */
614 - private static function repair_mojibake_segments( $text ) {
615 - $segments = preg_split( '/(\s+)/u', $text, -1, PREG_SPLIT_DELIM_CAPTURE );
616 -
617 - if ( ! is_array( $segments ) ) {
618 - return $text;
619 - }
620 -
621 - foreach ( $segments as &$segment ) {
622 - $score = self::mojibake_score( $segment );
623 -
624 - if ( 0 === $score ) {
625 - continue;
626 - }
627 -
628 - $fixed = self::convert_mojibake_candidate( $segment );
629 -
630 - if ( is_string( $fixed ) && $fixed !== $segment && self::mojibake_score( $fixed ) < $score ) {
631 - $segment = $fixed;
632 - }
633 - }
634 - unset( $segment );
635 -
636 - return implode( '', $segments );
637 - }
638 -
639 - /**
640 - * Convert a mojibake-looking UTF-8 string back through Windows-1252 bytes.
641 - *
642 - * @param string $text Text layer content.
643 - *
644 - * @return string|false Converted candidate.
645 - */
646 - private static function convert_mojibake_candidate( $text ) {
647 - $encodings = array( 'Windows-1252', 'ISO-8859-1' );
648 - $best = false;
649 -
650 - foreach ( $encodings as $encoding ) {
651 - $fixed = self::convert_to_legacy_bytes( $text, $encoding );
652 -
653 - if ( ! is_string( $fixed ) || '' === $fixed ) {
654 - continue;
655 - }
656 -
657 - if ( ! self::is_valid_utf8( $fixed ) ) {
658 - continue;
659 - }
660 -
661 - if ( false === $best || self::mojibake_score( $fixed ) < self::mojibake_score( $best ) ) {
662 - $best = $fixed;
663 - }
664 - }
665 -
666 - return $best;
667 - }
668 -
669 - /**
670 - * Convert UTF-8 text to legacy bytes only when the conversion is lossless.
671 - *
672 - * @param string $text Text layer content.
673 - * @param string $encoding Legacy encoding name.
674 - *
675 - * @return string|false Converted bytes.
676 - */
677 - private static function convert_to_legacy_bytes( $text, $encoding ) {
678 - $source = $text;
679 -
680 - if ( 'ISO-8859-1' === $encoding ) {
681 - $source = self::normalize_windows_1252_symbols( $text );
682 - }
683 -
684 - if ( function_exists( 'iconv' ) ) {
685 - // Suppress notices: we only care about the return value, errors are expected on partial sequences.
686 - $fixed = @iconv( 'UTF-8', $encoding, $source ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
687 -
688 - if ( is_string( $fixed ) && @iconv( $encoding, 'UTF-8', $fixed ) === $source ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
689 - return $fixed;
690 - }
691 - } elseif ( function_exists( 'mb_convert_encoding' ) ) {
692 - $fixed = @mb_convert_encoding( $source, $encoding, 'UTF-8' ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
693 -
694 - if ( is_string( $fixed ) && @mb_convert_encoding( $fixed, 'UTF-8', $encoding ) === $source ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
695 - return $fixed;
696 - }
697 - }
698 -
699 - return false;
700 - }
701 -
702 - /**
703 - * Normalize Windows-1252 printable symbols to ISO-8859-1 C1 controls.
704 - *
705 - * Some mojibake strings mix Windows-1252 symbols like "Å“" with raw C1
706 - * controls. Mapping those symbols back to controls lets ISO-8859-1 recover
707 - * the original UTF-8 byte stream.
708 - *
709 - * @param string $text Text to normalize.
710 - *
711 - * @return string Normalized text.
712 - */
713 - private static function normalize_windows_1252_symbols( $text ) {
714 - $bytes = array( 0x80, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8E, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9E, 0x9F );
715 -
716 - foreach ( $bytes as $byte ) {
717 - $symbol = @iconv( 'Windows-1252', 'UTF-8', chr( $byte ) ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
718 -
719 - if ( ! is_string( $symbol ) || '' === $symbol ) {
720 - continue;
721 - }
722 -
723 - $text = str_replace( $symbol, self::utf8_chr( $byte ), $text );
724 - }
725 -
726 - return $text;
727 - }
728 -
729 - /**
730 - * Convert a Unicode code point below U+0800 to UTF-8.
731 - *
732 - * @param int $code_point Unicode code point.
733 - *
734 - * @return string UTF-8 character.
735 - */
736 - private static function utf8_chr( $code_point ) {
737 - if ( $code_point < 0x80 ) {
738 - return chr( $code_point );
739 - }
740 -
741 - return chr( 0xC0 | ( $code_point >> 6 ) ) . chr( 0x80 | ( $code_point & 0x3F ) );
742 - }
743 -
744 - /**
745 - * Check whether a string contains valid UTF-8 bytes.
746 - *
747 - * @param string $text Text to check.
748 - *
749 - * @return bool Whether text is valid UTF-8.
750 - */
751 - private static function is_valid_utf8( $text ) {
752 - if ( function_exists( 'mb_check_encoding' ) ) {
753 - return mb_check_encoding( $text, 'UTF-8' );
754 - }
755 -
756 - return (bool) preg_match( '//u', $text );
757 - }
758 -
759 - /**
760 414 * Set empty boundary list to reset boundary for empty layers.
761 415 *
762 416 * @since 3.0
763 417 *
@@ -837,13 +491,9 @@
837 491 * @return string Filtered path to font file.
838 492 */
839 493 private static function get_fontpath( $layer, $path = '' ) {
840 494 if ( isset( $layer['fontname'] ) ) {
841 - $fontname = sanitize_key( $layer['fontname'] );
842 -
843 - if ( $fontname ) {
844 - $path = sprintf( SHARING_IMAGE_DIR . 'fonts/%s.ttf', $fontname );
845 - }
495 + $path = sprintf( SHARING_IMAGE_DIR . 'fonts/%s.ttf', $layer['fontname'] );
846 496 }
847 497
848 498 if ( isset( $layer['fontfile'] ) ) {
849 499 $path = get_attached_file( $layer['fontfile'] );
@@ -939,90 +589,6 @@
939 589 * @return array List of prepared args.
940 590 */
941 591 private static function prepare_args( $args, $allowed ) {
942 592 return wp_array_slice_assoc( $args, $allowed );
943 - }
944 -
945 - /**
946 - * Validate template before allocating GD resources.
947 - *
948 - * @param array $template Template settings.
949 - *
950 - * @return true|WP_Error True when valid, WP_Error otherwise.
951 - */
952 - private static function validate_template( $template ) {
953 - if ( empty( $template['width'] ) || empty( $template['height'] ) ) {
954 - return new WP_Error( 'generate', esc_html__( 'Incorrect template settings.', 'sharing-image' ) );
955 - }
956 -
957 - $width = absint( $template['width'] );
958 - $height = absint( $template['height'] );
959 -
960 - if ( $width < 1 || $height < 1 ) {
961 - return new WP_Error( 'generate', esc_html__( 'Incorrect template dimensions.', 'sharing-image' ) );
962 - }
963 -
964 - if ( $width > self::get_limit( 'max_width' ) || $height > self::get_limit( 'max_height' ) ) {
965 - return new WP_Error( 'generate', esc_html__( 'Template dimensions are too large.', 'sharing-image' ) );
966 - }
967 -
968 - if ( $width * $height > self::get_limit( 'max_pixels' ) ) {
969 - return new WP_Error( 'generate', esc_html__( 'Template canvas is too large.', 'sharing-image' ) );
970 - }
971 -
972 - if ( ! empty( $template['layers'] ) && is_array( $template['layers'] ) ) {
973 - if ( count( $template['layers'] ) > self::get_limit( 'max_layers' ) ) {
974 - return new WP_Error( 'generate', esc_html__( 'Template contains too many layers.', 'sharing-image' ) );
975 - }
976 -
977 - foreach ( $template['layers'] as $layer ) {
978 - $validation = self::validate_layer( $layer );
979 -
980 - if ( is_wp_error( $validation ) ) {
981 - return $validation;
982 - }
983 - }
984 - }
985 -
986 - return true;
987 - }
988 -
989 - /**
990 - * Validate layer settings before rendering.
991 - *
992 - * @param array $layer Layer settings.
993 - *
994 - * @return true|WP_Error True when valid, WP_Error otherwise.
995 - */
996 - private static function validate_layer( $layer ) {
997 - if ( isset( $layer['fontsize'] ) && absint( $layer['fontsize'] ) > self::get_limit( 'max_fontsize' ) ) {
998 - return new WP_Error( 'generate', esc_html__( 'Layer font size is too large.', 'sharing-image' ) );
999 - }
1000 -
1001 - foreach ( array( 'width', 'height' ) as $dimension ) {
1002 - if ( ! isset( $layer[ $dimension ] ) ) {
1003 - continue;
1004 - }
1005 -
1006 - $value = intval( $layer[ $dimension ] );
1007 -
1008 - if ( $value > self::get_limit( 'max_dimension' ) ) {
1009 - return new WP_Error( 'generate', esc_html__( 'Layer dimensions are too large.', 'sharing-image' ) );
1010 - }
1011 - }
1012 -
1013 - if ( isset( $layer['content'] ) && is_string( $layer['content'] ) ) {
1014 - $text = $layer['content'];
1015 - $max = self::get_limit( 'max_text' );
1016 -
1017 - if ( $max && function_exists( 'mb_strlen' ) && mb_strlen( $text, 'UTF-8' ) > $max ) {
1018 - return new WP_Error( 'generate', esc_html__( 'Layer text is too long.', 'sharing-image' ) );
1019 - }
1020 -
1021 - if ( $max && ! function_exists( 'mb_strlen' ) && strlen( $text ) > $max ) {
1022 - return new WP_Error( 'generate', esc_html__( 'Layer text is too long.', 'sharing-image' ) );
1023 - }
1024 - }
1025 -
1026 - return true;
1027 593 }
1028 594 }