PluginProbe
TablePress – Tables in WordPress made easy / 2.2.1
TablePress – Tables in WordPress made easy v2.2.1
3.4 3.3.4 3.3.3 3.3.2 3.3.1 trunk 1.12 1.14 1.9.2 2.0.4 2.1.7 2.1.8 2.2 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.3 2.3.1 2.3.2 2.4 2.4.1 2.4.2 2.4.3 All 45 releases
tablepress / libraries / csstidy / class.csstidy_print.php

class.csstidy_print.php in TablePress – Tables in WordPress made easy 2.2.1, at libraries/csstidy/class.csstidy_print.php

533 lines 15.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * CSSTidy Printing PHP Class
4 *
5 * @package TablePress
6 * @subpackage CSS
7 * @author Florian Schmitz, Brett Zamir, Nikolay Matsievsky, Cedric Morin, Christopher Finke, Mark Scherer, Tobias Bäthge
8 * @since 1.0.0
9 */
10
11 // Prohibit direct script loading.
12 defined( 'ABSPATH' ) || die( 'No direct script access allowed!' );
13
14 /**
15 * CSS Printing class
16 *
17 * This class prints CSS data generated by CSSTidy.
18 *
19 * @package CSSTidy
20 * @version 1.1.0
21 */
22 class TablePress_CSSTidy_Print {
23
24 /**
25 * TablePress_CSSTidy instance.
26 *
27 * @since 1.0.0
28 * @var TablePress_CSSTidy
29 */
30 public $parser;
31
32 /**
33 * The parsed CSS.
34 *
35 * @since 1.0.0
36 * @var array
37 */
38 public $css = array();
39
40 /**
41 * The output templates.
42 *
43 * @since 1.0.0
44 * @var array
45 */
46 public $template = array();
47
48 /**
49 * The raw parsed CSS.
50 *
51 * @since 1.0.0
52 * @var array
53 */
54 public $tokens = array();
55
56 /**
57 * The CSS charset.
58 *
59 * @since 1.0.0
60 * @var string
61 */
62 public $charset = '';
63
64 /**
65 * All @import URLs.
66 *
67 * @since 1.0.0
68 * @var array
69 */
70 public $import = array();
71
72 /**
73 * The namespace.
74 *
75 * @since 1.0.0
76 * @var string
77 */
78 public $namespace = '';
79
80 /**
81 * Saves the input CSS string.
82 *
83 * @since 1.0.0
84 * @var string
85 */
86 public $input_css = '';
87
88 /**
89 * Saves the formatted CSS string.
90 *
91 * @since 1.0.0
92 * @var string
93 */
94 public $output_css = '';
95
96 /**
97 * Saves the formatted CSS string (plain text).
98 *
99 * @since 1.0.0
100 * @var string
101 */
102 public $output_css_plain = '';
103
104 /**
105 * Constructor.
106 *
107 * @since 1.0
108 *
109 * @param TablePress_CSSTidy $csstidy Instance of the TablePress_CSSTidy class.
110 */
111 public function __construct( TablePress_CSSTidy $csstidy ) {
112 $this->parser = $csstidy;
113 $this->css = &$csstidy->css;
114 $this->template = &$csstidy->template;
115 $this->tokens = &$csstidy->tokens;
116 $this->charset = &$csstidy->charset;
117 $this->import = &$csstidy->import;
118 $this->namespace = &$csstidy->namespace;
119 }
120
121 /**
122 * Resets output_css and output_css_plain (new CSS code).
123 *
124 * @since 1.0
125 */
126 public function _reset(): void {
127 $this->output_css = '';
128 $this->output_css_plain = '';
129 }
130
131 /**
132 * Returns the CSS code as plain text.
133 *
134 * @since 1.0
135 *
136 * @param string $default_media Optional. Default @media to add to selectors without any @media.
137 * @return string Plain CSS.
138 */
139 public function plain( string $default_media = '' ): string {
140 $this->_print( true, $default_media );
141 return $this->output_css_plain;
142 }
143
144 /**
145 * Returns the formatted CSS code.
146 *
147 * @since 1.0
148 *
149 * @param string $default_media Optional. Default @media to add to selectors without any @media.
150 * @return string Formatted CSS.
151 */
152 public function formatted( string $default_media = '' ): string {
153 $this->_print( false, $default_media );
154 return $this->output_css;
155 }
156
157 /**
158 * Returns the formatted CSS code to make a complete webpage.
159 *
160 * @since 1.0
161 *
162 * @param string $doctype Optional. Shorthand for the document type.
163 * @param bool $externalcss Optional. Indicates whether styles to be attached internally or as an external stylesheet.
164 * @param string $title Optional. Title to be added in the head of the document.
165 * @param string $lang Optional. Two-letter language code to be added to the output.
166 * @return string Formatted CSS for a full page.
167 */
168 public function formatted_page( string $doctype = 'html5', bool $externalcss = true, string $title = '', string $lang = 'en' ): string {
169 switch ( $doctype ) {
170 case 'html5':
171 $doctype_output = '<!DOCTYPE html>';
172 break;
173 case 'xhtml1.0strict':
174 $doctype_output = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
175 break;
176 case 'xhtml1.1':
177 default:
178 $doctype_output = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">';
179 break;
180 }
181
182 $output = '';
183 $output .= $doctype_output . "\n" . '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="' . $lang . '"';
184 $output .= ( 'xhtml1.1' === $doctype ) ? '>' : ' lang="' . $lang . '">';
185 $output .= "\n<head>\n\t<title>{$title}</title>";
186
187 if ( $externalcss ) {
188 $output .= "\n\t<style>\n";
189 $output .= file_get_contents( 'cssparsed.css' ); // Adds an invisible BOM or something, but not in css_optimised.php.
190 $output .= "\n</style>";
191 } else {
192 $output .= "\n" . '<link rel="stylesheet" type="text/css" href="cssparsed.css" />'; // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet
193 }
194 $output .= "\n</head>\n<body><code id=\"copytext\">";
195 $output .= $this->formatted();
196 $output .= '</code>' . "\n" . '</body></html>';
197
198 $this->output_css_plain = $output;
199 return $this->output_css_plain;
200 }
201
202 /**
203 * Returns the formatted CSS Code and saves it into $this->output_css and $this->output_css_plain.
204 *
205 * @since 1.0
206 *
207 * @param bool $plain Optional. Plain text or not.
208 * @param string $default_media Optional. Default @media to add to selectors without any @media.
209 */
210 protected function _print( bool $plain = false, string $default_media = '' ): void {
211 if ( $this->output_css && $this->output_css_plain ) {
212 return;
213 }
214
215 $output = '';
216 if ( ! $this->parser->get_cfg( 'preserve_css' ) ) {
217 $this->_convert_raw_css( $default_media );
218 }
219
220 $template = &$this->template;
221
222 if ( $plain ) {
223 $template = array_map( 'strip_tags', $template );
224 }
225
226 if ( $this->parser->get_cfg( 'timestamp' ) ) {
227 array_unshift( $this->tokens, array( TablePress_CSSTidy::COMMENT, ' CSSTidy: ' . gmdate( 'r' ) . ' ' ) );
228 }
229
230 if ( ! empty( $this->charset ) ) {
231 $output .= $template[0] . '@charset ' . $template[5] . $this->charset . $template[6] . $template[13];
232 }
233
234 if ( ! empty( $this->import ) ) {
235 for ( $i = 0, $size = count( $this->import ); $i < $size; $i++ ) {
236 $import_components = explode( ' ', $this->import[ $i ] );
237 if ( str_starts_with( $import_components[0], 'url(' ) && str_ends_with( $import_components[0], ')' ) ) {
238 $import_components[0] = '\'' . trim( substr( $import_components[0], 4, -1 ), "'\"" ) . '\'';
239 $this->import[ $i ] = implode( ' ', $import_components );
240 $this->parser->log( 'Optimised @import : Removed "url("', 'Information' );
241 } elseif ( ! preg_match( '/^".+"$/', $this->import[ $i ] ) ) {
242 // Fixes a bug for @import ".." instead of the expected @import url("..").
243 // If it comes in due to @import ".." the "" will be missing and the output will become @import .. (which is an error).
244 $this->import[ $i ] = '"' . $this->import[ $i ] . '"';
245 }
246 $output .= $template[0] . '@import ' . $template[5] . $this->import[ $i ] . $template[6] . $template[13];
247 }
248 }
249
250 if ( ! empty( $this->namespace ) ) {
251 if ( false !== ( $p = strpos( $this->namespace, 'url(' ) ) && str_ends_with( $this->namespace, ')' ) ) { // phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.Found,Squiz.PHP.DisallowMultipleAssignments.FoundInControlStructure
252 $this->namespace = substr_replace( $this->namespace, '"', $p, 4 );
253 $this->namespace = substr( $this->namespace, 0, -1 ) . '"';
254 $this->parser->log( 'Optimised @namespace : Removed "url("', 'Information' );
255 }
256 $output .= $template[0] . '@namespace ' . $template[5] . $this->namespace . $template[6] . $template[13];
257 }
258
259 $in_at_out = array();
260 $out = &$output;
261 $indent_level = 0;
262
263 foreach ( $this->tokens as $key => $token ) {
264 switch ( $token[0] ) {
265 case TablePress_CSSTidy::AT_START:
266 if ( $this->parser->get_cfg( 'preserve_css' ) ) {
267 $token[1] = str_replace( ',', ",\n", $token[1] );
268 }
269 $out .= $template[0] . $this->_htmlsp( $token[1], $plain ) . $template[1];
270 ++$indent_level;
271 if ( ! isset( $in_at_out[ $indent_level ] ) ) {
272 $in_at_out[ $indent_level ] = '';
273 }
274 $out = &$in_at_out[ $indent_level ];
275 break;
276 case TablePress_CSSTidy::SEL_START:
277 if ( $this->parser->get_cfg( 'lowercase_s' ) ) {
278 $token[1] = strtolower( $token[1] );
279 }
280 if ( $this->parser->get_cfg( 'preserve_css' ) ) {
281 $token[1] = str_replace( ',', ",\n", $token[1] );
282 }
283 $out .= ( '@' !== $token[1][0] ) ? $template[2] . $this->_htmlsp( $token[1], $plain ) : $template[0] . $this->_htmlsp( $token[1], $plain );
284 $out .= $template[3];
285 break;
286 case TablePress_CSSTidy::PROPERTY:
287 if ( 2 === $this->parser->get_cfg( 'case_properties' ) ) {
288 $token[1] = strtoupper( $token[1] );
289 } elseif ( 1 === $this->parser->get_cfg( 'case_properties' ) ) {
290 $token[1] = strtolower( $token[1] );
291 }
292 $out .= $template[4] . $this->_htmlsp( $token[1], $plain ) . ':' . $template[5];
293 break;
294 case TablePress_CSSTidy::VALUE:
295 $out .= $this->_htmlsp( $token[1], $plain );
296 if ( TablePress_CSSTidy::SEL_END === $this->_seeknocomment( $key, 1 ) && $this->parser->get_cfg( 'remove_last_;' ) ) {
297 $out .= str_replace( ';', '', $template[6] );
298 } else {
299 $out .= $template[6];
300 }
301 if ( $this->parser->get_cfg( 'preserve_css' ) ) {
302 $out .= ( TablePress_CSSTidy::COMMENT === $this->tokens[ $key + 1 ][0] ) ? ' ' : "\n";
303 }
304 break;
305 case TablePress_CSSTidy::SEL_END:
306 $out .= $template[7];
307 if ( TablePress_CSSTidy::AT_END !== $this->_seeknocomment( $key, 1 ) ) {
308 $out .= $template[8];
309 }
310 break;
311 case TablePress_CSSTidy::AT_END:
312 if ( strlen( $template[10] ) ) {
313 // Indent the block we are closing.
314 $out = str_replace( "\n\n", "\r\n", $out ); // Don't fill empty lines.
315 $out = str_replace( "\n", "\n" . $template[10], $out );
316 $out = str_replace( "\r\n", "\n\n", $out );
317 }
318 if ( $indent_level > 1 ) {
319 $out = &$in_at_out[ $indent_level - 1 ];
320 } else {
321 $out = &$output;
322 }
323 $out .= $template[10] . $in_at_out[ $indent_level ];
324 if ( TablePress_CSSTidy::AT_END !== $this->_seeknocomment( $key, 1 ) ) {
325 $out .= $template[9];
326 } else {
327 $out .= rtrim( $template[9] );
328 }
329 unset( $in_at_out[ $indent_level ] );
330 --$indent_level;
331 break;
332 case TablePress_CSSTidy::IMPORTANT_COMMENT:
333 case TablePress_CSSTidy::COMMENT:
334 $out .= $template[11] . '/*' . $this->_htmlsp( $token[1], $plain ) . '*/' . $template[12];
335 break;
336 }
337 }
338
339 if ( ! $this->parser->get_cfg( 'preserve_css' ) ) {
340 $output = str_replace( ' !important', '!important', $output );
341 }
342
343 $output = trim( $output );
344
345 if ( ! $plain ) {
346 $this->output_css = $output;
347 $this->_print( true );
348 } else {
349 // If using spaces in the template, don't want these to appear in the plain output.
350 $this->output_css_plain = str_replace( '&#160;', '', $output );
351 }
352 }
353
354 /**
355 * Gets the next token type which is $move away from $key, excluding comments.
356 *
357 * @since 1.0
358 *
359 * @param int $key Current position.
360 * @param int $move Move this far.
361 * @return mixed A token type.
362 */
363 protected function _seeknocomment( int $key, int $move ) /* : mixed */ {
364 $go = ( $move > 0 ) ? 1 : -1;
365 for ( $i = $key + 1; abs( $key - $i ) - 1 < abs( $move ); $i += $go ) { // phpcs:ignore Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed
366 if ( ! isset( $this->tokens[ $i ] ) ) {
367 return;
368 }
369 if ( TablePress_CSSTidy::COMMENT === $this->tokens[ $i ][0] ) {
370 ++$move;
371 continue;
372 }
373 return $this->tokens[ $i ][0];
374 }
375 }
376
377 /**
378 * Converts $this->css array to a raw array ($this->tokens).
379 *
380 * @since 1.0.0
381 *
382 * @param string $default_media Optional. Default @media to add to selectors without any @media.
383 */
384 protected function _convert_raw_css( string $default_media = '' ): void {
385 $this->tokens = array();
386 $sort_selectors = $this->parser->get_cfg( 'sort_selectors' );
387 $sort_properties = $this->parser->get_cfg( 'sort_properties' );
388
389 // Important comment section?
390 if ( isset( $this->css['!'] ) ) {
391 $this->parser->_add_token( TablePress_CSSTidy::IMPORTANT_COMMENT, rtrim( $this->css['!'] ), true );
392 unset( $this->css['!'] );
393 }
394
395 foreach ( $this->css as $medium => $val ) {
396 if ( $sort_selectors ) {
397 ksort( $val );
398 }
399 if ( (int) $medium < TablePress_CSSTidy::DEFAULT_AT ) {
400 // An empty medium (containing @font-face or other @) produces no container.
401 if ( strlen( trim( $medium ) ) ) {
402 $parts_to_open = explode( '{', $medium );
403 foreach ( $parts_to_open as $part ) {
404 $this->parser->_add_token( TablePress_CSSTidy::AT_START, $part, true );
405 }
406 }
407 } elseif ( $default_media ) {
408 $this->parser->_add_token( TablePress_CSSTidy::AT_START, $default_media, true );
409 }
410
411 foreach ( $val as $selector => $vali ) {
412 if ( $sort_properties ) {
413 ksort( $vali );
414 }
415 $this->parser->_add_token( TablePress_CSSTidy::SEL_START, $selector, true );
416
417 $invalid = array(
418 '*' => array(), // IE7 hacks first.
419 '_' => array(), // IE6 hacks.
420 '/' => array(), // IE6 hacks.
421 '-' => array(), // IE6 hacks.
422 );
423 foreach ( $vali as $property => $valj ) {
424 if ( ! str_starts_with( $property, '//' ) ) {
425 $matches = array();
426 if ( $sort_properties && preg_match( '/^(\*|_|\/|-)(?!(ms|moz|o\b|xv|atsc|wap|khtml|webkit|ah|hp|ro|rim|tc)-)/', $property, $matches ) ) {
427 $invalid[ $matches[1] ][ $property ] = $valj;
428 } else {
429 $this->parser->_add_token( TablePress_CSSTidy::PROPERTY, $property, true );
430 $this->parser->_add_token( TablePress_CSSTidy::VALUE, $valj, true );
431 }
432 }
433 }
434 foreach ( $invalid as $prefix => $props ) {
435 foreach ( $props as $property => $valj ) {
436 $this->parser->_add_token( TablePress_CSSTidy::PROPERTY, $property, true );
437 $this->parser->_add_token( TablePress_CSSTidy::VALUE, $valj, true );
438 }
439 }
440 $this->parser->_add_token( TablePress_CSSTidy::SEL_END, $selector, true );
441 }
442
443 if ( (int) $medium < TablePress_CSSTidy::DEFAULT_AT ) {
444 // An empty medium (containing @font-face or other @) produces no container.
445 if ( strlen( trim( $medium ) ) ) {
446 $parts_to_close = explode( '{', $medium );
447 foreach ( array_reverse( $parts_to_close ) as $part ) {
448 $this->parser->_add_token( TablePress_CSSTidy::AT_END, $part, true );
449 }
450 }
451 } elseif ( $default_media ) {
452 $this->parser->_add_token( TablePress_CSSTidy::AT_END, $default_media, true );
453 }
454 }
455 }
456
457 /**
458 * Same as htmlspecialchars, only that chars are not replaced if $plain is true. This makes print_code() cleaner.
459 *
460 * @since 1.0
461 *
462 * @see CSSTidy_print::_print()
463 *
464 * @param string $a_string String.
465 * @param bool $plain Whether to print plain.
466 * @return string Original string or htmlspecialchars() version.
467 */
468 protected function _htmlsp( string $a_string, bool $plain ): string {
469 if ( ! $plain ) {
470 return htmlspecialchars( $a_string, ENT_QUOTES, 'utf-8' );
471 }
472 return $a_string;
473 }
474
475 /**
476 * Gets compression ratio.
477 *
478 * @since 1.2
479 *
480 * @return double Compression ratio.
481 */
482 public function get_ratio(): float {
483 if ( ! $this->output_css_plain ) {
484 $this->formatted();
485 }
486 return round( ( strlen( $this->input_css ) - strlen( $this->output_css_plain ) ) / strlen( $this->input_css ), 3 ) * 100;
487 }
488
489 /**
490 * Gets difference between the old and new code in bytes and prints the code if necessary.
491 *
492 * @since 1.1
493 *
494 * @return string Size difference.
495 */
496 public function get_diff(): string {
497 if ( ! $this->output_css_plain ) {
498 $this->formatted();
499 }
500
501 $diff = strlen( $this->output_css_plain ) - strlen( $this->input_css );
502
503 if ( $diff > 0 ) {
504 return '+' . $diff;
505 } elseif ( 0 === $diff ) {
506 return '+-' . $diff;
507 }
508
509 return (string) $diff;
510 }
511
512 /**
513 * Gets the size of either input or output CSS in kilobytes (KB).
514 *
515 * @since 1.0
516 *
517 * @param string $loc Optional. Location of the CSS.
518 * @return double Size of the CSS.
519 */
520 public function size( string $loc = 'output' ): float {
521 if ( 'output' === $loc && ! $this->output_css ) {
522 $this->formatted();
523 }
524
525 if ( 'input' === $loc ) {
526 return strlen( $this->input_css ) / 1000;
527 } else {
528 return strlen( $this->output_css_plain ) / 1000;
529 }
530 }
531
532 } // class TablePress_CSSTidy_Print
533