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

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