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

418 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 * @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 and saves it into $this->output_css and $this->output_css_plain.
146 *
147 * @since 1.0
148 *
149 * @param bool $plain Optional. Plain text or not.
150 * @param string $default_media Optional. Default @media to add to selectors without any @media.
151 */
152 protected function _print( bool $plain = false, string $default_media = '' ): void {
153 if ( $this->output_css && $this->output_css_plain ) {
154 return;
155 }
156
157 $output = '';
158 if ( ! $this->parser->get_cfg( 'preserve_css' ) ) {
159 $this->_convert_raw_css( $default_media );
160 }
161
162 $template = &$this->template;
163
164 if ( $plain ) {
165 $template = array_map( 'strip_tags', $template );
166 }
167
168 if ( $this->parser->get_cfg( 'timestamp' ) ) {
169 array_unshift( $this->tokens, array( TablePress_CSSTidy::COMMENT, ' CSSTidy: ' . gmdate( 'r' ) . ' ' ) );
170 }
171
172 if ( ! empty( $this->charset ) ) {
173 $output .= $template[0] . '@charset ' . $template[5] . $this->charset . $template[6] . $template[13];
174 }
175
176 if ( ! empty( $this->import ) ) {
177 for ( $i = 0, $size = count( $this->import ); $i < $size; $i++ ) {
178 $import_components = explode( ' ', $this->import[ $i ] );
179 if ( str_starts_with( $import_components[0], 'url(' ) && str_ends_with( $import_components[0], ')' ) ) {
180 $import_components[0] = '\'' . trim( substr( $import_components[0], 4, -1 ), "'\"" ) . '\'';
181 $this->import[ $i ] = implode( ' ', $import_components );
182 $this->parser->log( 'Optimised @import : Removed "url("', 'Information' );
183 } elseif ( ! preg_match( '/^".+"$/', $this->import[ $i ] ) ) {
184 // Fixes a bug for @import ".." instead of the expected @import url("..").
185 // If it comes in due to @import ".." the "" will be missing and the output will become @import .. (which is an error).
186 $this->import[ $i ] = '"' . $this->import[ $i ] . '"';
187 }
188 $output .= $template[0] . '@import ' . $template[5] . $this->import[ $i ] . $template[6] . $template[13];
189 }
190 }
191
192 if ( ! empty( $this->namespace ) ) {
193 if ( false !== ( $p = strpos( $this->namespace, 'url(' ) ) && str_ends_with( $this->namespace, ')' ) ) { // phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.Found,Squiz.PHP.DisallowMultipleAssignments.FoundInControlStructure
194 $this->namespace = substr_replace( $this->namespace, '"', $p, 4 );
195 $this->namespace = substr( $this->namespace, 0, -1 ) . '"';
196 $this->parser->log( 'Optimised @namespace : Removed "url("', 'Information' );
197 }
198 $output .= $template[0] . '@namespace ' . $template[5] . $this->namespace . $template[6] . $template[13];
199 }
200
201 $in_at_out = array();
202 $out = &$output;
203 $indent_level = 0;
204
205 foreach ( $this->tokens as $key => $token ) {
206 switch ( $token[0] ) {
207 case TablePress_CSSTidy::AT_START:
208 if ( $this->parser->get_cfg( 'preserve_css' ) ) {
209 $token[1] = str_replace( ',', ",\n", $token[1] );
210 }
211 $out .= $template[0] . $this->_htmlsp( $token[1], $plain ) . $template[1];
212 ++$indent_level;
213 if ( ! isset( $in_at_out[ $indent_level ] ) ) {
214 $in_at_out[ $indent_level ] = '';
215 }
216 $out = &$in_at_out[ $indent_level ];
217 break;
218 case TablePress_CSSTidy::SEL_START:
219 if ( $this->parser->get_cfg( 'lowercase_s' ) ) {
220 $token[1] = strtolower( $token[1] );
221 }
222 if ( $this->parser->get_cfg( 'preserve_css' ) ) {
223 $token[1] = str_replace( ',', ",\n", $token[1] );
224 }
225 $out .= ( '@' !== $token[1][0] ) ? $template[2] . $this->_htmlsp( $token[1], $plain ) : $template[0] . $this->_htmlsp( $token[1], $plain );
226 $out .= $template[3];
227 break;
228 case TablePress_CSSTidy::PROPERTY:
229 if ( 2 === $this->parser->get_cfg( 'case_properties' ) ) {
230 $token[1] = strtoupper( $token[1] );
231 } elseif ( 1 === $this->parser->get_cfg( 'case_properties' ) ) {
232 $token[1] = strtolower( $token[1] );
233 }
234 $out .= $template[4] . $this->_htmlsp( $token[1], $plain ) . ':' . $template[5];
235 break;
236 case TablePress_CSSTidy::VALUE:
237 $out .= $this->_htmlsp( $token[1], $plain );
238 if ( TablePress_CSSTidy::SEL_END === $this->_seeknocomment( $key, 1 ) && $this->parser->get_cfg( 'remove_last_;' ) ) {
239 $out .= str_replace( ';', '', $template[6] );
240 } else {
241 $out .= $template[6];
242 }
243 if ( $this->parser->get_cfg( 'preserve_css' ) ) {
244 $out .= ( TablePress_CSSTidy::COMMENT === $this->tokens[ $key + 1 ][0] ) ? ' ' : "\n";
245 }
246 break;
247 case TablePress_CSSTidy::SEL_END:
248 $out .= $template[7];
249 if ( TablePress_CSSTidy::AT_END !== $this->_seeknocomment( $key, 1 ) ) {
250 $out .= $template[8];
251 }
252 break;
253 case TablePress_CSSTidy::AT_END:
254 if ( strlen( $template[10] ) ) {
255 // Indent the block we are closing.
256 $out = str_replace( "\n\n", "\r\n", $out ); // Don't fill empty lines.
257 $out = str_replace( "\n", "\n" . $template[10], $out );
258 $out = str_replace( "\r\n", "\n\n", $out );
259 }
260 if ( $indent_level > 1 ) {
261 $out = &$in_at_out[ $indent_level - 1 ];
262 } else {
263 $out = &$output;
264 }
265 $out .= $template[10] . $in_at_out[ $indent_level ];
266 if ( TablePress_CSSTidy::AT_END !== $this->_seeknocomment( $key, 1 ) ) {
267 $out .= $template[9];
268 } else {
269 $out .= rtrim( $template[9] );
270 }
271 unset( $in_at_out[ $indent_level ] );
272 --$indent_level;
273 break;
274 case TablePress_CSSTidy::IMPORTANT_COMMENT:
275 case TablePress_CSSTidy::COMMENT:
276 $out .= $template[11] . '/*' . $this->_htmlsp( $token[1], $plain ) . '*/' . $template[12];
277 break;
278 }
279 }
280
281 if ( ! $this->parser->get_cfg( 'preserve_css' ) ) {
282 $output = str_replace( ' !important', '!important', $output );
283 }
284
285 $output = trim( $output );
286
287 if ( ! $plain ) {
288 $this->output_css = $output;
289 $this->_print( true );
290 } else {
291 // If using spaces in the template, don't want these to appear in the plain output.
292 $this->output_css_plain = str_replace( '&#160;', '', $output );
293 }
294 }
295
296 /**
297 * Gets the next token type which is $move away from $key, excluding comments.
298 *
299 * @since 1.0
300 *
301 * @param int $key Current position.
302 * @param int $move Move this far.
303 * @return mixed A token type.
304 */
305 protected function _seeknocomment( int $key, int $move ) /* : mixed */ {
306 $go = ( $move > 0 ) ? 1 : -1;
307 for ( $i = $key + 1; abs( $key - $i ) - 1 < abs( $move ); $i += $go ) { // phpcs:ignore Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed
308 if ( ! isset( $this->tokens[ $i ] ) ) {
309 return;
310 }
311 if ( TablePress_CSSTidy::COMMENT === $this->tokens[ $i ][0] ) {
312 ++$move;
313 continue;
314 }
315 return $this->tokens[ $i ][0];
316 }
317 }
318
319 /**
320 * Converts $this->css array to a raw array ($this->tokens).
321 *
322 * @since 1.0.0
323 *
324 * @param string $default_media Optional. Default @media to add to selectors without any @media.
325 */
326 protected function _convert_raw_css( string $default_media = '' ): void {
327 $this->tokens = array();
328 $sort_selectors = $this->parser->get_cfg( 'sort_selectors' );
329 $sort_properties = $this->parser->get_cfg( 'sort_properties' );
330
331 // Important comment section?
332 if ( isset( $this->css['!'] ) ) {
333 $this->parser->_add_token( TablePress_CSSTidy::IMPORTANT_COMMENT, rtrim( $this->css['!'] ), true );
334 unset( $this->css['!'] );
335 }
336
337 foreach ( $this->css as $medium => $val ) {
338 if ( $sort_selectors ) {
339 ksort( $val );
340 }
341 if ( (int) $medium < TablePress_CSSTidy::DEFAULT_AT ) {
342 // An empty medium (containing @font-face or other @) produces no container.
343 if ( strlen( trim( $medium ) ) ) {
344 $parts_to_open = explode( '{', $medium );
345 foreach ( $parts_to_open as $part ) {
346 $this->parser->_add_token( TablePress_CSSTidy::AT_START, $part, true );
347 }
348 }
349 } elseif ( $default_media ) {
350 $this->parser->_add_token( TablePress_CSSTidy::AT_START, $default_media, true );
351 }
352
353 foreach ( $val as $selector => $vali ) {
354 if ( $sort_properties ) {
355 ksort( $vali );
356 }
357 $this->parser->_add_token( TablePress_CSSTidy::SEL_START, $selector, true );
358
359 $invalid = array(
360 '*' => array(), // IE7 hacks first.
361 '_' => array(), // IE6 hacks.
362 '/' => array(), // IE6 hacks.
363 '-' => array(), // IE6 hacks.
364 );
365 foreach ( $vali as $property => $valj ) {
366 if ( ! str_starts_with( $property, '//' ) ) {
367 $matches = array();
368 if ( $sort_properties && preg_match( '/^(\*|_|\/|-)(?!(ms|moz|o\b|xv|atsc|wap|khtml|webkit|ah|hp|ro|rim|tc)-)/', $property, $matches ) ) {
369 $invalid[ $matches[1] ][ $property ] = $valj;
370 } else {
371 $this->parser->_add_token( TablePress_CSSTidy::PROPERTY, $property, true );
372 $this->parser->_add_token( TablePress_CSSTidy::VALUE, $valj, true );
373 }
374 }
375 }
376 foreach ( $invalid as $prefix => $props ) {
377 foreach ( $props as $property => $valj ) {
378 $this->parser->_add_token( TablePress_CSSTidy::PROPERTY, $property, true );
379 $this->parser->_add_token( TablePress_CSSTidy::VALUE, $valj, true );
380 }
381 }
382 $this->parser->_add_token( TablePress_CSSTidy::SEL_END, $selector, true );
383 }
384
385 if ( (int) $medium < TablePress_CSSTidy::DEFAULT_AT ) {
386 // An empty medium (containing @font-face or other @) produces no container.
387 if ( strlen( trim( $medium ) ) ) {
388 $parts_to_close = explode( '{', $medium );
389 foreach ( array_reverse( $parts_to_close ) as $part ) {
390 $this->parser->_add_token( TablePress_CSSTidy::AT_END, $part, true );
391 }
392 }
393 } elseif ( $default_media ) {
394 $this->parser->_add_token( TablePress_CSSTidy::AT_END, $default_media, true );
395 }
396 }
397 }
398
399 /**
400 * Same as htmlspecialchars, only that chars are not replaced if $plain is true. This makes print_code() cleaner.
401 *
402 * @since 1.0
403 *
404 * @see CSSTidy_print::_print()
405 *
406 * @param string $a_string String.
407 * @param bool $plain Whether to print plain.
408 * @return string Original string or htmlspecialchars() version.
409 */
410 protected function _htmlsp( string $a_string, bool $plain ): string {
411 if ( ! $plain ) {
412 return htmlspecialchars( $a_string, ENT_QUOTES, 'utf-8' );
413 }
414 return $a_string;
415 }
416
417 } // class TablePress_CSSTidy_Print
418