PluginProbe
TablePress – Tables in WordPress made easy / 2.3
TablePress – Tables in WordPress made easy v2.3
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 2.4.4 All 44 releases
tablepress / classes / class-css.php

class-css.php in TablePress – Tables in WordPress made easy 2.3, at classes/class-css.php

455 lines 16.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * TablePress CSS Class
4 *
5 * @package TablePress
6 * @subpackage CSS
7 * @author Tobias Bäthge
8 * @since 1.1.0
9 */
10
11 // Prohibit direct script loading.
12 defined( 'ABSPATH' ) || die( 'No direct script access allowed!' );
13
14 /**
15 * TablePress CSS Class
16 *
17 * @package TablePress
18 * @subpackage CSS
19 * @author Tobias Bäthge
20 * @since 1.1.0
21 */
22 class TablePress_CSS {
23
24 /**
25 * Sanitize and tidy a string of CSS.
26 *
27 * @since 1.1.0
28 *
29 * @param string $css CSS code.
30 * @return string Sanitized and tidied CSS code.
31 */
32 public function sanitize_css( string $css ): string {
33 $csstidy = TablePress::load_class( 'TablePress_CSSTidy', 'class.csstidy.php', 'libraries/csstidy' );
34
35 // Sanitization and not just tidying for users without enough privileges.
36 if ( ! current_user_can( 'unfiltered_html' ) ) {
37 // Let "arrows" survive, otherwise this might be recognized as the beginning of an HTML tag and removed with other stuff behind it.
38 $css = str_replace( '<=', '&lt;=', $css );
39 // Remove all HTML tags.
40 $css = wp_kses( $css, 'strip' );
41 // KSES replaces single ">" with "&gt;", but ">" is valid in CSS selectors.
42 $css = str_replace( '&gt;', '>', $css );
43 // strip_tags again, because of the just added ">" (KSES for a second time would again bring the ">" problem).
44 $css = strip_tags( $css );
45 }
46
47 $csstidy->set_cfg( 'remove_bslash', false );
48 $csstidy->set_cfg( 'compress_colors', false );
49 $csstidy->set_cfg( 'compress_font-weight', false );
50 $csstidy->set_cfg( 'lowercase_s', false );
51 $csstidy->set_cfg( 'optimise_shorthands', false );
52 $csstidy->set_cfg( 'remove_last_;', false );
53 $csstidy->set_cfg( 'case_properties', false );
54 $csstidy->set_cfg( 'sort_properties', false );
55 $csstidy->set_cfg( 'sort_selectors', false );
56 $csstidy->set_cfg( 'discard_invalid_selectors', false );
57 $csstidy->set_cfg( 'discard_invalid_properties', true );
58 $csstidy->set_cfg( 'merge_selectors', false );
59 $csstidy->set_cfg( 'css_level', 'CSS3.0' );
60 $csstidy->set_cfg( 'preserve_css', true );
61 $csstidy->set_cfg( 'timestamp', false );
62 $csstidy->set_cfg( 'template', 'default' );
63
64 $csstidy->parse( $css );
65 return $csstidy->print->plain();
66 }
67
68 /**
69 * Minify a string of CSS code, that should have been sanitized/tidied before.
70 *
71 * @since 1.1.0
72 *
73 * @param string $css CSS code.
74 * @return string Minified CSS code.
75 */
76 public function minify_css( string $css ): string {
77 $csstidy = TablePress::load_class( 'TablePress_CSSTidy', 'class.csstidy.php', 'libraries/csstidy' );
78 $csstidy->set_cfg( 'remove_bslash', false );
79 $csstidy->set_cfg( 'compress_colors', true );
80 $csstidy->set_cfg( 'compress_font-weight', true );
81 $csstidy->set_cfg( 'lowercase_s', false );
82 $csstidy->set_cfg( 'optimise_shorthands', 1 );
83 $csstidy->set_cfg( 'remove_last_;', true );
84 $csstidy->set_cfg( 'case_properties', false );
85 $csstidy->set_cfg( 'sort_properties', false );
86 $csstidy->set_cfg( 'sort_selectors', false );
87 $csstidy->set_cfg( 'discard_invalid_selectors', false );
88 $csstidy->set_cfg( 'discard_invalid_properties', true );
89 $csstidy->set_cfg( 'merge_selectors', false );
90 $csstidy->set_cfg( 'css_level', 'CSS3.0' );
91 $csstidy->set_cfg( 'preserve_css', false );
92 $csstidy->set_cfg( 'timestamp', false );
93 $csstidy->set_cfg( 'template', 'highest' );
94
95 $csstidy->parse( $css );
96 return $csstidy->print->plain();
97 }
98
99 /**
100 * Get the location (file path or URL) of the "Custom CSS" file, depending on whether it's a Multisite install or not.
101 *
102 * @since 1.0.0
103 *
104 * @param string $type "normal" version, "minified" version, or "combined" (with TablePress Default CSS) version.
105 * @param string $location "path" or "url", for file path or URL.
106 * @return string Full file path or full URL for the "Custom CSS" file.
107 */
108 public function get_custom_css_location( string $type, string $location ): string {
109 switch ( $type ) {
110 case 'combined':
111 $file = 'tablepress-combined.min.css';
112 break;
113 case 'minified':
114 $file = 'tablepress-custom.min.css';
115 break;
116 case 'normal':
117 default:
118 $file = 'tablepress-custom.css';
119 break;
120 }
121
122 if ( is_multisite() ) {
123 // Multisite installation: Use /wp-content/uploads/sites/<ID>/.
124 $upload_location = wp_upload_dir();
125 } else {
126 // Singlesite installation: Use /wp-content/.
127 $upload_location = array(
128 'basedir' => WP_CONTENT_DIR,
129 'baseurl' => content_url(),
130 );
131 }
132
133 switch ( $location ) {
134 case 'url':
135 $url = set_url_scheme( $upload_location['baseurl'] . '/' . $file );
136 /**
137 * Filters the URL from which the "Custom CSS" file is loaded.
138 *
139 * @since 1.0.0
140 *
141 * @param string $url URL of the "Custom CSS" file.
142 * @param string $file File name of the "Custom CSS" file.
143 * @param string $type Type of the "Custom CSS" file ("normal", "minified", or "combined").
144 */
145 $url = apply_filters( 'tablepress_custom_css_url', $url, $file, $type );
146 return $url;
147 // break; // unreachable.
148 case 'path':
149 $path = $upload_location['basedir'] . '/' . $file;
150 /**
151 * Filters the file path on the server from which the "Custom CSS" file is loaded.
152 *
153 * @since 1.0.0
154 *
155 * @param string $path File path of the "Custom CSS" file.
156 * @param string $file File name of the "Custom CSS" file.
157 * @param string $type Type of the "Custom CSS" file ("normal", "minified", or "combined").
158 */
159 $path = apply_filters( 'tablepress_custom_css_file_name', $path, $file, $type );
160 return $path;
161 // break; // unreachable.
162 default:
163 // Return an empty string if no valid location was provided.
164 return '';
165 // break; // unreachable.
166 }
167 }
168
169 /**
170 * Load the contents of the file with the "Custom CSS".
171 *
172 * @since 1.0.0
173 *
174 * @param string $type Optional. Whether to load "normal" version or "minified" version. Default "normal".
175 * @return string|false Custom CSS on success, false on error.
176 */
177 public function load_custom_css_from_file( string $type = 'normal' ) /* : string|false */ {
178 $filename = $this->get_custom_css_location( $type, 'path' );
179 // Check if file name is valid (0 means yes).
180 if ( 0 !== validate_file( $filename ) ) {
181 return false;
182 }
183 if ( ! @is_file( $filename ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
184 return false;
185 }
186 if ( ! @is_readable( $filename ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
187 return false;
188 }
189 return file_get_contents( $filename );
190 }
191
192 /**
193 * Loads the contents of the file with the TablePress Default CSS,
194 * either for left-to-right (LTR) or right-to-left (RTL), in minified form.
195 *
196 * @since 1.1.0
197 * @since 2.0.0 Added the `$load_rtl` parameter.
198 *
199 * @param bool $load_rtl Optional. Whether to load LTR or RTL version. Default LTR.
200 * @return string|false TablePress Default CSS on success, false on error.
201 */
202 public function load_default_css_from_file( bool $load_rtl = false ) /* : string|false */ {
203 $rtl = $load_rtl ? '-rtl' : '';
204 $filename = TABLEPRESS_ABSPATH . "css/build/default{$rtl}.css";
205 // Check if file name is valid (0 means yes).
206 if ( 0 !== validate_file( $filename ) ) {
207 return false;
208 }
209 if ( ! @is_file( $filename ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
210 return false;
211 }
212 if ( ! @is_readable( $filename ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
213 return false;
214 }
215 return file_get_contents( $filename );
216 }
217
218 /**
219 * Try to save "Custom CSS" to a file (requires "direct" method in WP_Filesystem, or stored FTP credentials).
220 *
221 * @since 1.1.0
222 *
223 * @param string $custom_css_normal Custom CSS code to be saved.
224 * @param string $custom_css_minified Minified CSS code to be saved.
225 * @return bool True on success, false on failure.
226 */
227 public function save_custom_css_to_file( string $custom_css_normal, string $custom_css_minified ): bool {
228 /**
229 * Filters whether the "Custom CSS" code shall be saved to a file on the server.
230 *
231 * @since 1.1.0
232 *
233 * @param bool $save Whether to save the "Custom CSS" to a file. Default true.
234 */
235 if ( ! apply_filters( 'tablepress_save_custom_css_to_file', true ) ) {
236 return false;
237 }
238
239 // Start capturing the output, to later prevent it.
240 ob_start();
241 $credentials = request_filesystem_credentials( '', '', false, '', null, false );
242
243 /*
244 * Do we have credentials already? (Otherwise the form will have been rendered, which is not supported here.)
245 * Or, if we have credentials, are they valid?
246 */
247 if ( false === $credentials || ! WP_Filesystem( $credentials ) ) { // @phpstan-ignore-line
248 ob_end_clean();
249 return false;
250 }
251
252 // We have valid access to the filesystem now -> try to save the files.
253 return $this->_custom_css_save_helper( $custom_css_normal, $custom_css_minified );
254 }
255
256 /**
257 * Save "Custom CSS" to files, delete "Custom CSS" files, or return HTML for the credentials form.
258 *
259 * Only used from "Plugin Options" screen, save_custom_css_to_file() is used in cases where no form output/redirection is possible (e.g. during plugin updates).
260 *
261 * @since 1.0.0
262 *
263 * @param string $custom_css_normal Custom CSS code to be saved. If empty, files will be deleted.
264 * @param string $custom_css_minified Minified CSS code to be saved.
265 * @return bool|string True on success, false on failure, or string of HTML for the credentials form for the WP_Filesystem API, if necessary.
266 */
267 public function save_custom_css_to_file_plugin_options( string $custom_css_normal, string $custom_css_minified ) /* : bool|string */ {
268 /** This filter is documented in classes/class-css.php */
269 if ( ! apply_filters( 'tablepress_save_custom_css_to_file', true ) ) {
270 return false;
271 }
272
273 // Start capturing the output, to get HTML of the credentials form (if needed).
274 ob_start();
275 $credentials = request_filesystem_credentials( '', '', false, '', null, false );
276 // Do we have credentials already? Otherwise the form will have been rendered already.
277 if ( false === $credentials ) {
278 $form_data = ob_get_clean();
279 $form_data = str_replace( 'name="upgrade" id="upgrade" class="button"', 'name="upgrade" id="upgrade" class="button button-primary button-large"', $form_data ); // @phpstan-ignore-line
280 return $form_data;
281 }
282
283 // We have received credentials, but don't know if they are valid yet.
284 if ( ! WP_Filesystem( $credentials ) ) { // @phpstan-ignore-line
285 // Credentials failed, so ask again (with $error flag true).
286 request_filesystem_credentials( '', '', true, '', null, false );
287 $form_data = ob_get_clean();
288 $form_data = str_replace( 'name="upgrade" id="upgrade" class="button"', 'name="upgrade" id="upgrade" class="button button-primary button-large"', $form_data ); // @phpstan-ignore-line
289 return $form_data;
290 }
291
292 // We have valid access to the filesystem now -> try to save the files, or delete them if the "Custom CSS" is empty.
293 if ( '' !== $custom_css_normal ) {
294 return $this->_custom_css_save_helper( $custom_css_normal, $custom_css_minified );
295 } else {
296 return $this->_custom_css_delete_helper();
297 }
298 }
299
300 /**
301 * Save "Custom CSS" to files, if validated access to the WP_Filesystem exists.
302 *
303 * Helper function to prevent code duplication.
304 *
305 * @since 1.1.0
306 *
307 * @global WP_Filesystem_* $wp_filesystem WordPress file system abstraction object.
308 * @see save_custom_css_to_file()
309 * @see save_custom_css_to_file_plugin_options()
310 *
311 * @param string $custom_css_normal Custom CSS code to be saved.
312 * @param string $custom_css_minified Minified CSS code to be saved.
313 * @return bool True on success, false on failure.
314 */
315 protected function _custom_css_save_helper( string $custom_css_normal, string $custom_css_minified ): bool {
316 global $wp_filesystem;
317
318 /*
319 * WP_CONTENT_DIR and (FTP-)Content-Dir can be different (e.g. if FTP working dir is /).
320 * We need to account for that by replacing the path difference in the filename.
321 */
322 $path_difference = str_replace( $wp_filesystem->wp_content_dir(), '', trailingslashit( WP_CONTENT_DIR ) );
323
324 $css_types = array( 'normal', 'minified', 'combined' );
325
326 $default_css_minified = $this->load_default_css_from_file( false );
327 if ( false === $default_css_minified ) {
328 $default_css_minified = '';
329 } else {
330 // Change relative URLs to web font files to absolute URLs, as combining the CSS files and saving to another directory breaks the relative URLs.
331 $absolute_path = plugins_url( 'css/build/tablepress.', TABLEPRESS__FILE__ );
332 // Make the absolute URL protocol-relative to prevent mixed content warnings.
333 $absolute_path = str_replace( array( 'http:', 'https:' ), '', $absolute_path );
334 $default_css_minified = str_replace( 'url(tablepress.', 'url(' . $absolute_path, $default_css_minified );
335 }
336 $file_content = array(
337 'normal' => $custom_css_normal,
338 'minified' => $custom_css_minified,
339 'combined' => $default_css_minified . $custom_css_minified,
340 );
341
342 $total_result = true; // Whether all files were saved successfully.
343 foreach ( $css_types as $css_type ) {
344 $filename = $this->get_custom_css_location( $css_type, 'path' );
345 // Check if filename is valid (0 means yes).
346 if ( 0 !== validate_file( $filename ) ) {
347 $total_result = false;
348 continue;
349 }
350 if ( '' !== $path_difference ) {
351 $filename = str_replace( $path_difference, '', $filename );
352 }
353 $result = $wp_filesystem->put_contents( $filename, $file_content[ $css_type ], FS_CHMOD_FILE );
354 $total_result = ( $total_result && $result );
355 }
356
357 $this->_flush_caching_plugins_css_minify_caches();
358
359 return $total_result;
360 }
361
362 /**
363 * Delete the "Custom CSS" files, if possible.
364 *
365 * @since 1.0.0
366 *
367 * @return bool True on success, false on failure.
368 */
369 public function delete_custom_css_files(): bool {
370 // Start capturing the output, to later prevent it.
371 ob_start();
372 $credentials = request_filesystem_credentials( '', '', false, '', null, false );
373
374 /*
375 * Do we have credentials already? (Otherwise the form will have been rendered, which is not supported here.)
376 * Or, if we have credentials, are they valid?
377 */
378 if ( false === $credentials || ! WP_Filesystem( $credentials ) ) { // @phpstan-ignore-line
379 ob_end_clean();
380 return false;
381 }
382
383 // We have valid access to the filesystem now -> try to delete the files.
384 return $this->_custom_css_delete_helper();
385 }
386
387 /**
388 * Delete "Custom CSS" files, if validated access to the WP_Filesystem exists.
389 *
390 * Helper function to prevent code duplication
391 *
392 * @since 1.1.0
393 *
394 * @global WP_Filesystem_* $wp_filesystem WordPress file system abstraction object.
395 * @see delete_custom_css_files()
396 * @see save_custom_css_to_file_plugin_options()
397 *
398 * @return bool True on success, false on failure.
399 */
400 protected function _custom_css_delete_helper(): bool {
401 global $wp_filesystem;
402
403 /*
404 * WP_CONTENT_DIR and (FTP-)Content-Dir can be different (e.g. if FTP working dir is /).
405 * We need to account for that by replacing the path difference in the filename.
406 */
407 $path_difference = str_replace( $wp_filesystem->wp_content_dir(), '', trailingslashit( WP_CONTENT_DIR ) );
408
409 $css_types = array( 'normal', 'minified', 'combined' );
410 $total_result = true; // Whether all files were deleted successfully.
411 foreach ( $css_types as $css_type ) {
412 $filename = $this->get_custom_css_location( $css_type, 'path' );
413 // Check if filename is valid (0 means yes).
414 if ( 0 !== validate_file( $filename ) ) {
415 $total_result = false;
416 continue;
417 }
418 if ( '' !== $path_difference ) {
419 $filename = str_replace( $path_difference, '', $filename );
420 }
421 // We have valid access to the filesystem now -> try to delete the file.
422 if ( $wp_filesystem->exists( $filename ) ) {
423 $result = $wp_filesystem->delete( $filename );
424 $total_result = ( $total_result && $result );
425 }
426 }
427
428 $this->_flush_caching_plugins_css_minify_caches();
429
430 return $total_result;
431 }
432
433 /**
434 * Flush the CSS minification caches of common caching plugins.
435 *
436 * @since 1.4.0
437 */
438 protected function _flush_caching_plugins_css_minify_caches(): void {
439 /** This filter is documented in models/model-table.php */
440 if ( ! apply_filters( 'tablepress_flush_caching_plugins_caches', true ) ) {
441 return;
442 }
443
444 // W3 Total Cache.
445 if ( function_exists( 'w3tc_minify_flush' ) ) {
446 w3tc_minify_flush();
447 }
448 // WP Fastest Cache.
449 if ( isset( $GLOBALS['wp_fastest_cache'] ) && is_callable( array( $GLOBALS['wp_fastest_cache'], 'deleteCache' ) ) ) {
450 $GLOBALS['wp_fastest_cache']->deleteCache( true ); // @phpstan-ignore-line
451 }
452 }
453
454 } // class TablePress_CSS
455