parser = $csstidy; $this->css = &$csstidy->css; $this->sub_value = &$csstidy->sub_value; $this->at = &$csstidy->at; $this->selector = &$csstidy->selector; $this->property = &$csstidy->property; $this->value = &$csstidy->value; } /** * Optimises $css after parsing. * * @since 1.0.0 */ public function postparse(): void { if ( $this->parser->get_cfg( 'preserve_css' ) ) { return; } if ( 2 === (int) $this->parser->get_cfg( 'merge_selectors' ) ) { foreach ( $this->css as $medium => $value ) { if ( is_array( $value ) ) { $this->merge_selectors( $this->css[ $medium ] ); } } } if ( $this->parser->get_cfg( 'discard_invalid_selectors' ) ) { foreach ( $this->css as $medium => $value ) { if ( is_array( $value ) ) { $this->discard_invalid_selectors( $this->css[ $medium ] ); } } } if ( $this->parser->get_cfg( 'optimise_shorthands' ) > 0 ) { foreach ( $this->css as $medium => $value ) { if ( is_array( $value ) ) { foreach ( $value as $selector => $value1 ) { $this->css[ $medium ][ $selector ] = $this->merge_4value_shorthands( $this->css[ $medium ][ $selector ] ); if ( $this->parser->get_cfg( 'optimise_shorthands' ) < 2 ) { continue; } $this->css[ $medium ][ $selector ] = $this->merge_font( $this->css[ $medium ][ $selector ] ); if ( $this->parser->get_cfg( 'optimise_shorthands' ) < 3 ) { continue; } $this->css[ $medium ][ $selector ] = $this->merge_bg( $this->css[ $medium ][ $selector ] ); if ( empty( $this->css[ $medium ][ $selector ] ) ) { unset( $this->css[ $medium ][ $selector ] ); } } } } } } /** * Optimises values * * @since 1.0.0 */ public function value(): void { $shorthands = &$this->parser->data['csstidy']['shorthands']; // Optimise shorthand properties. if ( isset( $shorthands[ $this->property ] ) && $this->parser->get_cfg( 'optimise_shorthands' ) > 0 ) { $temp = $this->shorthand( $this->value ); // FIXME - move. if ( $temp !== $this->value ) { $this->parser->log( 'Optimised shorthand notation (' . $this->property . '): Changed "' . $this->value . '" to "' . $temp . '"', 'Information' ); } $this->value = $temp; } // Remove whitespace at !important. if ( $this->value !== $this->compress_important( $this->value ) ) { $this->parser->log( 'Optimised !important', 'Information' ); } } /** * Optimises shorthands. * * @since 1.0.0 */ public function shorthands(): void { $shorthands = &$this->parser->data['csstidy']['shorthands']; if ( ! $this->parser->get_cfg( 'optimise_shorthands' ) || $this->parser->get_cfg( 'preserve_css' ) ) { return; } if ( 'font' === $this->property && $this->parser->get_cfg( 'optimise_shorthands' ) > 1 ) { $this->css[ $this->at ][ $this->selector ]['font'] = ''; $this->parser->merge_css_blocks( $this->at, $this->selector, $this->dissolve_short_font( $this->value ) ); } if ( 'background' === $this->property && $this->parser->get_cfg( 'optimise_shorthands' ) > 2 ) { $this->css[ $this->at ][ $this->selector ]['background'] = ''; $this->parser->merge_css_blocks( $this->at, $this->selector, $this->dissolve_short_bg( $this->value ) ); } if ( isset( $shorthands[ $this->property ] ) ) { $this->parser->merge_css_blocks( $this->at, $this->selector, $this->dissolve_4value_shorthands( $this->property, $this->value ) ); if ( is_array( $shorthands[ $this->property ] ) ) { $this->css[ $this->at ][ $this->selector ][ $this->property ] = ''; } } } /** * Optimises a sub-value. * * @since 1.0.0 */ public function subvalue(): void { $replace_colors = &$this->parser->data['csstidy']['replace_colors']; $this->sub_value = trim( $this->sub_value ); if ( '' === $this->sub_value ) { // caution : '0'. return; } $important = ''; if ( $this->parser->is_important( $this->sub_value ) ) { $important = ' !important'; } $this->sub_value = $this->parser->gvw_important( $this->sub_value ); // Compress font-weight. if ( 'font-weight' === $this->property && $this->parser->get_cfg( 'compress_font-weight' ) ) { if ( 'bold' === $this->sub_value ) { $this->sub_value = '700'; $this->parser->log( 'Optimised font-weight: Changed "bold" to "700"', 'Information' ); } elseif ( 'normal' === $this->sub_value ) { $this->sub_value = '400'; $this->parser->log( 'Optimised font-weight: Changed "normal" to "400"', 'Information' ); } } $temp = $this->compress_numbers( $this->sub_value ); if ( 0 !== strcasecmp( $temp, $this->sub_value ) ) { if ( strlen( $temp ) > strlen( $this->sub_value ) ) { $this->parser->log( 'Fixed invalid number: Changed "' . $this->sub_value . '" to "' . $temp . '"', 'Warning' ); } else { $this->parser->log( 'Optimised number: Changed "' . $this->sub_value . '" to "' . $temp . '"', 'Information' ); } $this->sub_value = $temp; } if ( $this->parser->get_cfg( 'compress_colors' ) ) { $temp = $this->cut_color( $this->sub_value ); if ( $temp !== $this->sub_value ) { if ( isset( $replace_colors[ $this->sub_value ] ) ) { $this->parser->log( 'Fixed invalid color name: Changed "' . $this->sub_value . '" to "' . $temp . '"', 'Warning' ); } else { $this->parser->log( 'Optimised color: Changed "' . $this->sub_value . '" to "' . $temp . '"', 'Information' ); } $this->sub_value = $temp; } } $this->sub_value .= $important; } /** * Compresses shorthand values. * * Example: `margin: 1px 1px 1px 1px` will become `margin: 1px`. * * @since 1.0.0 * * @param string $value Shorthand value. * @return string Compressed value. */ public function shorthand( string $value ): string { $important = ''; if ( $this->parser->is_important( $value ) ) { $values = $this->parser->gvw_important( $value ); $important = ' !important'; } else { $values = $value; } $values = explode( ' ', $values ); switch ( count( $values ) ) { case 4: if ( $values[0] === $values[1] && $values[0] === $values[2] && $values[0] === $values[3] ) { return $values[0] . $important; } elseif ( $values[1] === $values[3] && $values[0] === $values[2] ) { return $values[0] . ' ' . $values[1] . $important; } elseif ( $values[1] === $values[3] ) { return $values[0] . ' ' . $values[1] . ' ' . $values[2] . $important; } break; case 3: if ( $values[0] === $values[1] && $values[0] === $values[2] ) { return $values[0] . $important; } elseif ( $values[0] === $values[2] ) { return $values[0] . ' ' . $values[1] . $important; } break; case 2: if ( $values[0] === $values[1] ) { return $values[0] . $important; } break; } return $value; } /** * Removes unnecessary whitespace in ! important. * * @since 1.0.0 * * @param string $a_string String. * @return string Cleaned string. */ public function compress_important( string &$a_string ): string { if ( $this->parser->is_important( $a_string ) ) { $a_string = $this->parser->gvw_important( $a_string ) . ' !important'; } return $a_string; } /** * Color compression function. Converts all rgb() values to #-values and uses the short-form if possible. Also replaces 4 color names by #-values. * * @since 1.0.0 * * @param string $color Color value. * @return string Compressed color. */ public function cut_color( string $color ): string { $replace_colors = &$this->parser->data['csstidy']['replace_colors']; // If it's a string, don't touch it! if ( str_starts_with( $color, "'" ) || str_starts_with( $color, '"' ) ) { return $color; } // Complex gradient expressions. if ( str_contains( $color, '(' ) && 0 !== strncasecmp( $color, 'rgb(', 4 ) && 0 !== strncasecmp( $color, 'rgba(', 5 ) ) { // Don't touch properties within MSIE filters, those are too sensitive. if ( false !== stripos( $color, 'progid:' ) ) { return $color; } preg_match_all( ',rgba?\([^)]+\),i', $color, $matches, PREG_SET_ORDER ); if ( count( $matches ) ) { foreach ( $matches as $m ) { $color = str_replace( $m[0], $this->cut_color( $m[0] ), $color ); } } preg_match_all( ',#[0-9a-f]{6}(?=[^0-9a-f]),i', $color, $matches, PREG_SET_ORDER ); if ( count( $matches ) ) { foreach ( $matches as $m ) { $color = str_replace( $m[0], $this->cut_color( $m[0] ), $color ); } } return $color; } // rgb(0,0,0) -> #000000 (or #000 in this case later). if ( // Be sure to not corrupt a rgb with calc() value. ( 0 === strncasecmp( $color, 'rgb(', 4 ) && false === strpos( $color, '(', 4 ) ) || ( 0 === strncasecmp( $color, 'rgba(', 5 ) && false === strpos( $color, '(', 5 ) ) ) { $color_tmp = explode( '(', $color, 2 ); $color_tmp = rtrim( end( $color_tmp ), ')' ); if ( str_contains( $color_tmp, '/' ) ) { $color_tmp = explode( '/', $color_tmp, 2 ); $color_parts = explode( ' ', trim( reset( $color_tmp ) ), 3 ); while ( count( $color_parts ) < 3 ) { // phpcs:ignore Squiz.PHP.DisallowSizeFunctionsInLoops.Found $color_parts[] = 0; } $color_parts[] = end( $color_tmp ); } else { $color_parts = explode( ',', $color_tmp, 4 ); } $color_parts_count = count( $color_parts ); for ( $i = 0; $i < $color_parts_count; $i++ ) { $color_parts[ $i ] = trim( $color_parts[ $i ] ); if ( str_ends_with( $color_parts[ $i ], '%' ) ) { $color_parts[ $i ] = round( ( 255 * intval( $color_parts[ $i ] ) ) / 100 ); } elseif ( $i > 2 ) { // 4th argument is alpha layer between 0 and 1 (if not %). $color_parts[ $i ] = round( 255 * floatval( $color_parts[ $i ] ) ); } $color_parts[ $i ] = intval( $color_parts[ $i ] ); if ( $color_parts[ $i ] > 255 ) { $color_parts[ $i ] = 255; } } $color = '#'; // 3 or 4 parts depending on alpha layer. $nb = min( max( count( $color_parts ), 3 ), 4 ); for ( $i = 0; $i < $nb; $i++ ) { if ( ! isset( $color_parts[ $i ] ) ) { $color_parts[ $i ] = 0; } if ( $color_parts[ $i ] < 16 ) { $color .= '0' . dechex( $color_parts[ $i ] ); } else { $color .= dechex( $color_parts[ $i ] ); } } } // Fix bad color names. if ( isset( $replace_colors[ strtolower( $color ) ] ) ) { $color = $replace_colors[ strtolower( $color ) ]; } if ( 7 === strlen( $color ) ) { // #aabbcc -> #abc $color_temp = strtolower( $color ); if ( '#' === $color_temp[0] && $color_temp[1] === $color_temp[2] && $color_temp[3] === $color_temp[4] && $color_temp[5] === $color_temp[6] ) { $color = '#' . $color[1] . $color[3] . $color[5]; } } elseif ( 9 === strlen( $color ) ) { // #aabbccdd -> #abcd $color_temp = strtolower( $color ); if ( '#' === $color_temp[0] && $color_temp[1] === $color_temp[2] && $color_temp[3] === $color_temp[4] && $color_temp[5] === $color_temp[6] && $color_temp[7] === $color_temp[8] ) { $color = '#' . $color[1] . $color[3] . $color[5] . $color[7]; } } switch ( strtolower( $color ) ) { /* color name -> hex code */ case 'black': return '#000'; case 'fuchsia': return '#f0f'; case 'white': return '#fff'; case 'yellow': return '#ff0'; /* hex code -> color name */ case '#800000': return 'maroon'; case '#ffa500': return 'orange'; case '#808000': return 'olive'; case '#800080': return 'purple'; case '#008000': return 'green'; case '#000080': return 'navy'; case '#008080': return 'teal'; case '#c0c0c0': return 'silver'; case '#808080': return 'gray'; case '#f00': return 'red'; } return $color; } /** * Compresses numbers (ie. 1.0 becomes 1 or 1.100 becomes 1.1). * * @since 1.0.0 * * @param string $subvalue Value. * @return string Compressed value. */ public function compress_numbers( string $subvalue ): string { $unit_values = &$this->parser->data['csstidy']['unit_values']; $color_values = &$this->parser->data['csstidy']['color_values']; // for font:1em/1em sans-serif...;. if ( 'font' === $this->property ) { $temp = explode( '/', $subvalue ); } else { $temp = array( $subvalue ); } $temp_count = count( $temp ); for ( $l = 0; $l < $temp_count; $l++ ) { // If we are not dealing with a number at this point, do not optimize anything. $number = $this->analyse_css_number( $temp[ $l ] ); if ( false === $number ) { return $subvalue; } // Fix bad colors. if ( in_array( $this->property, $color_values, true ) ) { if ( 3 === strlen( $temp[ $l ] ) || 6 === strlen( $temp[ $l ] ) ) { $temp[ $l ] = '#' . $temp[ $l ]; } else { $temp[ $l ] = '0'; } continue; } if ( abs( $number[0] ) > 0 ) { if ( '' === $number[1] && in_array( $this->property, $unit_values, true ) ) { $number[1] = 'px'; } } elseif ( 's' !== $number[1] && 'ms' !== $number[1] ) { $number[1] = ''; } $temp[ $l ] = $number[0] . $number[1]; } return ( count( $temp ) > 1 ) ? $temp[0] . '/' . $temp[1] : $temp[0]; } /** * Checks if a given string is a CSS valid number. If it is, an array containing the value and unit is returned. * * @since 1.0.0 * * @param string $a_string String. * @return array{int|string, string}|false ('unit' if unit is found or '' if no unit exists, number value) or false if no number. */ public function analyse_css_number( string $a_string ) /* : array|false */ { // Most simple checks first. if ( 0 === strlen( $a_string ) || ctype_alpha( $a_string[0] ) ) { return false; } $units = &$this->parser->data['csstidy']['units']; $return = array( 0, '' ); $return[0] = (float) $a_string; if ( abs( $return[0] ) > 0 && abs( $return[0] ) < 1 ) { if ( $return[0] < 0 ) { $return[0] = '-' . ltrim( substr( $return[0], 1 ), '0' ); } else { $return[0] = ltrim( $return[0], '0' ); } } // Look for unit and split from value if exists. foreach ( $units as $unit ) { $expect_unit_at = strlen( $a_string ) - strlen( $unit ); if ( ! ( $unit_in_string = stristr( $a_string, $unit ) ) ) { // phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.Found,Squiz.PHP.DisallowMultipleAssignments.FoundInControlStructure continue; } $actual_position = strpos( $a_string, $unit_in_string ); if ( $expect_unit_at === $actual_position ) { $return[1] = $unit; $a_string = substr( $a_string, 0, - strlen( $unit ) ); break; } } if ( ! is_numeric( $a_string ) ) { return false; } return $return; } /** * Merges selectors with same properties. Example: a{color:red} b{color:red} -> a,b{color:red} * Very basic and has at least one bug. Hopefully there is a replacement soon. * * @since 1.0.0 * * @param array $an_array List of selectors. This parameter is modified by reference. */ public function merge_selectors( array &$an_array ): void { $css = $an_array; foreach ( $css as $key => $value ) { if ( ! isset( $css[ $key ] ) ) { continue; } // Check if properties also exist in another selector. $keys = array(); // PHP bug (?) without $css = $an_array; here. foreach ( $css as $selector => $vali ) { if ( $selector === $key ) { continue; } if ( $css[ $key ] === $vali ) { $keys[] = $selector; } } if ( ! empty( $keys ) ) { $newsel = $key; unset( $css[ $key ] ); foreach ( $keys as $selector ) { unset( $css[ $selector ] ); $newsel .= ',' . $selector; } $css[ $newsel ] = $value; } } $an_array = $css; } /** * Removes invalid selectors and their corresponding rule-sets as * defined by 4.1.7 in REC-CSS2. This is a very rudimentary check * and should be replaced by a full-blown parsing algorithm or * regular expression. * * @since 1.0.0 * * @param array $an_array [description]. */ public function discard_invalid_selectors( array &$an_array ): void { foreach ( $an_array as $selector => $decls ) { $ok = true; $selectors = array_map( 'trim', explode( ',', $selector ) ); foreach ( $selectors as $s ) { $simple_selectors = preg_split( '/\s*[+>~\s]\s*/', $s ); foreach ( $simple_selectors as $ss ) { if ( '' === $ss ) { $ok = false; } // Could also check $ss for internal structure, but that probably would be too slow. } } if ( ! $ok ) { unset( $an_array[ $selector ] ); } } } /** * Dissolves properties like padding:10px 10px 10px to padding-top:10px;padding-bottom:10px;... * * @since 1.0.0 * * @param string $property [description]. * @param string $value [description]. * @return array [description] */ public function dissolve_4value_shorthands( string $property, string $value ): array { $return = array(); $shorthands = &$this->parser->data['csstidy']['shorthands']; if ( ! is_array( $shorthands[ $property ] ) ) { $return[ $property ] = $value; return $return; } $important = ''; if ( $this->parser->is_important( $value ) ) { $value = $this->parser->gvw_important( $value ); $important = ' !important'; } $values = explode( ' ', $value ); if ( 4 === count( $values ) ) { for ( $i = 0; $i < 4; $i++ ) { $return[ $shorthands[ $property ][ $i ] ] = $values[ $i ] . $important; } } elseif ( 3 === count( $values ) ) { $return[ $shorthands[ $property ][0] ] = $values[0] . $important; $return[ $shorthands[ $property ][1] ] = $values[1] . $important; $return[ $shorthands[ $property ][3] ] = $values[1] . $important; $return[ $shorthands[ $property ][2] ] = $values[2] . $important; } elseif ( 2 === count( $values ) ) { for ( $i = 0; $i < 4; $i++ ) { $return[ $shorthands[ $property ][ $i ] ] = ( 0 !== $i % 2 ) ? $values[1] . $important : $values[0] . $important; } } else { for ( $i = 0; $i < 4; $i++ ) { $return[ $shorthands[ $property ][ $i ] ] = $values[0] . $important; } } return $return; } /** * Explodes a string as explode() does, however, not if $sep is escaped or within a string. * * @since 1.0.0 * * @param string $sep Separator. * @param string $a_string String. * @return array [description] */ public function explode_ws( string $sep, string $a_string ): array { $status = 'st'; $to = ''; $output = array(); $num = 0; for ( $i = 0, $len = strlen( $a_string ); $i < $len; $i++ ) { switch ( $status ) { case 'st': if ( $a_string[ $i ] === $sep && ! $this->parser->escaped( $a_string, $i ) ) { ++$num; } elseif ( '"' === $a_string[ $i ] || "'" === $a_string[ $i ] || '(' === $a_string[ $i ] && ! $this->parser->escaped( $a_string, $i ) ) { $status = 'str'; $to = ( '(' === $a_string[ $i ] ) ? ')' : $a_string[ $i ]; ( isset( $output[ $num ] ) ) ? $output[ $num ] .= $a_string[ $i ] : $output[ $num ] = $a_string[ $i ]; } else { ( isset( $output[ $num ] ) ) ? $output[ $num ] .= $a_string[ $i ] : $output[ $num ] = $a_string[ $i ]; } break; case 'str': if ( $a_string[ $i ] === $to && ! $this->parser->escaped( $a_string, $i ) ) { $status = 'st'; } ( isset( $output[ $num ] ) ) ? $output[ $num ] .= $a_string[ $i ] : $output[ $num ] = $a_string[ $i ]; break; } } if ( isset( $output[0] ) ) { return $output; } else { return array( $output ); } } /** * Merges Shorthand properties again, the opposite of dissolve_4value_shorthands(). * * @since 1.0.0 * * @param array $an_array [description]. * @return array [description] */ public function merge_4value_shorthands( array $an_array ): array { $return = $an_array; $shorthands = &$this->parser->data['csstidy']['shorthands']; foreach ( $shorthands as $key => $value ) { if ( 0 !== $value && isset( $an_array[ $value[0] ], $an_array[ $value[1] ], $an_array[ $value[2] ], $an_array[ $value[3] ] ) ) { $return[ $key ] = ''; $important = ''; for ( $i = 0; $i < 4; $i++ ) { $val = $an_array[ $value[ $i ] ]; if ( $this->parser->is_important( $val ) ) { $important = ' !important'; $return[ $key ] .= $this->parser->gvw_important( $val ) . ' '; } else { $return[ $key ] .= $val . ' '; } unset( $return[ $value[ $i ] ] ); } $return[ $key ] = $this->shorthand( trim( $return[ $key ] . $important ) ); } } return $return; } /** * Dissolve background property. * * @todo Full CSS3 compliance. * * @since 1.0.0 * * @param string $str_value String value. * @return array Array. */ public function dissolve_short_bg( string $str_value ): array { // Don't try to explode background gradient! if ( false !== stripos( $str_value, 'gradient(' ) ) { return array( 'background' => $str_value ); } $background_prop_default = &$this->parser->data['csstidy']['background_prop_default']; $repeat = array( 'repeat', 'repeat-x', 'repeat-y', 'no-repeat', 'space' ); $attachment = array( 'scroll', 'fixed', 'local' ); $clip = array( 'border', 'padding' ); $origin = array( 'border', 'padding', 'content' ); $pos = array( 'top', 'center', 'bottom', 'left', 'right' ); $important = ''; $return = array( 'background-image' => null, 'background-size' => null, 'background-repeat' => null, 'background-position' => null, 'background-attachment' => null, 'background-clip' => null, 'background-origin' => null, 'background-color' => null, ); if ( $this->parser->is_important( $str_value ) ) { $important = ' !important'; $str_value = $this->parser->gvw_important( $str_value ); } $have = array(); $str_value = $this->explode_ws( ',', $str_value ); $str_value_count = count( $str_value ); for ( $i = 0; $i < $str_value_count; $i++ ) { $have['clip'] = false; $have['pos'] = false; $have['color'] = false; $have['bg'] = false; if ( is_array( $str_value[ $i ] ) ) { $str_value[ $i ] = $str_value[ $i ][0]; } $str_value[ $i ] = $this->explode_ws( ' ', trim( $str_value[ $i ] ) ); $str_value_i_count = count( $str_value[ $i ] ); for ( $j = 0; $j < $str_value_i_count; $j++ ) { if ( false === $have['bg'] && ( str_starts_with( $str_value[ $i ][ $j ], 'url(' ) || 'none' === $str_value[ $i ][ $j ] ) ) { $return['background-image'] .= $str_value[ $i ][ $j ] . ','; $have['bg'] = true; } elseif ( in_array( $str_value[ $i ][ $j ], $repeat, true ) ) { $return['background-repeat'] .= $str_value[ $i ][ $j ] . ','; } elseif ( in_array( $str_value[ $i ][ $j ], $attachment, true ) ) { $return['background-attachment'] .= $str_value[ $i ][ $j ] . ','; } elseif ( in_array( $str_value[ $i ][ $j ], $clip, true ) && ! $have['clip'] ) { $return['background-clip'] .= $str_value[ $i ][ $j ] . ','; $have['clip'] = true; } elseif ( in_array( $str_value[ $i ][ $j ], $origin, true ) ) { $return['background-origin'] .= $str_value[ $i ][ $j ] . ','; } elseif ( '(' === $str_value[ $i ][ $j ][0] ) { $return['background-size'] .= substr( $str_value[ $i ][ $j ], 1, -1 ) . ','; } elseif ( in_array( $str_value[ $i ][ $j ], $pos, true ) || is_numeric( $str_value[ $i ][ $j ][0] ) || is_null( $str_value[ $i ][ $j ][0] ) || '-' === $str_value[ $i ][ $j ][0] || '.' === $str_value[ $i ][ $j ][0] ) { $return['background-position'] .= $str_value[ $i ][ $j ]; if ( ! $have['pos'] ) { $return['background-position'] .= ' '; } else { $return['background-position'] .= ','; } $have['pos'] = true; } elseif ( ! $have['color'] ) { $return['background-color'] .= $str_value[ $i ][ $j ] . ','; $have['color'] = true; } } } foreach ( $background_prop_default as $bg_prop => $default_value ) { if ( null !== $return[ $bg_prop ] ) { $return[ $bg_prop ] = substr( $return[ $bg_prop ], 0, -1 ) . $important; } else { $return[ $bg_prop ] = $default_value . $important; } } return $return; } /** * Merges all background properties. * * @todo Full CSS3 compliance. * * @since 1.0.0 * * @param array $input_css CSS. * @return array Array. */ public function merge_bg( array $input_css ): array { $background_prop_default = &$this->parser->data['csstidy']['background_prop_default']; // Max number of background images. CSS3 not yet fully implemented. $number_of_values = @max( count( $this->explode_ws( ',', $input_css['background-image'] ) ), count( $this->explode_ws( ',', $input_css['background-color'] ) ), 1 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged // Array with background images to check if BG image exists. $bg_img_array = @$this->explode_ws( ',', $this->parser->gvw_important( $input_css['background-image'] ) ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged $new_bg_value = ''; $important = ''; // If background properties is here and not empty, don't try anything. if ( isset( $input_css['background'] ) && $input_css['background'] ) { return $input_css; } for ( $i = 0; $i < $number_of_values; $i++ ) { foreach ( $background_prop_default as $bg_property => $default_value ) { // Skip if property does not exist. if ( ! isset( $input_css[ $bg_property ] ) ) { continue; } $cur_value = $input_css[ $bg_property ]; // Skip all optimisation if gradient() somewhere. if ( false !== stripos( $cur_value, 'gradient(' ) ) { return $input_css; } // Skip some properties if there is no background image. if ( ( ! isset( $bg_img_array[ $i ] ) || 'none' === $bg_img_array[ $i ] ) && ( 'background-size' === $bg_property || 'background-position' === $bg_property || 'background-attachment' === $bg_property || 'background-repeat' === $bg_property ) ) { continue; } // Remove !important. if ( $this->parser->is_important( $cur_value ) ) { $important = ' !important'; $cur_value = $this->parser->gvw_important( $cur_value ); } // Do not add default values. if ( $cur_value === $default_value ) { continue; } $temp = $this->explode_ws( ',', $cur_value ); if ( isset( $temp[ $i ] ) ) { if ( 'background-size' === $bg_property ) { $new_bg_value .= '(' . $temp[ $i ] . ') '; } else { $new_bg_value .= $temp[ $i ] . ' '; } } } $new_bg_value = trim( $new_bg_value ); if ( $i !== $number_of_values - 1 ) { $new_bg_value .= ','; } } // Delete all background properties. foreach ( $background_prop_default as $bg_property => $default_value ) { unset( $input_css[ $bg_property ] ); } // Add new background property. if ( '' !== $new_bg_value ) { $input_css['background'] = $new_bg_value . $important; } elseif ( isset( $input_css['background'] ) ) { $input_css['background'] = 'none'; } return $input_css; } /** * Dissolve font property. * * @since 1.0.0 * * @param string $str_value [description]. * @return array [description] */ public function dissolve_short_font( string $str_value ): array { $font_prop_default = &$this->parser->data['csstidy']['font_prop_default']; $font_weight = array( 'normal', 'bold', 'bolder', 'lighter', 100, 200, 300, 400, 500, 600, 700, 800, 900 ); $font_variant = array( 'normal', 'small-caps' ); $font_style = array( 'normal', 'italic', 'oblique' ); $important = ''; $return = array( 'font-style' => null, 'font-variant' => null, 'font-weight' => null, 'font-size' => null, 'line-height' => null, 'font-family' => null, ); if ( $this->parser->is_important( $str_value ) ) { $important = ' !important'; $str_value = $this->parser->gvw_important( $str_value ); } $have = array(); $have['style'] = false; $have['variant'] = false; $have['weight'] = false; $have['size'] = false; // Detects if font-family consists of several words w/o quotes. $multiwords = false; // Workaround with multiple font-families. $str_value = $this->explode_ws( ',', trim( $str_value ) ); $str_value[0] = $this->explode_ws( ' ', trim( $str_value[0] ) ); $str_value_0_count = count( $str_value[0] ); for ( $j = 0; $j < $str_value_0_count; $j++ ) { if ( false === $have['weight'] && in_array( $str_value[0][ $j ], $font_weight, false ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.FoundNonStrictFalse $return['font-weight'] = $str_value[0][ $j ]; $have['weight'] = true; } elseif ( false === $have['variant'] && in_array( $str_value[0][ $j ], $font_variant, true ) ) { $return['font-variant'] = $str_value[0][ $j ]; $have['variant'] = true; } elseif ( false === $have['style'] && in_array( $str_value[0][ $j ], $font_style, true ) ) { $return['font-style'] = $str_value[0][ $j ]; $have['style'] = true; } elseif ( false === $have['size'] && ( is_numeric( $str_value[0][ $j ][0] ) || is_null( $str_value[0][ $j ][0] ) || '.' === $str_value[0][ $j ][0] ) ) { $size = $this->explode_ws( '/', trim( $str_value[0][ $j ] ) ); $return['font-size'] = $size[0]; if ( isset( $size[1] ) ) { $return['line-height'] = $size[1]; } else { $return['line-height'] = ''; // Don't add 'normal'! } $have['size'] = true; } else { // phpcs:ignore Universal.ControlStructures.DisallowLonelyIf.Found if ( isset( $return['font-family'] ) ) { $return['font-family'] .= ' ' . $str_value[0][ $j ]; $multiwords = true; } else { $return['font-family'] = $str_value[0][ $j ]; } } } // Add quotes if we have several words in font-family. if ( $multiwords ) { $return['font-family'] = '"' . $return['font-family'] . '"'; } $i = 1; while ( isset( $str_value[ $i ] ) ) { $return['font-family'] .= ',' . trim( $str_value[ $i ] ); ++$i; } // Fix for font-size 100 and higher. if ( false === $have['size'] && isset( $return['font-weight'] ) && is_numeric( $return['font-weight'][0] ) ) { $return['font-size'] = $return['font-weight']; unset( $return['font-weight'] ); } foreach ( $font_prop_default as $font_prop => $default_value ) { if ( null !== $return[ $font_prop ] ) { $return[ $font_prop ] = $return[ $font_prop ] . $important; } else { $return[ $font_prop ] = $default_value . $important; } } return $return; } /** * Merges all fonts properties. * * @since 1.0.0 * * @param array $input_css [description]. * @return array [description] */ public function merge_font( array $input_css ): array { $font_prop_default = &$this->parser->data['csstidy']['font_prop_default']; $new_font_value = ''; $important = ''; // Skip if no font-family and font-size set. if ( isset( $input_css['font-family'], $input_css['font-size'] ) && 'inherit' !== $input_css['font-family'] ) { // Fix several words in font-family - add quotes. $families = explode( ',', $input_css['font-family'] ); $result_families = array(); foreach ( $families as $family ) { $family = trim( $family ); $len = strlen( $family ); if ( str_contains( $family, ' ' ) && ! ( ( '"' === $family[0] && '"' === $family[ $len - 1 ] ) || ( "'" === $family[0] && "'" === $family[ $len - 1 ] ) ) ) { $family = '"' . $family . '"'; } $result_families[] = $family; } $input_css['font-family'] = implode( ',', $result_families ); foreach ( $font_prop_default as $font_property => $default_value ) { // Skip if property does not exist. if ( ! isset( $input_css[ $font_property ] ) ) { continue; } $cur_value = $input_css[ $font_property ]; // Skip if default value is used. if ( $cur_value === $default_value ) { continue; } // Remove !important. if ( $this->parser->is_important( $cur_value ) ) { $important = ' !important'; $cur_value = $this->parser->gvw_important( $cur_value ); } $new_font_value .= $cur_value; // Add delimiter. $new_font_value .= ( 'font-size' === $font_property && isset( $input_css['line-height'] ) ) ? '/' : ' '; } $new_font_value = trim( $new_font_value ); // Delete all font properties. foreach ( $font_prop_default as $font_property => $default_value ) { if ( 'font' !== $font_property || ! $new_font_value ) { unset( $input_css[ $font_property ] ); } } // Add new font property. if ( '' !== $new_font_value ) { $input_css['font'] = $new_font_value . $important; } } return $input_css; } } // class TablePress_CSSTidy_Optimise