PluginProbe
TablePress – Tables in WordPress made easy / 3.3.2
TablePress – Tables in WordPress made easy v3.3.2
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 3.3.2, at libraries/csstidy/class.csstidy_print.php

411 lines 12.3 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 */
29 public \TablePress_CSSTidy $parser;
30
31 /**
32 * The parsed CSS.
33 *
34 * @since 1.0.0
35 */
36 public array $css = array();
37
38 /**
39 * The output templates.
40 *
41 * @since 1.0.0
42 */
43 public array $template = array();
44
45 /**
46 * The raw parsed CSS.
47 *
48 * @since 1.0.0
49 */
50 public array $tokens = array();
51
52 /**
53 * The CSS charset.
54 *
55 * @since 1.0.0
56 */
57 public string $charset = '';
58
59 /**
60 * All @import URLs.
61 *
62 * @since 1.0.0
63 */
64 public array $import = array();
65
66 /**
67 * The namespace.
68 *
69 * @since 1.0.0
70 */
71 public string $namespace = '';
72
73 /**
74 * Saves the input CSS string.
75 *
76 * @since 1.0.0
77 */
78 public string $input_css = '';
79
80 /**
81 * Saves the formatted CSS string.
82 *
83 * @since 1.0.0
84 */
85 public string $output_css = '';
86
87 /**
88 * Saves the formatted CSS string (plain text).
89 *
90 * @since 1.0.0
91 */
92 public string $output_css_plain = '';
93
94 /**
95 * Constructor.
96 *
97 * @since 1.0
98 *
99 * @param TablePress_CSSTidy $csstidy Instance of the TablePress_CSSTidy class.
100 */
101 public function __construct( TablePress_CSSTidy $csstidy ) {
102 $this->parser = $csstidy;
103 $this->css = &$csstidy->css;
104 $this->template = &$csstidy->template;
105 $this->tokens = &$csstidy->tokens;
106 $this->charset = &$csstidy->charset;
107 $this->import = &$csstidy->import;
108 $this->namespace = &$csstidy->namespace;
109 }
110
111 /**
112 * Resets output_css and output_css_plain (new CSS code).
113 *
114 * @since 1.0
115 */
116 public function _reset(): void {
117 $this->output_css = '';
118 $this->output_css_plain = '';
119 }
120
121 /**
122 * Returns the CSS code as plain text.
123 *
124 * @since 1.0
125 *
126 * @param string $default_media Optional. Default @media to add to selectors without any @media.
127 * @return string Plain CSS.
128 */
129 public function plain( string $default_media = '' ): string {
130 $this->_print( true, $default_media );
131 return $this->output_css_plain;
132 }
133
134 /**
135 * Returns the formatted CSS Code and saves it into $this->output_css and $this->output_css_plain.
136 *
137 * @since 1.0
138 *
139 * @param bool $plain Optional. Plain text or not.
140 * @param string $default_media Optional. Default @media to add to selectors without any @media.
141 */
142 protected function _print( bool $plain = false, string $default_media = '' ): void {
143 if ( $this->output_css && $this->output_css_plain ) {
144 return;
145 }
146
147 $output = '';
148 if ( ! $this->parser->get_cfg( 'preserve_css' ) ) {
149 $this->_convert_raw_css( $default_media );
150 }
151
152 $template = &$this->template;
153
154 if ( $plain ) {
155 $template = array_map( 'strip_tags', $template );
156 }
157
158 if ( $this->parser->get_cfg( 'timestamp' ) ) {
159 array_unshift( $this->tokens, array( TablePress_CSSTidy::COMMENT, ' CSSTidy: ' . gmdate( 'r' ) . ' ' ) );
160 }
161
162 if ( ! empty( $this->charset ) ) {
163 $output .= $template[0] . '@charset ' . $template[5] . $this->charset . $template[6] . $template[13];
164 }
165
166 if ( ! empty( $this->import ) ) {
167 for ( $i = 0, $size = count( $this->import ); $i < $size; $i++ ) {
168 $import_components = explode( ' ', $this->import[ $i ] );
169 if ( str_starts_with( $import_components[0], 'url(' ) && str_ends_with( $import_components[0], ')' ) ) {
170 $import_components[0] = '\'' . trim( substr( $import_components[0], 4, -1 ), "'\"" ) . '\'';
171 $this->import[ $i ] = implode( ' ', $import_components );
172 $this->parser->log( 'Optimised @import : Removed "url("', 'Information' );
173 } elseif ( ! preg_match( '/^".+"$/', $this->import[ $i ] ) ) {
174 // Fixes a bug for @import ".." instead of the expected @import url("..").
175 // If it comes in due to @import ".." the "" will be missing and the output will become @import .. (which is an error).
176 $this->import[ $i ] = '"' . $this->import[ $i ] . '"';
177 }
178 $output .= $template[0] . '@import ' . $template[5] . $this->import[ $i ] . $template[6] . $template[13];
179 }
180 }
181
182 if ( ! empty( $this->namespace ) ) {
183 if ( false !== ( $p = strpos( $this->namespace, 'url(' ) ) && str_ends_with( $this->namespace, ')' ) ) { // phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.Found,Squiz.PHP.DisallowMultipleAssignments.FoundInControlStructure
184 $this->namespace = substr_replace( $this->namespace, '"', $p, 4 );
185 $this->namespace = substr( $this->namespace, 0, -1 ) . '"';
186 $this->parser->log( 'Optimised @namespace : Removed "url("', 'Information' );
187 }
188 $output .= $template[0] . '@namespace ' . $template[5] . $this->namespace . $template[6] . $template[13];
189 }
190
191 $in_at_out = array();
192 $out = &$output;
193 $indent_level = 0;
194
195 foreach ( $this->tokens as $key => $token ) {
196 switch ( $token[0] ) {
197 case TablePress_CSSTidy::AT_START:
198 if ( $this->parser->get_cfg( 'preserve_css' ) ) {
199 $token[1] = str_replace( ',', ",\n", $token[1] );
200 }
201 $out .= $template[0] . $this->_htmlsp( $token[1], $plain ) . $template[1];
202 ++$indent_level;
203 if ( ! isset( $in_at_out[ $indent_level ] ) ) {
204 $in_at_out[ $indent_level ] = '';
205 }
206 $out = &$in_at_out[ $indent_level ];
207 break;
208 case TablePress_CSSTidy::SEL_START:
209 if ( $this->parser->get_cfg( 'lowercase_s' ) ) {
210 $token[1] = strtolower( $token[1] );
211 }
212 if ( $this->parser->get_cfg( 'preserve_css' ) ) {
213 $token[1] = str_replace( ',', ",\n", $token[1] );
214 }
215 $out .= ( '' !== $token[1] && '@' !== $token[1][0] ) ? $template[2] . $this->_htmlsp( $token[1], $plain ) : $template[0] . $this->_htmlsp( $token[1], $plain );
216 $out .= $template[3];
217 break;
218 case TablePress_CSSTidy::PROPERTY:
219 if ( 2 === $this->parser->get_cfg( 'case_properties' ) ) {
220 $token[1] = strtoupper( $token[1] );
221 } elseif ( 1 === $this->parser->get_cfg( 'case_properties' ) ) {
222 $token[1] = strtolower( $token[1] );
223 }
224 $out .= $template[4] . $this->_htmlsp( $token[1], $plain ) . ':' . $template[5];
225 break;
226 case TablePress_CSSTidy::VALUE:
227 $out .= $this->_htmlsp( $token[1], $plain );
228 if ( TablePress_CSSTidy::SEL_END === $this->_seeknocomment( $key, 1 ) && $this->parser->get_cfg( 'remove_last_;' ) ) {
229 $out .= str_replace( ';', '', $template[6] );
230 } else {
231 $out .= $template[6];
232 }
233 if ( $this->parser->get_cfg( 'preserve_css' ) ) {
234 $out .= ( TablePress_CSSTidy::COMMENT === $this->tokens[ $key + 1 ][0] ) ? ' ' : "\n";
235 }
236 break;
237 case TablePress_CSSTidy::SEL_END:
238 $out .= $template[7];
239 if ( TablePress_CSSTidy::AT_END !== $this->_seeknocomment( $key, 1 ) ) {
240 $out .= $template[8];
241 }
242 break;
243 case TablePress_CSSTidy::AT_END:
244 if ( strlen( $template[10] ) ) {
245 // Indent the block we are closing.
246 $out = str_replace( "\n\n", "\r\n", $out ); // Don't fill empty lines.
247 $out = str_replace( "\n", "\n" . $template[10], $out );
248 $out = str_replace( "\r\n", "\n\n", $out );
249 }
250 if ( $indent_level > 1 ) {
251 $out = &$in_at_out[ $indent_level - 1 ];
252 } else {
253 $out = &$output;
254 }
255 $out .= $template[10];
256 if ( isset( $in_at_out[ $indent_level ] ) ) {
257 $out .= $in_at_out[ $indent_level ];
258 }
259 if ( TablePress_CSSTidy::AT_END !== $this->_seeknocomment( $key, 1 ) ) {
260 $out .= $template[9];
261 } else {
262 $out .= rtrim( $template[9] );
263 }
264 unset( $in_at_out[ $indent_level ] );
265 --$indent_level;
266 break;
267 case TablePress_CSSTidy::IMPORTANT_COMMENT:
268 case TablePress_CSSTidy::COMMENT:
269 $out .= $template[11] . '/*' . $this->_htmlsp( $token[1], $plain ) . '*/' . $template[12];
270 break;
271 }
272 }
273
274 if ( ! $this->parser->get_cfg( 'preserve_css' ) ) {
275 $output = str_replace( ' !important', '!important', $output );
276 }
277
278 $output = trim( $output );
279
280 if ( ! $plain ) {
281 $this->output_css = $output;
282 $this->_print( true );
283 } else {
284 // If using spaces in the template, don't want these to appear in the plain output.
285 $this->output_css_plain = str_replace( '&#160;', '', $output );
286 }
287 }
288
289 /**
290 * Gets the next token type which is $move away from $key, excluding comments.
291 *
292 * @since 1.0
293 *
294 * @param int $key Current position.
295 * @param int $move Move this far.
296 * @return mixed A token type.
297 */
298 protected function _seeknocomment( int $key, int $move ) /* : mixed */ {
299 $go = ( $move > 0 ) ? 1 : -1;
300 for ( $i = $key + 1; abs( $key - $i ) - 1 < abs( $move ); $i += $go ) { // phpcs:ignore Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed
301 if ( ! isset( $this->tokens[ $i ] ) ) {
302 return;
303 }
304 if ( TablePress_CSSTidy::COMMENT === $this->tokens[ $i ][0] ) {
305 ++$move;
306 continue;
307 }
308 return $this->tokens[ $i ][0];
309 }
310 }
311
312 /**
313 * Converts $this->css array to a raw array ($this->tokens).
314 *
315 * @since 1.0.0
316 *
317 * @param string $default_media Optional. Default @media to add to selectors without any @media.
318 */
319 protected function _convert_raw_css( string $default_media = '' ): void {
320 $this->tokens = array();
321 $sort_selectors = $this->parser->get_cfg( 'sort_selectors' );
322 $sort_properties = $this->parser->get_cfg( 'sort_properties' );
323
324 // Important comment section?
325 if ( isset( $this->css['!'] ) ) {
326 $this->parser->_add_token( TablePress_CSSTidy::IMPORTANT_COMMENT, rtrim( $this->css['!'] ), true );
327 unset( $this->css['!'] );
328 }
329
330 foreach ( $this->css as $medium => $val ) {
331 if ( $sort_selectors ) {
332 ksort( $val );
333 }
334 if ( (int) $medium < TablePress_CSSTidy::DEFAULT_AT ) {
335 // An empty medium (containing @font-face or other @) produces no container.
336 if ( strlen( trim( $medium ) ) ) {
337 $parts_to_open = explode( '{', $medium );
338 foreach ( $parts_to_open as $part ) {
339 $this->parser->_add_token( TablePress_CSSTidy::AT_START, $part, true );
340 }
341 }
342 } elseif ( $default_media ) {
343 $this->parser->_add_token( TablePress_CSSTidy::AT_START, $default_media, true );
344 }
345
346 foreach ( $val as $selector => $vali ) {
347 if ( $sort_properties ) {
348 ksort( $vali );
349 }
350 $this->parser->_add_token( TablePress_CSSTidy::SEL_START, $selector, true );
351
352 $invalid = array(
353 '*' => array(), // IE7 hacks first.
354 '_' => array(), // IE6 hacks.
355 '/' => array(), // IE6 hacks.
356 '-' => array(), // IE6 hacks.
357 );
358 foreach ( $vali as $property => $valj ) {
359 if ( ! str_starts_with( $property, '//' ) ) {
360 $matches = array();
361 if ( $sort_properties && preg_match( '/^(\*|_|\/|-)(?!(ms|moz|o\b|xv|atsc|wap|khtml|webkit|ah|hp|ro|rim|tc)-)/', $property, $matches ) ) {
362 $invalid[ $matches[1] ][ $property ] = $valj;
363 } else {
364 $this->parser->_add_token( TablePress_CSSTidy::PROPERTY, $property, true );
365 $this->parser->_add_token( TablePress_CSSTidy::VALUE, $valj, true );
366 }
367 }
368 }
369 foreach ( $invalid as $prefix => $props ) {
370 foreach ( $props as $property => $valj ) {
371 $this->parser->_add_token( TablePress_CSSTidy::PROPERTY, $property, true );
372 $this->parser->_add_token( TablePress_CSSTidy::VALUE, $valj, true );
373 }
374 }
375 $this->parser->_add_token( TablePress_CSSTidy::SEL_END, $selector, true );
376 }
377
378 if ( (int) $medium < TablePress_CSSTidy::DEFAULT_AT ) {
379 // An empty medium (containing @font-face or other @) produces no container.
380 if ( strlen( trim( $medium ) ) ) {
381 $parts_to_close = explode( '{', $medium );
382 foreach ( array_reverse( $parts_to_close ) as $part ) {
383 $this->parser->_add_token( TablePress_CSSTidy::AT_END, $part, true );
384 }
385 }
386 } elseif ( $default_media ) {
387 $this->parser->_add_token( TablePress_CSSTidy::AT_END, $default_media, true );
388 }
389 }
390 }
391
392 /**
393 * Same as htmlspecialchars, only that chars are not replaced if $plain is true. This makes print_code() cleaner.
394 *
395 * @since 1.0
396 *
397 * @see CSSTidy_print::_print()
398 *
399 * @param string $a_string String.
400 * @param bool $plain Whether to print plain.
401 * @return string Original string or htmlspecialchars() version.
402 */
403 protected function _htmlsp( string $a_string, bool $plain ): string {
404 if ( ! $plain ) {
405 return htmlspecialchars( $a_string, ENT_QUOTES, 'utf-8' );
406 }
407 return $a_string;
408 }
409
410 } // class TablePress_CSSTidy_Print
411