Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 158
0.00% covered (danger)
0.00%
0 / 13
CRAP
0.00% covered (danger)
0.00%
0 / 1
csstidy_print
0.00% covered (danger)
0.00%
0 / 158
0.00% covered (danger)
0.00%
0 / 13
4830
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 csstidy_print
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 _reset
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 plain
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 formatted
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 formatted_page
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
42
 _print
0.00% covered (danger)
0.00%
0 / 73
0.00% covered (danger)
0.00%
0 / 1
992
 seeknocomment
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
30
 convert_raw_css
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
110
 htmlsp
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 get_ratio
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 get_diff
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
 size
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
20
1<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2/**
3 * CSSTidy - CSS Parser and Optimiser
4 *
5 * CSS Printing class
6 * This class prints CSS data generated by csstidy.
7 *
8 * Copyright 2005, 2006, 2007 Florian Schmitz
9 *
10 * This file is part of CSSTidy.
11 *
12 *   CSSTidy is free software; you can redistribute it and/or modify
13 *   it under the terms of the GNU Lesser General Public License as published by
14 *   the Free Software Foundation; either version 2.1 of the License, or
15 *   (at your option) any later version.
16 *
17 *   CSSTidy is distributed in the hope that it will be useful,
18 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
19 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 *   GNU Lesser General Public License for more details.
21 *
22 *   You should have received a copy of the GNU Lesser General Public License
23 *   along with this program.  If not, see <https://www.gnu.org/licenses/>.
24 *
25 * @license https://opensource.org/licenses/lgpl-license.php GNU Lesser General Public License
26 * @package csstidy
27 * @author Florian Schmitz (floele at gmail dot com) 2005-2007
28 * @author Brett Zamir (brettz9 at yahoo dot com) 2007
29 * @author Cedric Morin (cedric at yterium dot com) 2010
30 */
31
32/**
33 * CSS Printing class
34 *
35 * This class prints CSS data generated by csstidy.
36 *
37 * @package csstidy
38 * @author Florian Schmitz (floele at gmail dot com) 2005-2006
39 * @version 1.0.1
40 */
41#[AllowDynamicProperties]
42class csstidy_print { // phpcs:ignore
43
44    /**
45     * Saves the input CSS string
46     *
47     * @var string
48     * @access private
49     */
50    public $input_css = '';
51    /**
52     * Saves the formatted CSS string
53     *
54     * @var string
55     * @access public
56     */
57    public $output_css = '';
58    /**
59     * Saves the formatted CSS string (plain text)
60     *
61     * @var string
62     * @access public
63     */
64    public $output_css_plain = '';
65
66    /**
67     * Constructor
68     *
69     * @param csstidy $css contains the class csstidy.
70     * @access private
71     * @version 1.0
72     */
73    public function __construct( &$css ) {
74        $this->parser    = & $css;
75        $this->css       = & $css->css;
76        $this->template  = & $css->template;
77        $this->tokens    = & $css->tokens;
78        $this->charset   = & $css->charset;
79        $this->import    = & $css->import;
80        $this->namespace = & $css->namespace;
81    }
82
83    /**
84     * Call constructor function.
85     *
86     * @param csstidy $css - the CSS we're working with.
87     */
88    public function csstidy_print( &$css ) {
89        $this->__construct( $css );
90    }
91
92    /**
93     * Resets output_css and output_css_plain (new css code)
94     *
95     * @access private
96     * @version 1.0
97     */
98    public function _reset() { // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore
99        $this->output_css       = '';
100        $this->output_css_plain = '';
101    }
102
103    /**
104     * Returns the CSS code as plain text
105     *
106     * @param string $default_media default @media to add to selectors without any @media.
107     * @return string
108     * @access public
109     * @version 1.0
110     */
111    public function plain( $default_media = '' ) {
112        $this->_print( true, $default_media );
113        return $this->output_css_plain;
114    }
115
116    /**
117     * Returns the formatted CSS code
118     *
119     * @param string $default_media default @media to add to selectors without any @media.
120     * @return string
121     * @access public
122     * @version 1.0
123     */
124    public function formatted( $default_media = '' ) {
125        $this->_print( false, $default_media );
126        return $this->output_css;
127    }
128
129    /**
130     * Returns the formatted CSS code to make a complete webpage
131     *
132     * @param string $doctype shorthand for the document type.
133     * @param bool   $externalcss indicates whether styles to be attached internally or as an external stylesheet.
134     * @param string $title title to be added in the head of the document.
135     * @param string $lang two-letter language code to be added to the output.
136     * @return string
137     * @access public
138     * @version 1.4
139     */
140    public function formatted_page( $doctype = 'xhtml1.1', $externalcss = true, $title = '', $lang = 'en' ) {
141        switch ( $doctype ) {
142            case 'xhtml1.0strict':
143                $doctype_output = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
144            "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
145                break;
146            case 'xhtml1.1':
147            default:
148                $doctype_output = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
149                "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">';
150                break;
151        }
152        $cssparsed              = '';
153        $output                 = '';
154        $this->output_css_plain = & $output;
155
156        $output .= $doctype_output . "\n" . '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="' . $lang . '"';
157        $output .= ( $doctype === 'xhtml1.1' ) ? '>' : ' lang="' . $lang . '">';
158        $output .= "\n<head>\n    <title>$title</title>";
159
160        if ( $externalcss ) {
161            $output   .= "\n    <style type=\"text/css\">\n";
162            $cssparsed = file_get_contents( 'cssparsed.css' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
163            $output   .= $cssparsed; // Adds an invisible BOM or something, but not in css_optimised.php
164            $output   .= "\n</style>";
165        } else {
166            $output .= "\n" . '    <link rel="stylesheet" type="text/css" href="cssparsed.css" />'; // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet
167            // }
168        }
169        $output .= "\n</head>\n<body><code id=\"copytext\">";
170        $output .= $this->formatted();
171        $output .= '</code>' . "\n" . '</body></html>';
172        return $this->output_css_plain;
173    }
174
175    /**
176     * Returns the formatted CSS Code and saves it into $this->output_css and $this->output_css_plain
177     *
178     * @param bool   $plain plain text or not.
179     * @param string $default_media default @media to add to selectors without any @media.
180     * @access private
181     * @version 2.0
182     */
183    public function _print( $plain = false, $default_media = '' ) { // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore -- print is a reserved word anyway.
184        if ( $this->output_css && $this->output_css_plain ) {
185            return;
186        }
187
188        $output = '';
189        if ( ! $this->parser->get_cfg( 'preserve_css' ) ) {
190            $this->convert_raw_css( $default_media );
191        }
192
193        $template = & $this->template;
194
195        if ( $plain ) {
196            $template = array_map( 'strip_tags', $template );
197        }
198
199        if ( $this->parser->get_cfg( 'timestamp' ) ) {
200            array_unshift( $this->tokens, array( COMMENT, ' CSSTidy ' . $this->parser->version . ': ' . gmdate( 'r' ) . ' ' ) );
201        }
202
203        if ( ! empty( $this->charset ) ) {
204            $output .= $template[0] . '@charset ' . $template[5] . $this->charset . $template[6];
205        }
206
207        if ( ! empty( $this->import ) ) {
208            $import_count = is_countable( $this->import ) ? count( $this->import ) : 0;
209            for ( $i = 0; $i < $import_count; $i++ ) {
210                $import_components = explode( ' ', $this->import[ $i ] );
211                if ( str_starts_with( $import_components[0], 'url(' ) && str_ends_with( $import_components[0], ')' ) ) {
212                    $import_components[0] = '\'' . trim( substr( $import_components[0], 4, -1 ), "'\"" ) . '\'';
213                    $this->import[ $i ]   = implode( ' ', $import_components );
214                    $this->parser->log( 'Optimised @import : Removed "url("', 'Information' );
215                }
216                $output .= $template[0] . '@import ' . $template[5] . $this->import[ $i ] . $template[6];
217            }
218        }
219        if ( ! empty( $this->namespace ) ) {
220            if ( str_starts_with( $this->namespace, 'url(' ) && str_ends_with( $this->namespace, ')' ) ) {
221                $this->namespace = '\'' . substr( $this->namespace, 4, -1 ) . '\'';
222                $this->parser->log( 'Optimised @namespace : Removed "url("', 'Information' );
223            }
224            $output .= $template[0] . '@namespace ' . $template[5] . $this->namespace . $template[6];
225        }
226
227        $output   .= $template[13];
228        $in_at_out = '';
229        $out       = & $output;
230
231        foreach ( $this->tokens as $key => $token ) {
232            switch ( $token[0] ) {
233                case AT_START:
234                    $out .= $template[0] . $this->htmlsp( $token[1], $plain ) . $template[1];
235                    $out  = & $in_at_out;
236                    break;
237
238                case SEL_START:
239                    if ( $this->parser->get_cfg( 'lowercase_s' ) ) {
240                        $token[1] = strtolower( $token[1] );
241                    }
242                    $out .= ( $token[1][0] !== '@' ) ? $template[2] . $this->htmlsp( $token[1], $plain ) : $template[0] . $this->htmlsp( $token[1], $plain );
243                    $out .= $template[3];
244                    break;
245
246                case PROPERTY:
247                    if ( $this->parser->get_cfg( 'case_properties' ) === 2 ) {
248                        $token[1] = strtoupper( $token[1] );
249                    } elseif ( $this->parser->get_cfg( 'case_properties' ) === 1 ) {
250                        $token[1] = strtolower( $token[1] );
251                    }
252                    $out .= $template[4] . $this->htmlsp( $token[1], $plain ) . ':' . $template[5];
253                    break;
254
255                case VALUE:
256                    $out .= $this->htmlsp( $token[1], $plain );
257                    if ( $this->seeknocomment( $key, 1 ) === SEL_END && $this->parser->get_cfg( 'remove_last_;' ) ) {
258                        $out .= str_replace( ';', '', $template[6] );
259                    } else {
260                        $out .= $template[6];
261                    }
262                    break;
263
264                case SEL_END:
265                    $out .= $template[7];
266                    if ( $this->seeknocomment( $key, 1 ) !== AT_END ) {
267                        $out .= $template[8];
268                    }
269                    break;
270
271                case AT_END:
272                    $out       = & $output;
273                    $out      .= $template[10] . str_replace( "\n", "\n" . $template[10], $in_at_out );
274                    $in_at_out = '';
275                    $out      .= $template[9];
276                    break;
277
278                case COMMENT:
279                    $out .= $template[11] . '/*' . $this->htmlsp( $token[1], $plain ) . '*/' . $template[12];
280                    break;
281            }
282        }
283
284        $output = trim( $output );
285
286        if ( ! $plain ) {
287            $this->output_css = $output;
288            $this->_print( true );
289        } else {
290            // If using spaces in the template, don't want these to appear in the plain output
291            $this->output_css_plain = str_replace( '&#160;', '', $output );
292        }
293    }
294
295    /**
296     * Gets the next token type which is $move away from $key, excluding comments
297     *
298     * @param integer $key current position.
299     * @param integer $move move this far.
300     * @return mixed a token type
301     * @access private
302     * @version 1.0
303     */
304    public function seeknocomment( $key, $move ) {
305        $go = ( $move > 0 ) ? 1 : -1;
306        for ( $i = $key + 1; abs( $key - $i ) - 1 < abs( $move ); $i += $go ) { // phpcs:ignore Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed
307            if ( ! isset( $this->tokens[ $i ] ) ) {
308                return;
309            }
310            if ( $this->tokens[ $i ][0] === COMMENT ) {
311                ++$move;
312                continue;
313            }
314            return $this->tokens[ $i ][0];
315        }
316    }
317
318    /**
319     * Converts $this->css array to a raw array ($this->tokens)
320     *
321     * @param string $default_media default @media to add to selectors without any @media.
322     * @access private
323     * @version 1.0
324     */
325    public function convert_raw_css( $default_media = '' ) {
326        $this->tokens = array();
327
328        foreach ( $this->css as $medium => $val ) {
329            if ( $this->parser->get_cfg( 'sort_selectors' ) ) {
330                ksort( $val );
331            }
332            if ( (int) $medium < DEFAULT_AT ) {
333                $this->parser->_add_token( AT_START, $medium, true );
334            } elseif ( $default_media ) {
335                $this->parser->_add_token( AT_START, $default_media, true );
336            }
337
338            foreach ( $val as $selector => $vali ) {
339                if ( $this->parser->get_cfg( 'sort_properties' ) ) {
340                    ksort( $vali );
341                }
342                $this->parser->_add_token( SEL_START, $selector, true );
343
344                foreach ( $vali as $property => $valj ) {
345                    $this->parser->_add_token( PROPERTY, $property, true );
346                    $this->parser->_add_token( VALUE, $valj, true );
347                }
348
349                $this->parser->_add_token( SEL_END, $selector, true );
350            }
351
352            if ( (int) $medium < DEFAULT_AT ) {
353                $this->parser->_add_token( AT_END, $medium, true );
354            } elseif ( $default_media ) {
355                $this->parser->_add_token( AT_END, $default_media, true );
356            }
357        }
358    }
359
360    /**
361     * Same as htmlspecialchars, only that chars are not replaced if $plain !== true. This makes  print_code() cleaner.
362     *
363     * @param string $string - the string we're converting.
364     * @param bool   $plain - plain text or not.
365     * @return string
366     * @see csstidy_print::_print()
367     * @access private
368     * @version 1.0
369     */
370    public function htmlsp( $string, $plain ) {
371        if ( ! $plain ) {
372            return htmlspecialchars( $string, ENT_QUOTES, 'utf-8' );
373        }
374        return $string;
375    }
376
377    /**
378     * Get compression ratio
379     *
380     * @access public
381     * @return float
382     * @version 1.2
383     */
384    public function get_ratio() {
385        if ( ! $this->output_css_plain ) {
386            $this->formatted();
387        }
388        return round( ( strlen( $this->input_css ) - strlen( $this->output_css_plain ) ) / strlen( $this->input_css ), 3 ) * 100;
389    }
390
391    /**
392     * Get difference between the old and new code in bytes and prints the code if necessary.
393     *
394     * @access public
395     * @return string
396     * @version 1.1
397     */
398    public function get_diff() {
399        if ( ! $this->output_css_plain ) {
400            $this->formatted();
401        }
402
403        $diff = strlen( $this->output_css_plain ) - strlen( $this->input_css );
404
405        if ( $diff > 0 ) {
406            return '+' . $diff;
407        } elseif ( $diff === 0 ) {
408            return '+-' . $diff;
409        }
410
411        return $diff;
412    }
413
414    /**
415     * Get the size of either input or output CSS in KB
416     *
417     * @param string $loc default is "output".
418     * @access public
419     * @return integer
420     * @version 1.0
421     */
422    public function size( $loc = 'output' ) {
423        if ( $loc === 'output' && ! $this->output_css ) {
424            $this->formatted();
425        }
426
427        if ( $loc === 'input' ) {
428            return ( strlen( $this->input_css ) / 1000 );
429        } else {
430            return ( strlen( $this->output_css_plain ) / 1000 );
431        }
432    }
433}