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 / classes / class-css.php

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

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