PluginProbe
TablePress – Tables in WordPress made easy / 3.1.2
TablePress – Tables in WordPress made easy v3.1.2
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 3.1.2, at classes/class-css.php

452 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 // 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 $css = $csstidy->print->plain();
97
98 // Remove all CSS comments from the minified CSS code, as CSSTidy does not remove those inside a CSS selector.
99 return preg_replace( '!/\*[^*]*\*+([^/][^*]*\*+)*/\n?!', '', $css );
100 }
101
102 /**
103 * Get the location (file path or URL) of the "Custom CSS" file, depending on whether it's a Multisite install or not.
104 *
105 * @since 1.0.0
106 *
107 * @param string $type "normal" version, "minified" version, or "combined" (with TablePress Default CSS) version.
108 * @param string $location "path" or "url", for file path or URL.
109 * @return string Full file path or full URL for the "Custom CSS" file.
110 */
111 public function get_custom_css_location( string $type, string $location ): string {
112 switch ( $type ) {
113 case 'combined':
114 $file = 'tablepress-combined.min.css';
115 break;
116 case 'minified':
117 $file = 'tablepress-custom.min.css';
118 break;
119 case 'normal':
120 default:
121 $file = 'tablepress-custom.css';
122 break;
123 }
124
125 if ( is_multisite() ) {
126 // Multisite installation: Use /wp-content/uploads/sites/<ID>/.
127 $upload_location = wp_upload_dir();
128 } else {
129 // Singlesite installation: Use /wp-content/.
130 $upload_location = array(
131 'basedir' => WP_CONTENT_DIR,
132 'baseurl' => content_url(),
133 );
134 }
135
136 switch ( $location ) {
137 case 'url':
138 $url = set_url_scheme( $upload_location['baseurl'] . '/' . $file );
139 /**
140 * Filters the URL from which the "Custom CSS" file is loaded.
141 *
142 * @since 1.0.0
143 *
144 * @param string $url URL of the "Custom CSS" file.
145 * @param string $file File name of the "Custom CSS" file.
146 * @param string $type Type of the "Custom CSS" file ("normal", "minified", or "combined").
147 */
148 $url = apply_filters( 'tablepress_custom_css_url', $url, $file, $type );
149 return $url;
150 // break; // unreachable.
151 case 'path':
152 $path = $upload_location['basedir'] . '/' . $file;
153 /**
154 * Filters the file path on the server from which the "Custom CSS" file is loaded.
155 *
156 * @since 1.0.0
157 *
158 * @param string $path File path of the "Custom CSS" file.
159 * @param string $file File name of the "Custom CSS" file.
160 * @param string $type Type of the "Custom CSS" file ("normal", "minified", or "combined").
161 */
162 $path = apply_filters( 'tablepress_custom_css_file_name', $path, $file, $type );
163 return $path;
164 // break; // unreachable.
165 default:
166 // Return an empty string if no valid location was provided.
167 return '';
168 // break; // unreachable.
169 }
170 }
171
172 /**
173 * Load the contents of the file with the "Custom CSS".
174 *
175 * @since 1.0.0
176 *
177 * @param string $type Optional. Whether to load "normal" version or "minified" version. Default "normal".
178 * @return string|false Custom CSS on success, false on error.
179 */
180 public function load_custom_css_from_file( string $type = 'normal' ) /* : string|false */ {
181 $filename = $this->get_custom_css_location( $type, 'path' );
182 // Check if file name is valid (0 means yes).
183 if ( 0 !== validate_file( $filename ) ) {
184 return false;
185 }
186 if ( ! @is_file( $filename ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
187 return false;
188 }
189 if ( ! @is_readable( $filename ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
190 return false;
191 }
192 return file_get_contents( $filename );
193 }
194
195 /**
196 * Loads the contents of the file with the TablePress Default CSS,
197 * either for left-to-right (LTR) or right-to-left (RTL), in minified form.
198 *
199 * @since 1.1.0
200 * @since 2.0.0 Added the `$load_rtl` parameter.
201 *
202 * @param bool $load_rtl Optional. Whether to load LTR or RTL version. Default LTR.
203 * @return string|false TablePress Default CSS on success, false on error.
204 */
205 public function load_default_css_from_file( bool $load_rtl = false ) /* : string|false */ {
206 $rtl = $load_rtl ? '-rtl' : '';
207 $filename = TABLEPRESS_ABSPATH . "css/build/default{$rtl}.css";
208 // Check if file name is valid (0 means yes).
209 if ( 0 !== validate_file( $filename ) ) {
210 return false;
211 }
212 if ( ! @is_file( $filename ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
213 return false;
214 }
215 if ( ! @is_readable( $filename ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
216 return false;
217 }
218 return file_get_contents( $filename );
219 }
220
221 /**
222 * Try to save "Custom CSS" to a file (requires "direct" method in WP_Filesystem, or stored FTP credentials).
223 *
224 * @since 1.1.0
225 *
226 * @param string $custom_css_normal Custom CSS code to be saved.
227 * @param string $custom_css_minified Minified CSS code to be saved.
228 * @return bool True on success, false on failure.
229 */
230 public function save_custom_css_to_file( string $custom_css_normal, string $custom_css_minified ): bool {
231 /**
232 * Filters whether the "Custom CSS" code shall be saved to a file on the server.
233 *
234 * @since 1.1.0
235 *
236 * @param bool $save Whether to save the "Custom CSS" to a file. Default true.
237 */
238 if ( ! apply_filters( 'tablepress_save_custom_css_to_file', true ) ) {
239 return false;
240 }
241
242 // Start capturing the output, to later prevent it.
243 ob_start();
244 $credentials = request_filesystem_credentials( '', '', false, '', null, false );
245
246 /*
247 * Do we have credentials already? (Otherwise the form will have been rendered, which is not supported here.)
248 * Or, if we have credentials, are they valid?
249 */
250 if ( false === $credentials || ! WP_Filesystem( $credentials ) ) { // @phpstan-ignore argument.type
251 ob_end_clean();
252 return false;
253 }
254
255 // We have valid access to the filesystem now -> try to save the files.
256 return $this->_custom_css_save_helper( $custom_css_normal, $custom_css_minified );
257 }
258
259 /**
260 * Save "Custom CSS" to files, delete "Custom CSS" files, or return HTML for the credentials form.
261 *
262 * 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).
263 *
264 * @since 1.0.0
265 *
266 * @param string $custom_css_normal Custom CSS code to be saved. If empty, files will be deleted.
267 * @param string $custom_css_minified Minified CSS code to be saved.
268 * @return bool|string True on success, false on failure, or string of HTML for the credentials form for the WP_Filesystem API, if necessary.
269 */
270 public function save_custom_css_to_file_plugin_options( string $custom_css_normal, string $custom_css_minified ) /* : bool|string */ {
271 /** This filter is documented in classes/class-css.php */
272 if ( ! apply_filters( 'tablepress_save_custom_css_to_file', true ) ) {
273 return false;
274 }
275
276 // Start capturing the output, to get HTML of the credentials form (if needed).
277 ob_start();
278 $credentials = request_filesystem_credentials( '', '', false, '', null, false );
279 // Do we have credentials already? Otherwise the form will have been rendered already.
280 if ( false === $credentials ) {
281 $form_data = ob_get_clean();
282 $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
283 return $form_data;
284 }
285
286 // We have received credentials, but don't know if they are valid yet.
287 if ( ! WP_Filesystem( $credentials ) ) { // @phpstan-ignore argument.type
288 // Credentials failed, so ask again (with $error flag true).
289 request_filesystem_credentials( '', '', true, '', null, false );
290 $form_data = ob_get_clean();
291 $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
292 return $form_data;
293 }
294
295 // We have valid access to the filesystem now -> try to save the files, or delete them if the "Custom CSS" is empty.
296 if ( '' !== $custom_css_normal ) {
297 return $this->_custom_css_save_helper( $custom_css_normal, $custom_css_minified );
298 } else {
299 return $this->_custom_css_delete_helper();
300 }
301 }
302
303 /**
304 * Save "Custom CSS" to files, if validated access to the WP_Filesystem exists.
305 *
306 * Helper function to prevent code duplication.
307 *
308 * @since 1.1.0
309 *
310 * @global WP_Filesystem_* $wp_filesystem WordPress file system abstraction object.
311 * @see save_custom_css_to_file()
312 * @see save_custom_css_to_file_plugin_options()
313 *
314 * @param string $custom_css_normal Custom CSS code to be saved.
315 * @param string $custom_css_minified Minified CSS code to be saved.
316 * @return bool True on success, false on failure.
317 */
318 protected function _custom_css_save_helper( string $custom_css_normal, string $custom_css_minified ): bool {
319 global $wp_filesystem;
320
321 /*
322 * WP_CONTENT_DIR and (FTP-)Content-Dir can be different (e.g. if FTP working dir is /).
323 * We need to account for that by replacing the path difference in the filename.
324 */
325 $path_difference = str_replace( $wp_filesystem->wp_content_dir(), '', trailingslashit( WP_CONTENT_DIR ) );
326
327 $css_types = array( 'normal', 'minified', 'combined' );
328
329 $default_css_minified = $this->load_default_css_from_file( false );
330 if ( false === $default_css_minified ) {
331 $default_css_minified = '';
332 }
333 $file_content = array(
334 'normal' => $custom_css_normal,
335 'minified' => $custom_css_minified,
336 'combined' => $default_css_minified . $custom_css_minified,
337 );
338
339 $total_result = true; // Whether all files were saved successfully.
340 foreach ( $css_types as $css_type ) {
341 $filename = $this->get_custom_css_location( $css_type, 'path' );
342 // Check if filename is valid (0 means yes).
343 if ( 0 !== validate_file( $filename ) ) {
344 $total_result = false;
345 continue;
346 }
347 if ( '' !== $path_difference ) {
348 $filename = str_replace( $path_difference, '', $filename );
349 }
350 $result = $wp_filesystem->put_contents( $filename, $file_content[ $css_type ], FS_CHMOD_FILE );
351 $total_result = ( $total_result && $result );
352 }
353
354 $this->_flush_caching_plugins_css_minify_caches();
355
356 return $total_result;
357 }
358
359 /**
360 * Delete the "Custom CSS" files, if possible.
361 *
362 * @since 1.0.0
363 *
364 * @return bool True on success, false on failure.
365 */
366 public function delete_custom_css_files(): bool {
367 // Start capturing the output, to later prevent it.
368 ob_start();
369 $credentials = request_filesystem_credentials( '', '', false, '', null, false );
370
371 /*
372 * Do we have credentials already? (Otherwise the form will have been rendered, which is not supported here.)
373 * Or, if we have credentials, are they valid?
374 */
375 if ( false === $credentials || ! WP_Filesystem( $credentials ) ) { // @phpstan-ignore argument.type
376 ob_end_clean();
377 return false;
378 }
379
380 // We have valid access to the filesystem now -> try to delete the files.
381 return $this->_custom_css_delete_helper();
382 }
383
384 /**
385 * Delete "Custom CSS" files, if validated access to the WP_Filesystem exists.
386 *
387 * Helper function to prevent code duplication
388 *
389 * @since 1.1.0
390 *
391 * @global WP_Filesystem_* $wp_filesystem WordPress file system abstraction object.
392 * @see delete_custom_css_files()
393 * @see save_custom_css_to_file_plugin_options()
394 *
395 * @return bool True on success, false on failure.
396 */
397 protected function _custom_css_delete_helper(): bool {
398 global $wp_filesystem;
399
400 /*
401 * WP_CONTENT_DIR and (FTP-)Content-Dir can be different (e.g. if FTP working dir is /).
402 * We need to account for that by replacing the path difference in the filename.
403 */
404 $path_difference = str_replace( $wp_filesystem->wp_content_dir(), '', trailingslashit( WP_CONTENT_DIR ) );
405
406 $css_types = array( 'normal', 'minified', 'combined' );
407 $total_result = true; // Whether all files were deleted successfully.
408 foreach ( $css_types as $css_type ) {
409 $filename = $this->get_custom_css_location( $css_type, 'path' );
410 // Check if filename is valid (0 means yes).
411 if ( 0 !== validate_file( $filename ) ) {
412 $total_result = false;
413 continue;
414 }
415 if ( '' !== $path_difference ) {
416 $filename = str_replace( $path_difference, '', $filename );
417 }
418 // We have valid access to the filesystem now -> try to delete the file.
419 if ( $wp_filesystem->exists( $filename ) ) {
420 $result = $wp_filesystem->delete( $filename );
421 $total_result = ( $total_result && $result );
422 }
423 }
424
425 $this->_flush_caching_plugins_css_minify_caches();
426
427 return $total_result;
428 }
429
430 /**
431 * Flush the CSS minification caches of common caching plugins.
432 *
433 * @since 1.4.0
434 */
435 protected function _flush_caching_plugins_css_minify_caches(): void {
436 /** This filter is documented in models/model-table.php */
437 if ( ! apply_filters( 'tablepress_flush_caching_plugins_caches', true ) ) {
438 return;
439 }
440
441 // W3 Total Cache.
442 if ( function_exists( 'w3tc_minify_flush' ) ) {
443 w3tc_minify_flush();
444 }
445 // WP Fastest Cache.
446 if ( isset( $GLOBALS['wp_fastest_cache'] ) && is_callable( array( $GLOBALS['wp_fastest_cache'], 'deleteCache' ) ) ) {
447 $GLOBALS['wp_fastest_cache']->deleteCache( true ); // @phpstan-ignore method.nonObject
448 }
449 }
450
451 } // class TablePress_CSS
452